esphome icon indicating copy to clipboard operation
esphome copied to clipboard

[pmsx003] Replace state machine with asynchronous callbacks

Open cmuellner opened this issue 10 months ago • 27 comments

What does this implement/fix?

The current implementation of the pmsx003 component driver has the issue that it stops reporting in "passive" mode, where the sensor is put to sleep between measurements. An analysis of the code showed that a state machine is driving the program flow in ::loop() based on the current time in milli- seconds in an uint32_t.

There are a few issues with the implementation:

  • ESPhome provides an API which invites implementing event-driven (asynchronous or callback-based) component drivers. So, instead of a single state-machine that handles all program logic, we could have several callbacks with specific tasks.
  • The state machine requires 5 different variables to keep track of the current state. All of this state can be removed when using an event-driven programming style.
  • An uint32_t with milliseconds can distinguish 4.294.967.296 milliseconds. That's less than 50 days before an overflow occurs. The code does not handle this case.
  • Using {}, true and false as possible return value of a function named check_byte_() is certainly not easy to read.
  • The component driver does not take care of device quirks (e.g. my PMS5003T sends two identical data frames in passive mode after requesting one).

This PR addresses these issues by doing the following:

  • Replace ::loop() by ::setup() and time-driven callbacks using ::set_timeout().
  • Remove all timestamp comparisons.
  • Remove all state variables.
  • Add enum to better express result of check_byte_().
  • Add clear_data_buffer_() method to ensure we will never parse and publish the same measurement frame twice.
  • Match the declaration order with the implementation order.
  • Don't parse/publish data received during the stabilization period.

Testing:

  • Active and passive mode were tested for several hours.
  • Transient communication errors were accepted (disconnecting jumpwires for RX or TX and reconnecting again).
  • Tests with a PMS5003T connected to a ESP32-dev board via a adapter board. Only pins +5V, GND, TX and RX were connected.

This PR does not change the documented behavior or configuration properties. Therefore, no changes to the documentation are necessary.

Types of changes

  • [x] Bugfix (non-breaking change which fixes an issue)
  • [ ] New feature (non-breaking change which adds functionality)
  • [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • [x] Code quality improvements to existing code or addition of tests
  • [ ] Other

Related issue or feature (if applicable):

Pull request in esphome-docs with documentation (if applicable):

Test Environment

  • [x] ESP32
  • [ ] ESP32 IDF
  • [ ] ESP8266
  • [ ] RP2040
  • [ ] BK72xx
  • [ ] RTL87xx

Example entry for config.yaml:

uart:
  # https://esphome.io/components/uart.html                                          
  - id: uart0_id
    tx_pin: GPIO17                                                                                                            
    rx_pin: GPIO16
    baud_rate: 9600
  
sensor:
  - platform: pmsx003                                                                
    # https://esphome.io/components/sensor/pmsx003.html                                                                       
    type: PMS5003S
    uart_id: uart0_id
    # Uncomment the line below to run in passive mode
    update_interval: 60s                                                             
    pm_0_3um:
      name: "PMS5003S PM 0.3 um"
    pm_0_5um:
      name: "PMS5003S PM 0.5 um"
    pm_1_0_std:
      name: "PMS5003S PM 1.0 std"
    pm_1_0:
      name: "PMS5003S PM 1.0"
    pm_1_0um:
      name: "PMS5003S PM 1.0 um"
    pm_2_5_std:
      name: "PMS5003S PM 2.5 std"
    pm_2_5:
      name: "PMS5003S PM 2.5"
    pm_2_5um:
      name: "PMS5003S PM 2.5 um"
    pm_5_0um:
      name: "PMS5003S PM 5.0 um"
    pm_10_0_std:
      name: "PMS5003S PM 10 std"
    pm_10_0:
      name: "PMS5003S PM 10"
    pm_10_0um:
      name: "PMS5003S PM 10.0 um"
    formaldehyde:
      name: "PMS5003S formaldehyde"

Checklist:

  • [x] The code change is tested and works locally.
  • [ ] Tests have been added to verify that the new code works (under tests/ folder).

If user exposed functionality or configuration variables are added/changed:

cmuellner avatar Jun 02 '25 07:06 cmuellner

Hey there @ximex, mind taking a look at this pull request as it has been labeled with an integration (pmsx003) you are listed as a code owner for? Thanks! (message by CodeOwnersMention)

probot-esphome[bot] avatar Jun 02 '25 07:06 probot-esphome[bot]

Codecov Report

All modified and coverable lines are covered by tests :white_check_mark:

Project coverage is 16.48%. Comparing base (4d8b5ed) to head (a0d2c88). Report is 2973 commits behind head on dev.

Additional details and impacted files
@@             Coverage Diff             @@
##              dev    #8989       +/-   ##
===========================================
- Coverage   53.70%   16.48%   -37.23%     
===========================================
  Files          50     1166     +1116     
  Lines        9408    48593    +39185     
  Branches     1654     5832     +4178     
===========================================
+ Hits         5053     8011     +2958     
- Misses       4056    40013    +35957     
- Partials      299      569      +270     

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

codecov-commenter avatar Jun 02 '25 07:06 codecov-commenter

Updated PR:

  • Adding trailing _ after new method names to address linter errors

cmuellner avatar Jun 02 '25 07:06 cmuellner

I added what I thought was how to test this. After installing I'm not getting any readings from the PMS5003 sensor. Twice now this has happened after doing an OTA install. A full power cycle of the device gets it to start reporting again.

external_components:
  # replace 1234 with the number of the pull request
  - source: github://pr#8989
    components:
      # list all components modified by this pull request here
      - pmsx003

From the purple text when first connecting to the logs

[16:30:40][C][pmsx003:025]: PMSX003:
[16:30:40][C][pmsx003:030]:   PM1.0 'PM 1.0'
[16:30:40][C][pmsx003:030]:     Device Class: 'pm1'
[16:30:40][C][pmsx003:030]:     State Class: 'measurement'
[16:30:40][C][pmsx003:030]:     Unit of Measurement: 'µg/m³'
[16:30:40][C][pmsx003:030]:     Accuracy Decimals: 0
[16:30:40][C][pmsx003:030]:     Icon: 'mdi:chemical-weapon'
[16:30:40][C][pmsx003:031]:   PM2.5 'PM 2.5 Raw'
[16:30:40][C][pmsx003:031]:     Device Class: 'pm25'
[16:30:40][C][pmsx003:031]:     State Class: 'measurement'
[16:30:40][C][pmsx003:031]:     Unit of Measurement: 'µg/m³'
[16:30:40][C][pmsx003:031]:     Accuracy Decimals: 0
[16:30:40][C][pmsx003:031]:     Icon: 'mdi:chemical-weapon'
[16:30:40][C][pmsx003:032]:   PM10.0 'PM 10.0'
[16:30:40][C][pmsx003:032]:     Device Class: 'pm10'
[16:30:40][C][pmsx003:032]:     State Class: 'measurement'
[16:30:40][C][pmsx003:032]:     Unit of Measurement: 'µg/m³'
[16:30:40][C][pmsx003:032]:     Accuracy Decimals: 0
[16:30:40][C][pmsx003:032]:     Icon: 'mdi:chemical-weapon'
[16:30:40][C][pmsx003:034]:   PM0.3um 'PM 0.3'
[16:30:40][C][pmsx003:034]:     State Class: 'measurement'
[16:30:40][C][pmsx003:034]:     Unit of Measurement: '/dL'
[16:30:40][C][pmsx003:034]:     Accuracy Decimals: 0
[16:30:40][C][pmsx003:034]:     Icon: 'mdi:chemical-weapon'

No other data from the debug logs after 4+ minutes

sensor:
  - platform: pmsx003
    # Default interval of updating every second, but changing to 30 seconds to extend life but keep fan spinning
    # PMS5003 https://esphome.io/components/sensor/pmsx003.html
    type: PMSX003
    uart_id: pms5003_uart
    pm_2_5:
      name: "PM 2.5 Raw"
      id: pm_2_5_raw
      device_class: pm25  # Added to report properly to HomeKit
      disabled_by_default: true
    pm_1_0:
      name: "PM 1.0"
      id: pm_1_0
      device_class: pm1  # Added to report properly to HomeKit
    pm_10_0:
      name: "PM 10.0"
      id: pm_10_0
      device_class: pm10  # Added to report properly to HomeKit
    pm_0_3um:
      name: "PM 0.3"
      id: pm_0_3um
      filters:
    update_interval: 30s

MallocArray avatar Jun 02 '25 21:06 MallocArray

The logs look a bit different than I remember. I thought that it by default reports all of the values it retrieved and then would show what it was sending to HA. While looking at the logs for this PR, I'm only see one log entry from the component itself showing what it got, instead of it repeating and displaying the value

[16:51:41][D][pmsx003:242]: Got PM1.0 Standard Concentration: 2 µg/m³, PM2.5 Standard Concentration 3 µg/m³, PM10.0 Standard Concentration: 3 µg/m³, PM1.0 Concentration: 2 µg/m³, PM2.5 Concentration 3 µg/m³, PM10.0 Concentration: 3 µg/m³
[16:51:41][D][pmsx003:283]: Got PM0.3 Particles: 450 Count/0.1L, PM0.5 Particles: 130 Count/0.1L, PM1.0 Particles: 27 Count/0.1L, PM2.5 Particles 1 Count/0.1L, PM5.0 Particles: 1 Count/0.1L, PM10.0 Particles 0 Count/0.1L

Logs showing only it sending states to HA at the expected 30s interval, but not seeing anything else from pmsx003

[16:53:15][D][sensor:093]: 'WiFi Signal': Sending state -49.00000 dBm with 0 decimals of accuracy
[16:53:15][D][sensor:093]: 'VOC Index': Sending state 100.00000  with 0 decimals of accuracy
[16:53:15][D][sensor:093]: 'NOx Index': Sending state 1.00000  with 0 decimals of accuracy
[16:53:18][D][sensor:093]: 'PM 2.5': Sending state 2.65122 µg/m³ with 0 decimals of accuracy
[16:53:22][D][sensor:093]: 'Temperature Raw': Sending state 21.09598 °C with 2 decimals of accuracy
[16:53:22][D][sensor:093]: 'Temperature': Sending state 21.09598 °C with 2 decimals of accuracy
[16:53:22][D][sensor:093]: 'Humidity Raw': Sending state 54.14153 % with 2 decimals of accuracy
[16:53:22][D][sensor:093]: 'Humidity': Sending state 54.14153 % with 2 decimals of accuracy
[16:53:23][D][sensor:093]: 'Uptime': Sending state 132.81400 s with 0 decimals of accuracy
[16:53:31][D][senseair:059]: SenseAir Received CO₂=1053ppm Status=0x00
[16:53:31][D][sensor:093]: 'CO2': Sending state 1053.00000 ppm with 0 decimals of accuracy
[16:53:48][D][sensor:093]: 'PM 2.5': Sending state 2.65500 µg/m³ with 0 decimals of accuracy
[16:54:15][D][sensor:093]: 'WiFi Signal': Sending state -51.00000 dBm with 0 decimals of accuracy
[16:54:15][D][sensor:093]: 'VOC Index': Sending state 109.00000  with 0 decimals of accuracy
[16:54:15][D][sensor:093]: 'NOx Index': Sending state 1.00000  with 0 decimals of accuracy
[16:54:18][D][sensor:093]: 'PM 2.5': Sending state 2.65500 µg/m³ with 0 decimals of accuracy
[16:54:22][D][http_request.idf:041]: Received response header, name: content-length, value: 0
[16:54:22][D][sensor:093]: 'Temperature Raw': Sending state 21.06927 °C with 2 decimals of accuracy
[16:54:22][D][sensor:093]: 'Temperature': Sending state 21.06927 °C with 2 decimals of accuracy
[16:54:22][D][sensor:093]: 'Humidity Raw': Sending state 54.09575 % with 2 decimals of accuracy
[16:54:22][D][sensor:093]: 'Humidity': Sending state 54.09575 % with 2 decimals of accuracy
[16:54:23][D][sensor:093]: 'Uptime': Sending state 192.81700 s with 0 decimals of accuracy
[16:54:31][D][senseair:059]: SenseAir Received CO₂=1053ppm Status=0x00
[16:54:31][D][sensor:093]: 'CO2': Sending state 1053.00000 ppm with 0 decimals of accuracy
[16:54:48][D][sensor:093]: 'PM 2.5': Sending state 2.65895 µg/m³ with 0 decimals of accuracy

MallocArray avatar Jun 02 '25 21:06 MallocArray

Confirmed, that without this PR, I get much more information in the logs and every update_interval. Also in this PR, I was only seeing that it was sending state for PM 2.5, which is a template sensor for me. What it should be sending is "PM 2.5 Raw" along with others.

So at least the debug logging looks to be missing information, and may not be updating everything for me.

[17:48:09][D][pmsx003:221]: Got PM1.0 Standard Concentration: 5 µg/m³, PM2.5 Standard Concentration 9 µg/m³, PM10.0 Standard Concentration: 9 µg/m³, PM1.0 Concentration: 5 µg/m³, PM2.5 Concentration 9 µg/m³, PM10.0 Concentration: 9 µg/m³
[17:48:09][D][sensor:093]: 'PM 1.0': Sending state 5.00000 µg/m³ with 0 decimals of accuracy
[17:48:09][D][sensor:093]: 'PM 2.5 Raw': Sending state 9.00000 µg/m³ with 0 decimals of accuracy
[17:48:09][D][sensor:093]: 'PM 10.0': Sending state 9.00000 µg/m³ with 0 decimals of accuracy
[17:48:09][D][sensor:093]: 'PM 0.3': Sending state 966.00000 /dL with 0 decimals of accuracy
[17:48:09][D][pmsx003:262]: Got PM0.3 Particles: 966 Count/0.1L, PM0.5 Particles: 277 Count/0.1L, PM1.0 Particles: 64 Count/0.1L, PM2.5 Particles 6 Count/0.1L, PM5.0 Particles: 1 Count/0.1L, PM10.0 Particles 0 Count/0.1L

MallocArray avatar Jun 02 '25 22:06 MallocArray

I tested with a config based on your input:

substitutions:
  # Edit these:
  device_name: "my-device-1"
  device_comment: "My cool device"
  device_friendly_name: "My sensor 1"

esphome:
  name: ${device_name}
  comment: ${device_comment}
  friendly_name: ${device_friendly_name}

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

external_components:
  - source: github://pr#8989
    components: [ pmsx003 ]

uart:
  # https://esphome.io/components/uart.html
  - id: uart0_id
    tx_pin: GPIO17
    rx_pin: GPIO16
    baud_rate: 9600

sensor:
  - platform: pmsx003
    # https://esphome.io/components/sensor/pmsx003.html
    type: PMSX003
    uart_id: uart0_id
    pm_2_5:
      name: "PM 2.5 Raw"
      id: pm_2_5_raw
      device_class: pm25  # Added to report properly to HomeKit
      disabled_by_default: true
    pm_1_0:
      name: "PM 1.0"
      id: pm_1_0
      device_class: pm1  # Added to report properly to HomeKit
    pm_10_0:
      name: "PM 10.0"
      id: pm_10_0
      device_class: pm10  # Added to report properly to HomeKit
    pm_0_3um:
      name: "PM 0.3"
      id: pm_0_3um
      filters:
    update_interval: 30s

Here is what I get:

[11:39:39][I][logger:171]: Log initialized
[11:39:39][I][app:029]: Running through setup()...
[11:39:39][C][uart.arduino_esp32:077]: Setting up UART...
[11:39:39][C][pmsx003:049]: PMSX003 setup:
[11:39:39][D][pmsx003:055]: PMSX003 update_interval: 30000
[11:39:39][D][pmsx003:066]: Using passive measurment
[11:39:39][I][app:062]: setup() finished successfully!
[11:39:39][I][app:100]: ESPHome version 2025.2.0 compiled on Jun  3 2025, 11:38:56
[11:39:39][C][logger:177]: Logger:
[11:39:39][C][logger:178]:   Max Level: DEBUG
[11:39:39][C][logger:179]:   Initial Level: DEBUG
[11:39:39][C][logger:181]:   Log Baud Rate: 115200
[11:39:39][C][logger:182]:   Hardware UART: UART0
[11:39:39][C][uart.arduino_esp32:151]: UART Bus 1:
[11:39:39][C][uart.arduino_esp32:152]:   TX Pin: GPIO17
[11:39:39][C][uart.arduino_esp32:153]:   RX Pin: GPIO16
[11:39:39][C][uart.arduino_esp32:155]:   RX Buffer Size: 256
[11:39:39][C][uart.arduino_esp32:157]:   Baud Rate: 9600 baud
[11:39:39][C][uart.arduino_esp32:158]:   Data Bits: 8
[11:39:39][C][uart.arduino_esp32:159]:   Parity: NONE
[11:39:40][C][uart.arduino_esp32:160]:   Stop bits: 1
[11:39:40][C][pmsx003:025]: PMSX003:
[11:39:40][C][pmsx003:030]:   PM1.0 'PM 1.0'
[11:39:40][C][pmsx003:030]:     Device Class: 'pm1'
[11:39:40][C][pmsx003:030]:     State Class: 'measurement'
[11:39:40][C][pmsx003:030]:     Unit of Measurement: 'µg/m³'
[11:39:40][C][pmsx003:030]:     Accuracy Decimals: 0
[11:39:40][C][pmsx003:030]:     Icon: 'mdi:chemical-weapon'
[11:39:40][C][pmsx003:031]:   PM2.5 'PM 2.5 Raw'
[11:39:40][C][pmsx003:031]:     Device Class: 'pm25'
[11:39:40][C][pmsx003:031]:     State Class: 'measurement'
[11:39:40][C][pmsx003:031]:     Unit of Measurement: 'µg/m³'
[11:39:40][C][pmsx003:031]:     Accuracy Decimals: 0
[11:39:40][C][pmsx003:031]:     Icon: 'mdi:chemical-weapon'
[11:39:40][C][pmsx003:032]:   PM10.0 'PM 10.0'
[11:39:40][C][pmsx003:032]:     Device Class: 'pm10'
[11:39:40][C][pmsx003:032]:     State Class: 'measurement'
[11:39:40][C][pmsx003:032]:     Unit of Measurement: 'µg/m³'
[11:39:40][C][pmsx003:032]:     Accuracy Decimals: 0
[11:39:40][C][pmsx003:032]:     Icon: 'mdi:chemical-weapon'
[11:39:40][C][pmsx003:034]:   PM0.3um 'PM 0.3'
[11:39:40][C][pmsx003:034]:     State Class: 'measurement'
[11:39:40][C][pmsx003:034]:     Unit of Measurement: '/dL'
[11:39:40][C][pmsx003:034]:     Accuracy Decimals: 0
[11:39:40][C][pmsx003:034]:     Icon: 'mdi:chemical-weapon'
[11:40:10][D][pmsx003:247]: Got PM1.0 Standard Concentration: 0 µg/m³, PM2.5 Standard Concentration 2 µg/m³, PM10.0 Standard Concentration: 2 µg/m³, PM1.0 Concentration: 0 µg/m³, PM2.5 Concentration 2 µg/m³, PM10.0 Concentration: 2 µg/m³
[11:40:10][D][sensor:094]: 'PM 1.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[11:40:10][D][sensor:094]: 'PM 2.5 Raw': Sending state 2.00000 µg/m³ with 0 decimals of accuracy
[11:40:10][D][sensor:094]: 'PM 10.0': Sending state 2.00000 µg/m³ with 0 decimals of accuracy
[11:40:10][D][sensor:094]: 'PM 0.3': Sending state 433.00000 /dL with 0 decimals of accuracy
[11:40:10][D][pmsx003:287]: Got PM0.3 Particles: 433 Count/0.1L, PM0.5 Particles: 261 Count/0.1L, PM1.0 Particles: 40 Count/0.1L, PM2.5 Particles 4 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[11:40:10][W][component:237]: Component pmsx003.sensor took a long time for an operation (75 ms).
[11:40:10][W][component:238]: Components should block for at most 30 ms.
[11:40:41][D][pmsx003:247]: Got PM1.0 Standard Concentration: 0 µg/m³, PM2.5 Standard Concentration 3 µg/m³, PM10.0 Standard Concentration: 3 µg/m³, PM1.0 Concentration: 0 µg/m³, PM2.5 Concentration 3 µg/m³, PM10.0 Concentration: 3 µg/m³
[11:40:41][D][sensor:094]: 'PM 1.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[11:40:41][D][sensor:094]: 'PM 2.5 Raw': Sending state 3.00000 µg/m³ with 0 decimals of accuracy
[11:40:41][D][sensor:094]: 'PM 10.0': Sending state 3.00000 µg/m³ with 0 decimals of accuracy
[11:40:41][D][sensor:094]: 'PM 0.3': Sending state 251.00000 /dL with 0 decimals of accuracy
[11:40:41][D][pmsx003:287]: Got PM0.3 Particles: 251 Count/0.1L, PM0.5 Particles: 210 Count/0.1L, PM1.0 Particles: 48 Count/0.1L, PM2.5 Particles 4 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[11:40:41][W][component:237]: Component pmsx003.sensor took a long time for an operation (74 ms).
[11:40:41][W][component:238]: Components should block for at most 30 ms.
[11:41:42][D][pmsx003:247]: Got PM1.0 Standard Concentration: 0 µg/m³, PM2.5 Standard Concentration 3 µg/m³, PM10.0 Standard Concentration: 3 µg/m³, PM1.0 Concentration: 0 µg/m³, PM2.5 Concentration 3 µg/m³, PM10.0 Concentration: 3 µg/m³
[11:41:42][D][sensor:094]: 'PM 1.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[11:41:42][D][sensor:094]: 'PM 2.5 Raw': Sending state 3.00000 µg/m³ with 0 decimals of accuracy
[11:41:42][D][sensor:094]: 'PM 10.0': Sending state 3.00000 µg/m³ with 0 decimals of accuracy
[11:41:42][D][sensor:094]: 'PM 0.3': Sending state 455.00000 /dL with 0 decimals of accuracy
[11:41:42][D][pmsx003:287]: Got PM0.3 Particles: 455 Count/0.1L, PM0.5 Particles: 294 Count/0.1L, PM1.0 Particles: 44 Count/0.1L, PM2.5 Particles 2 Count/0.1L, PM5.0 Particles: 2 Count/0.1L, PM10.0 Particles 0 Count/0.1L

So, initially the log says that it will run in "passive measurment" mode (I just saw the typo now). Then, data will come in 30-second intervals.

If you don't get any sensor output, then please make sure that the TX and RX lines are properly connected.

cmuellner avatar Jun 03 '25 21:06 cmuellner

Updated PR:

  • Fixing type ("measurment" -> "measurement")
  • Rebased on origin/dev

cmuellner avatar Jun 03 '25 21:06 cmuellner

As far as I know, I have the UART pins connected and in the non-PR I am able to control how often it updates. But I'm still not getting any data from the PMS5003 with this PR. I am using esp-idf if that matters

substitutions:
  name: "ag-one"
  friendly_name: "AG One"
  name_add_mac_suffix: "true"

external_components:
  # replace 1234 with the number of the pull request
  - source: github://pr#8989
    components:
      # list all components modified by this pull request here
      - pmsx003

api:  

ota:  
  platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

esphome:
  name: "${name}"
  friendly_name: "${friendly_name}"
  name_add_mac_suffix: ${name_add_mac_suffix}  # Set to false if you don't want part of the MAC address in the name

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf

logger:
  logs:
    component: ERROR  # Hiding warning messages about component taking a long time https://github.com/esphome/issues/issues/4717

uart:
  # https://esphome.io/components/uart.html#uart
  - rx_pin: GPIO0  # Pin 12
    tx_pin: GPIO1  # Pin 13
    baud_rate: 9600
    id: senseair_co2_uart

  - rx_pin: GPIO20  # Pin 30 or RX
    tx_pin: GPIO21  # Pin 31 or TX
    baud_rate: 9600
    id: pms5003_uart

i2c:
  # https://esphome.io/components/i2c.html
  sda: GPIO7 # Pin 21
  scl: GPIO6 # Pin 20
  frequency: 400kHz  # 400kHz eliminates warnings about components taking a long time other than SGP40 component: https://github.com/esphome/issues/issues/4717

sensor:
  - platform: sht4x
    # SHT40 https://esphome.io/components/sensor/sht4x.html
    temperature:
      name: "Temperature Raw"
      id: temp_raw
      disabled_by_default: true
    humidity:
      name: "Humidity Raw"
      id: humidity_raw
      disabled_by_default: true
    address: 0x44

  - platform: copy
    source_id: temp_raw
    name: "Temperature"
    id: temp
    # https://www.airgradient.com/documentation/correction-algorithms/
    # No current correction for indoor devices, but structure in place if developed

  - platform: copy
    source_id: humidity_raw
    name: "Humidity"
    id: humidity
    # https://www.airgradient.com/documentation/correction-algorithms/
    # No current correction for indoor devices, but structure in place if developed

  - platform: pmsx003
    # Default interval of updating every second, but changing to 30 seconds to extend life but keep fan spinning
    # PMS5003 https://esphome.io/components/sensor/pmsx003.html
    type: PMSX003
    uart_id: pms5003_uart
    pm_2_5:
      name: "PM 2.5 Raw"
      id: pm_2_5_raw
      device_class: pm25  # Added to report properly to HomeKit
      disabled_by_default: true
    pm_1_0:
      name: "PM 1.0"
      id: pm_1_0
      device_class: pm1  # Added to report properly to HomeKit
    pm_10_0:
      name: "PM 10.0"
      id: pm_10_0
      device_class: pm10  # Added to report properly to HomeKit
    pm_0_3um:
      name: "PM 0.3"
      id: pm_0_3um
      filters:
    update_interval: 30s

  - platform: template
    # Implements PM2.5 correction algorithm supported by AirGradient from EPA and optionally batch specific corrections
    # https://www.airgradient.com/documentation/correction-algorithms/
    # https://document.airnow.gov/airnow-fire-and-smoke-map-questions-and-answers.pdf
    name: "PM 2.5"
    id: pm_2_5
    update_interval: 30s
    device_class: pm25  # Added to report properly to HomeKit
    unit_of_measurement: µg/m³
    icon: mdi:chemical-weapon
    accuracy_decimals: 0
    state_class: measurement
    lambda: |-
      // https://www.airgradient.com/blog/low-readings-from-pms5003/
      // Correction for sensor batches after 20231030
      float pm_2_5_calibrated_low = $pm_2_5_scaling_factor * id(pm_0_3um).state + $pm_2_5_intercept;
      float pm_2_5_calibrated = 0;
      if (pm_2_5_calibrated_low < 31) {
        pm_2_5_calibrated = pm_2_5_calibrated_low;
      } else {
        pm_2_5_calibrated = id(pm_2_5_raw).state;
      }
      if (pm_2_5_calibrated < 0) {
        pm_2_5_calibrated = 0;
      }
      // EPA Formula
      float result = 0.0;
      if (pm_2_5_calibrated == 0.0) {
        result = 0.0;
      } else if (pm_2_5_calibrated < 30.0) {
        result = (0.524 * pm_2_5_calibrated) - (0.0862 * id(humidity_raw).state) + 5.75;
      } else if (pm_2_5_calibrated < 50.0) {
        result = (0.786 * (pm_2_5_calibrated / 20 - 3/2) + 0.524 * (1 - (pm_2_5_calibrated / 20 - 3/2))) * pm_2_5_calibrated - (0.0862 * id(humidity_raw).state) + 5.75;
      } else if (pm_2_5_calibrated < 210.0) {
        result = (0.786 * pm_2_5_calibrated) - (0.0862 * id(humidity_raw).state) + 5.75;
      } else if (pm_2_5_calibrated < 260.0) {
        result = (0.69 * (pm_2_5_calibrated / 50 - 21/5) + 0.786 * (1 - (pm_2_5_calibrated / 50 - 21/5))) * pm_2_5_calibrated - (0.0862 * id(humidity_raw).state * (1 - (pm_2_5_calibrated / 50 - 21/5))) + (2.966 * (pm_2_5_calibrated / 50 - 21/5)) + (5.75 * (1 - (pm_2_5_calibrated / 50 - 21/5))) + (8.84 * pow(10,-4) * pow(pm_2_5_calibrated,2) * (pm_2_5_calibrated / 50 - 21/5));
      } else {
        result = 2.966 + (0.69 * pm_2_5_calibrated) + (8.84 * pow(10,-4) * pow(pm_2_5_calibrated,2));
      }
      if (result <= 0.0) {
        return 0.0;
      } else {
        return result;
      }

  - platform: template
    # Depends on another sensor providing an ID of pm_2_5 such as a pms5003
    name: "PM 2.5 AQI"
    id: pm_2_5_aqi
    update_interval: 5 min
    device_class: aqi
    icon: "mdi:air-filter"
    accuracy_decimals: 0
    filters:
      - skip_initial: 1  # Need valid data from PM 2.5 sensor before able to calculate
    lambda: |-
      // 2024 update from EPA https://www.epa.gov/system/files/documents/2024-02/pm-naaqs-air-quality-index-fact-sheet.pdf
      // https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
      // Borrowed from https://github.com/kylemanna/sniffer/blob/master/esphome/sniffer_common.yaml
      if (id(pm_2_5).state <= 9.0) {
      // good
      return((50.0 - 0.0) / (9.0 - 0.0) * (id(pm_2_5).state - 0.0) + 0.0);
      } else if (id(pm_2_5).state <= 35.4) {
      // moderate
      return((100.0 - 51.0) / (35.4 - 9.1) * (id(pm_2_5).state - 9.1) + 51.0);
      } else if (id(pm_2_5).state <= 55.4) {
      // usg
      return((150.0 - 101.0) / (55.4 - 35.5) * (id(pm_2_5).state - 35.5) + 101.0);
      } else if (id(pm_2_5).state <= 125.4) {
      // unhealthy
      return((200.0 - 151.0) / (125.4 - 55.5) * (id(pm_2_5).state - 55.5) + 151.0);
      } else if (id(pm_2_5).state <= 225.4) {
      // very unhealthy
      return((300.0 - 201.0) / (225.4 - 125.5) * (id(pm_2_5).state - 125.5) + 201.0);
      } else if (id(pm_2_5).state <= 325.4) {
      // hazardous
      return((500.0 - 301.0) / (325.4 - 225.5) * (id(pm_2_5).state - 225.5) + 301.0);
      } else {
      return(500);
      }

All I get in the logs is temp/humidity and my template sensor which can't calculate a new value since it isn't getting data from PM2.5 Raw

[06:45:48][D][sensor:093]: 'Temperature Raw': Sending state 20.46044 °C with 2 decimals of accuracy
[06:45:48][D][sensor:093]: 'Temperature': Sending state 20.46044 °C with 2 decimals of accuracy
[06:45:48][D][sensor:093]: 'Humidity Raw': Sending state 56.70504 % with 2 decimals of accuracy
[06:45:48][D][sensor:093]: 'Humidity': Sending state 56.70504 % with 2 decimals of accuracy
[06:46:05][D][sensor:093]: 'PM 2.5': Sending state nan µg/m³ with 0 decimals of accuracy
[06:46:35][D][sensor:093]: 'PM 2.5': Sending state nan µg/m³ with 0 decimals of accuracy
[06:46:48][D][sensor:093]: 'Temperature Raw': Sending state 20.47112 °C with 2 decimals of accuracy
[06:46:48][D][sensor:093]: 'Temperature': Sending state 20.47112 °C with 2 decimals of accuracy
[06:46:48][D][sensor:093]: 'Humidity Raw': Sending state 56.65545 % with 2 decimals of accuracy
[06:46:48][D][sensor:093]: 'Humidity': Sending state 56.65545 % with 2 decimals of accuracy
[06:47:05][D][sensor:093]: 'PM 2.5': Sending state nan µg/m³ with 0 decimals of accuracy

MallocArray avatar Jun 04 '25 11:06 MallocArray

I have an ESP32-C3 Super Mini here, which I also tested successfully.

My config file (note the esp-idf):

$ cat pmsx003-sensor-mallocarray-c3.yaml 
# Run:
#   esphome run --device /dev/ttyACM0 pmsx003-sensor-mallocarray-c3.yaml

substitutions:
  device_name: "my-device-1"
  device_comment: "My cool device"
  device_friendly_name: "My sensor 1"

esphome:
  name: ${device_name}
  comment: ${device_comment}
  friendly_name: ${device_friendly_name}
  platformio_options:
    board_build.flash_mode: dio

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf

# Enable logging
logger:

external_components:
  - source: github://pr#8989
    components: [ pmsx003 ]

uart:
  # https://esphome.io/components/uart.html
  - id: uart0_id
    rx_pin: GPIO20
    tx_pin: GPIO21
    baud_rate: 9600

sensor:
  - platform: pmsx003
    # https://esphome.io/components/sensor/pmsx003.html
    type: PMSX003
    uart_id: uart0_id
    pm_2_5:
      name: "PM 2.5 Raw"
      id: pm_2_5_raw
      device_class: pm25  # Added to report properly to HomeKit
      disabled_by_default: true
    pm_1_0:
      name: "PM 1.0"
      id: pm_1_0
      device_class: pm1  # Added to report properly to HomeKit
    pm_10_0:
      name: "PM 10.0"
      id: pm_10_0
      device_class: pm10  # Added to report properly to HomeKit
    pm_0_3um:
      name: "PM 0.3"
      id: pm_0_3um
      filters:
    #update_interval: 30s

Log output:

[17:20:47]I (120) esp_image: segment 1: paddr=0001bcb0 vaddr=3fc8ae00 size=010b0h (  4272) load
[17:20:47]I (122) esp_image: segment 2: paddr=0001cd68 vaddr=40380000 size=032b0h ( 12976) load
[17:20:47]I (132) esp_image: segment 3: paddr=00020020 vaddr=42000020 size=23cf0h (146672) map
[17:20:47]I (161) esp_image: segment 4: paddr=00043d18 vaddr=403832b0 size=079ech ( 31212) load
[17:20:47]I (171) boot: Loaded app from partition at offset 0x10000
[17:20:47]I (201) boot: Set actual ota_seq=1 in otadata[0]
[17:20:47]I (201) boot: Disabling RNG early entropy source...
[17:20:47]I (201) cpu_start: Unicore app
[17:20:47]I (204) cpu_start: Pro cpu up.
[17:20:47]I (219) cpu_start: Pro cpu start user code
[17:20:47]I (219) cpu_start: cpu freq: 160000000 Hz
[17:20:47]I (219) cpu_start: Application information:
[17:20:47]I (222) cpu_start: Project name:     my-device-1
[17:20:47]I (227) cpu_start: App version:      2025.2.0
[17:20:47]I (232) cpu_start: Compile time:     Jun  4 2025 17:19:56
[17:20:47]I (239) cpu_start: ELF file SHA256:  c0cd3a6a7b564319...
[17:20:47]I (245) cpu_start: ESP-IDF:          5.1.5
[17:20:47]I (249) cpu_start: Min chip rev:     v0.3
[17:20:47]I (254) cpu_start: Max chip rev:     v1.99 
[17:20:47]I (259) cpu_start: Chip rev:         v0.4
[17:20:47]I (264) heap_init: Initializing. RAM available for dynamic allocation:
[17:20:47]I (271) heap_init: At 3FC8CEB0 len 00033150 (204 KiB): DRAM
[17:20:47]I (277) heap_init: At 3FCC0000 len 0001C710 (113 KiB): DRAM/RETENTION
[17:20:47]I (284) heap_init: At 3FCDC710 len 00002950 (10 KiB): DRAM/RETENTION/STACK
[17:20:47]I (292) heap_init: At 50000010 len 00001FD8 (7 KiB): RTCRAM
[17:20:47]I (299) spi_flash: detected chip: generic
[17:20:47]I (303) spi_flash: flash io: dio
[17:20:47]I (307) sleep: Configure to isolate all GPIO pins in sleep state
[17:20:47]I (313) sleep: Enable automatic switching of GPIO sleep configuration
[17:20:47]I (321) app_start: Starting scheduler on CPU0
[17:20:47]I (325) main_task: Started on CPU0
[17:20:47]I (325) main_task: Calling app_main()
[17:20:47][I][logger:171]: Log initialized
[17:20:47][I][app:029]: Running through setup()...
[17:20:47][C][uart.idf:093]: Setting up UART 0...
[17:20:47][D][esp-idf:000]: I (395) uart: queue free spaces: 20
[17:20:47]
[17:20:47][C][pmsx003:049]: PMSX003 setup:
[17:20:47][D][pmsx003:055]: PMSX003 update_interval: 0
[17:20:47][D][pmsx003:060]: Using active measurement
[17:20:47][I][app:062]: setup() finished successfully!
[17:20:47][I][app:100]: ESPHome version 2025.2.0 compiled on Jun  4 2025, 17:19:52
[17:20:47][C][logger:177]: Logger:
[17:20:47][C][logger:178]:   Max Level: DEBUG
[17:20:47][C][logger:179]:   Initial Level: DEBUG
[17:20:47][C][logger:181]:   Log Baud Rate: 115200
[17:20:47][C][logger:182]:   Hardware UART: USB_SERIAL_JTAG
[17:20:47][C][uart.idf:159]: UART Bus 0:
[17:20:47][C][uart.idf:160]:   TX Pin: GPIO21
[17:20:47][C][uart.idf:161]:   RX Pin: GPIO20
[17:20:47][C][uart.idf:163]:   RX Buffer Size: 256
[17:20:47][C][uart.idf:165]:   Baud Rate: 9600 baud
[17:20:47][C][uart.idf:166]:   Data Bits: 8
[17:20:47][C][uart.idf:167]:   Parity: NONE
[17:20:47][C][uart.idf:168]:   Stop bits: 1
[17:20:47][C][pmsx003:025]: PMSX003:
[17:20:47][C][pmsx003:030]:   PM1.0 'PM 1.0'
[17:20:47][C][pmsx003:030]:     Device Class: 'pm1'
[17:20:47][C][pmsx003:030]:     State Class: 'measurement'
[17:20:47][C][pmsx003:030]:     Unit of Measurement: 'µg/m³'
[17:20:47][C][pmsx003:030]:     Accuracy Decimals: 0
[17:20:47][C][pmsx003:030]:     Icon: 'mdi:chemical-weapon'
[17:20:47][C][pmsx003:031]:   PM2.5 'PM 2.5 Raw'
[17:20:47][C][pmsx003:031]:     Device Class: 'pm25'
[17:20:47][C][pmsx003:031]:     State Class: 'measurement'
[17:20:47][C][pmsx003:031]:     Unit of Measurement: 'µg/m³'
[17:20:47][C][pmsx003:031]:     Accuracy Decimals: 0
[17:20:47][C][pmsx003:031]:     Icon: 'mdi:chemical-weapon'
[17:20:47][C][pmsx003:032]:   PM10.0 'PM 10.0'
[17:20:47][C][pmsx003:032]:     Device Class: 'pm10'
[17:20:47][C][pmsx003:032]:     State Class: 'measurement'
[17:20:47][C][pmsx003:032]:     Unit of Measurement: 'µg/m³'
[17:20:47][C][pmsx003:032]:     Accuracy Decimals: 0
[17:20:47][C][pmsx003:032]:     Icon: 'mdi:chemical-weapon'
[17:20:47][C][pmsx003:034]:   PM0.3um 'PM 0.3'
[17:20:47][C][pmsx003:034]:     State Class: 'measurement'
[17:20:47][C][pmsx003:034]:     Unit of Measurement: '/dL'
[17:20:47][C][pmsx003:034]:     Accuracy Decimals: 0
[17:20:47][C][pmsx003:034]:     Icon: 'mdi:chemical-weapon'
[17:21:18][D][pmsx003:242]: Got PM1.0 Standard Concentration: 2 µg/m³, PM2.5 Standard Concentration 4 µg/m³, PM10.0 Standard Concentration: 4 µg/m³, PM1.0 Concentration: 2 µg/m³, PM2.5 Concentration 4 µg/m³, PM10.0 Concentration: 4 µg/m³
[17:21:18][D][sensor:093]: 'PM 1.0': Sending state 2.00000 µg/m³ with 0 decimals of accuracy
[17:21:18][D][sensor:093]: 'PM 2.5 Raw': Sending state 4.00000 µg/m³ with 0 decimals of accuracy
[17:21:18][D][sensor:093]: 'PM 10.0': Sending state 4.00000 µg/m³ with 0 decimals of accuracy
[17:21:18][D][sensor:093]: 'PM 0.3': Sending state 351.00000 /dL with 0 decimals of accuracy
[17:21:18][D][pmsx003:283]: Got PM0.3 Particles: 351 Count/0.1L, PM0.5 Particles: 309 Count/0.1L, PM1.0 Particles: 44 Count/0.1L, PM2.5 Particles 0 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[17:21:19][D][pmsx003:242]: Got PM1.0 Standard Concentration: 2 µg/m³, PM2.5 Standard Concentration 4 µg/m³, PM10.0 Standard Concentration: 4 µg/m³, PM1.0 Concentration: 2 µg/m³, PM2.5 Concentration 4 µg/m³, PM10.0 Concentration: 4 µg/m³
[17:21:19][D][sensor:093]: 'PM 1.0': Sending state 2.00000 µg/m³ with 0 decimals of accuracy
[17:21:19][D][sensor:093]: 'PM 2.5 Raw': Sending state 4.00000 µg/m³ with 0 decimals of accuracy
[17:21:19][D][sensor:093]: 'PM 10.0': Sending state 4.00000 µg/m³ with 0 decimals of accuracy
[17:21:19][D][sensor:093]: 'PM 0.3': Sending state 379.00000 /dL with 0 decimals of accuracy
[17:21:19][D][pmsx003:283]: Got PM0.3 Particles: 379 Count/0.1L, PM0.5 Particles: 333 Count/0.1L, PM1.0 Particles: 42 Count/0.1L, PM2.5 Particles 0 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[17:21:20][D][pmsx003:242]: Got PM1.0 Standard Concentration: 2 µg/m³, PM2.5 Standard Concentration 4 µg/m³, PM10.0 Standard Concentration: 4 µg/m³, PM1.0 Concentration: 2 µg/m³, PM2.5 Concentration 4 µg/m³, PM10.0 Concentration: 4 µg/m³
[17:21:20][D][sensor:093]: 'PM 1.0': Sending state 2.00000 µg/m³ with 0 decimals of accuracy
[17:21:20][D][sensor:093]: 'PM 2.5 Raw': Sending state 4.00000 µg/m³ with 0 decimals of accuracy
[17:21:20][D][sensor:093]: 'PM 10.0': Sending state 4.00000 µg/m³ with 0 decimals of accuracy
[17:21:20][D][sensor:093]: 'PM 0.3': Sending state 373.00000 /dL with 0 decimals of accuracy
[17:21:20][D][pmsx003:283]: Got PM0.3 Particles: 373 Count/0.1L, PM0.5 Particles: 327 Count/0.1L, PM1.0 Particles: 38 Count/0.1L, PM2.5 Particles 0 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[17:21:21][D][pmsx003:242]: Got PM1.0 Standard Concentration: 3 µg/m³, PM2.5 Standard Concentration 5 µg/m³, PM10.0 Standard Concentration: 5 µg/m³, PM1.0 Concentration: 3 µg/m³, PM2.5 Concentration 5 µg/m³, PM10.0 Concentration: 5 µg/m³
[17:21:21][D][sensor:093]: 'PM 1.0': Sending state 3.00000 µg/m³ with 0 decimals of accuracy
[17:21:21][D][sensor:093]: 'PM 2.5 Raw': Sending state 5.00000 µg/m³ with 0 decimals of accuracy
[17:21:21][D][sensor:093]: 'PM 10.0': Sending state 5.00000 µg/m³ with 0 decimals of accuracy
[17:21:21][D][sensor:093]: 'PM 0.3': Sending state 388.00000 /dL with 0 decimals of accuracy
[...]

Data flows as expected.

I'm sorry, but I can't reproduce the issue here. And I can't see why you would not get any data besides connection issues.

cmuellner avatar Jun 04 '25 15:06 cmuellner

I'm not sure either, but it is certainly related to changes in this PR. I removed the update_interval so it is using the default of every second and still get nothing.

Removing the PR external_component restores functionality with both no update_interval and my 30s setting

MallocArray avatar Jun 04 '25 17:06 MallocArray

I'm not sure either, but it is certainly related to changes in this PR. I removed the update_interval so it is using the default of every second and still get nothing.

Removing the PR external_component restores functionality with both no update_interval and my 30s setting

I've updated this PR to include log output whenever one of the functions is entered. Please do the following:

  • Provide the output of esphome --version
  • Use a config file similar to what was provided (i.e., remove all other components to rule out other error sources)
  • Provide a full log since startup
  • Provide information about the board that you are using

cmuellner avatar Jun 04 '25 18:06 cmuellner

@ximex, it would be great if you could quickly test this PR.

cmuellner avatar Jun 04 '25 18:06 cmuellner

i did a very quick test. my esp32c6 is crashing and rebooting. more debugging maybe on friday

ximex avatar Jun 04 '25 18:06 ximex

Also works on an ESP32-C6 here:

$ cat pmsx003-fix-esp32-c6.yaml 
# Run:
#   esphome run --device /dev/ttyACM0 pmsx003-fix-esp32-c3.yaml

esphome:
  name: "my-device-1"

esp32:
  board: esp32-c6-devkitc-1
  flash_size: 8MB
  variant: esp32c6
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESPTOOLPY_FLASHSIZE_8MB: y

logger:

external_components:
  - source: github://pr#8989
    components: [ pmsx003 ]

uart:
  - id: uart0_id
    rx_pin: GPIO04
    tx_pin: GPIO05
    baud_rate: 9600

sensor:
  - platform: pmsx003
    type: PMSX003
    uart_id: uart0_id
    pm_2_5:
      name: "PM 2.5"

Log:

[22:06:20]I (121) esp_image: segment 1: paddr=0001b5e8 vaddr=40800000 size=04a30h ( 18992) load
[22:06:20]I (126) esp_image: segment 2: paddr=00020020 vaddr=42000020 size=1b3a0h (111520) map
[22:06:20]I (151) esp_image: segment 3: paddr=0003b3c8 vaddr=40804a30 size=0597ch ( 22908) load
[22:06:20]I (157) esp_image: segment 4: paddr=00040d4c vaddr=4080a3b0 size=014cch (  5324) load
[22:06:20]I (161) boot: Loaded app from partition at offset 0x10000
[22:06:20]I (204) boot: Set actual ota_seq=1 in otadata[0]
[22:06:20]I (204) boot: Disabling RNG early entropy source...
[22:06:20]I (205) cpu_start: Unicore app
[22:06:20]I (207) cpu_start: Pro cpu up.
[22:06:20]W (222) clk: esp_perip_clk_init() has not been implemented yet
[22:06:20]I (222) cpu_start: Pro cpu start user code
[22:06:20]I (223) cpu_start: cpu freq: 160000000 Hz
[22:06:20]I (227) cpu_start: Application information:
[22:06:20]I (231) cpu_start: Project name:     my-device-1
[22:06:20]I (237) cpu_start: App version:      2025.2.0
[22:06:20]I (242) cpu_start: Compile time:     Jun  4 2025 21:56:39
[22:06:20]I (248) cpu_start: ELF file SHA256:  0ae53b9e58854fb2...
[22:06:20]I (254) cpu_start: ESP-IDF:          5.1.5
[22:06:20]I (259) cpu_start: Min chip rev:     v0.0
[22:06:20]I (263) cpu_start: Max chip rev:     v0.99 
[22:06:20]I (268) cpu_start: Chip rev:         v0.1
[22:06:20]I (273) heap_init: Initializing. RAM available for dynamic allocation:
[22:06:20]I (280) heap_init: At 4080C8E0 len 0006FD30 (447 KiB): D/IRAM
[22:06:20]I (286) heap_init: At 4087C610 len 00002F54 (11 KiB): STACK/DIRAM
[22:06:20]I (293) heap_init: At 50000000 len 00003FE8 (15 KiB): RTCRAM
[22:06:20]I (300) spi_flash: detected chip: generic
[22:06:20]I (304) spi_flash: flash io: dio
[22:06:20]I (308) sleep: Configure to isolate all GPIO pins in sleep state
[22:06:20]I (315) sleep: Enable automatic switching of GPIO sleep configuration
[22:06:20]I (322) coexist: coex firmware version: 27d8387
[22:06:20]I (322) coexist: coexist rom version 5b8dcfa
[22:06:20]I (323) app_start: Starting scheduler on CPU0
[22:06:20]I (327) main_task: Started on CPU0
[22:06:20]I (327) main_task: Calling app_main()
[22:06:20][I][logger:171]: Log initialized
[22:06:20][I][app:029]: Running through setup()...
[22:06:20][C][uart.idf:093]: Setting up UART 0...
[22:06:20][D][esp-idf:000]: I (427) uart: queue free spaces: 20
[22:06:20]
[22:06:20][C][pmsx003:049]: PMSX003 setup:
[22:06:20][D][pmsx003:055]: PMSX003 update_interval: 0
[22:06:20][D][pmsx003:060]: Using active measurement
[22:06:20][I][app:062]: setup() finished successfully!
[22:06:20][I][app:100]: ESPHome version 2025.2.0 compiled on Jun  4 2025, 21:56:36
[22:06:20][C][logger:177]: Logger:
[22:06:20][C][logger:178]:   Max Level: DEBUG
[22:06:20][C][logger:179]:   Initial Level: DEBUG
[22:06:20][C][logger:181]:   Log Baud Rate: 115200
[22:06:20][C][logger:182]:   Hardware UART: USB_SERIAL_JTAG
[22:06:20][C][uart.idf:159]: UART Bus 0:
[22:06:20][C][uart.idf:160]:   TX Pin: GPIO5
[22:06:20][C][uart.idf:161]:   RX Pin: GPIO4
[22:06:20][C][uart.idf:163]:   RX Buffer Size: 256
[22:06:20][C][uart.idf:165]:   Baud Rate: 9600 baud
[22:06:20][C][uart.idf:166]:   Data Bits: 8
[22:06:20][C][uart.idf:167]:   Parity: NONE
[22:06:20][C][uart.idf:168]:   Stop bits: 1
[22:06:20][C][pmsx003:025]: PMSX003:
[22:06:20][C][pmsx003:031]:   PM2.5 'PM 2.5'
[22:06:20][C][pmsx003:031]:     Device Class: 'pm25'
[22:06:20][C][pmsx003:031]:     State Class: 'measurement'
[22:06:20][C][pmsx003:031]:     Unit of Measurement: 'µg/m³'
[22:06:20][C][pmsx003:031]:     Accuracy Decimals: 0
[22:06:20][C][pmsx003:031]:     Icon: 'mdi:chemical-weapon'
[22:06:51][D][pmsx003:242]: Got PM1.0 Standard Concentration: 5 µg/m³, PM2.5 Standard Concentration 12 µg/m³, PM10.0 Standard Concentration: 12 µg/m³, PM1.0 Concentration: 5 µg/m³, PM2.5 Concentration 12 µg/m³, PM10.0 Concentration: 12 µg/m³
[22:06:51][D][sensor:093]: 'PM 2.5': Sending state 12.00000 µg/m³ with 0 decimals of accuracy
[22:06:51][D][pmsx003:283]: Got PM0.3 Particles: 610 Count/0.1L, PM0.5 Particles: 494 Count/0.1L, PM1.0 Particles: 107 Count/0.1L, PM2.5 Particles 4 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[22:06:52][D][pmsx003:242]: Got PM1.0 Standard Concentration: 5 µg/m³, PM2.5 Standard Concentration 12 µg/m³, PM10.0 Standard Concentration: 12 µg/m³, PM1.0 Concentration: 5 µg/m³, PM2.5 Concentration 12 µg/m³, PM10.0 Concentration: 12 µg/m³
[22:06:52][D][sensor:093]: 'PM 2.5': Sending state 12.00000 µg/m³ with 0 decimals of accuracy
[22:06:52][D][pmsx003:283]: Got PM0.3 Particles: 598 Count/0.1L, PM0.5 Particles: 485 Count/0.1L, PM1.0 Particles: 109 Count/0.1L, PM2.5 Particles 6 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[22:06:53][D][pmsx003:242]: Got PM1.0 Standard Concentration: 5 µg/m³, PM2.5 Standard Concentration 12 µg/m³, PM10.0 Standard Concentration: 12 µg/m³, PM1.0 Concentration: 5 µg/m³, PM2.5 Concentration 12 µg/m³, PM10.0 Concentration: 12 µg/m³
[22:06:53][D][sensor:093]: 'PM 2.5': Sending state 12.00000 µg/m³ with 0 decimals of accuracy
[22:06:53][D][pmsx003:283]: Got PM0.3 Particles: 578 Count/0.1L, PM0.5 Particles: 472 Count/0.1L, PM1.0 Particles: 111 Count/0.1L, PM2.5 Particles 6 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[22:06:54][D][pmsx003:242]: Got PM1.0 Standard Concentration: 5 µg/m³, PM2.5 Standard Concentration 13 µg/m³, PM10.0 Standard Concentration: 13 µg/m³, PM1.0 Concentration: 5 µg/m³, PM2.5 Concentration 13 µg/m³, PM10.0 Concentration: 13 µg/m³
[22:06:54][D][sensor:093]: 'PM 2.5': Sending state 13.00000 µg/m³ with 0 decimals of accuracy
[22:06:54][D][pmsx003:283]: Got PM0.3 Particles: 584 Count/0.1L, PM0.5 Particles: 487 Count/0.1L, PM1.0 Particles: 109 Count/0.1L, PM2.5 Particles 8 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[22:06:55][D][pmsx003:242]: Got PM1.0 Standard Concentration: 5 µg/m³, PM2.5 Standard Concentration 13 µg/m³, PM10.0 Standard Concentration: 13 µg/m³, PM1.0 Concentration: 5 µg/m³, PM2.5 Concentration 13 µg/m³, PM10.0 Concentration: 13 µg/m³
[22:06:55][D][sensor:093]: 'PM 2.5': Sending state 13.00000 µg/m³ with 0 decimals of accuracy
[22:06:55][D][pmsx003:283]: Got PM0.3 Particles: 599 Count/0.1L, PM0.5 Particles: 504 Count/0.1L, PM1.0 Particles: 115 Count/0.1L, PM2.5 Particles 10 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L

cmuellner avatar Jun 04 '25 20:06 cmuellner

Looks like my debug help commit on top caused a crash. I have fixed that. Note, that this commit should not be merged anyway.

I've updated my config to refresh the PR code like this:

external_components:                                                                                                                                                     
  - source: github://pr#8989                                                                                                                                             
    components: [ pmsx003 ]                                                                                                                                              
    refresh: 0s

With the debug help commit, I get a good insight into what the system does:

[22:16:27][I][app:062]: setup() finished successfully!
[22:16:27][I][app:100]: ESPHome version 2025.2.0 compiled on Jun  4 2025, 22:14:19
[22:16:27][C][logger:177]: Logger:
[22:16:27][C][logger:178]:   Max Level: DEBUG
[22:16:27][C][logger:179]:   Initial Level: DEBUG
[22:16:27][C][logger:181]:   Log Baud Rate: 115200
[22:16:27][C][logger:182]:   Hardware UART: USB_SERIAL_JTAG
[22:16:27][C][uart.idf:159]: UART Bus 0:
[22:16:27][C][uart.idf:160]:   TX Pin: GPIO5
[22:16:27][C][uart.idf:161]:   RX Pin: GPIO4
[22:16:27][C][uart.idf:163]:   RX Buffer Size: 256
[22:16:27][C][uart.idf:165]:   Baud Rate: 9600 baud
[22:16:27][C][uart.idf:166]:   Data Bits: 8
[22:16:27][C][uart.idf:167]:   Parity: NONE
[22:16:27][C][uart.idf:168]:   Stop bits: 1
[22:16:27][C][pmsx003:025]: PMSX003:
[22:16:27][C][pmsx003:031]:   PM2.5 'PM 2.5'
[22:16:27][C][pmsx003:031]:     Device Class: 'pm25'
[22:16:27][C][pmsx003:031]:     State Class: 'measurement'
[22:16:27][C][pmsx003:031]:     Unit of Measurement: 'µg/m³'
[22:16:27][C][pmsx003:031]:     Accuracy Decimals: 0
[22:16:27][C][pmsx003:031]:     Icon: 'mdi:chemical-weapon'
[22:16:57][D][pmsx003:075]: measure_active_initial_: Entering
[22:16:58][D][pmsx003:088]: measure_active_: Entering
[22:16:58][D][pmsx003:148]: process_data_: Entering
[22:16:58][D][pmsx003:088]: measure_active_: Entering
[22:16:58][D][pmsx003:148]: process_data_: Entering
[22:16:58][D][pmsx003:258]: Got PM1.0 Standard Concentration: 6 µg/m³, PM2.5 Standard Concentration 8 µg/m³, PM10.0 Standard Concentration: 10 µg/m³, PM1.0 Concentration: 6 µg/m³, PM2.5 Concentration 8 µg/m³, PM10.0 Concentration: 10 µg/m³
[22:16:58][D][sensor:093]: 'PM 2.5': Sending state 8.00000 µg/m³ with 0 decimals of accuracy
[22:16:58][D][pmsx003:299]: Got PM0.3 Particles: 494 Count/0.1L, PM0.5 Particles: 418 Count/0.1L, PM1.0 Particles: 66 Count/0.1L, PM2.5 Particles 8 Count/0.1L, PM5.0 Particles: 2 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[22:16:58][D][pmsx003:141]: clear_data_buffer_: Entering
[22:16:59][D][pmsx003:088]: measure_active_: Entering
[22:16:59][D][pmsx003:148]: process_data_: Entering
[22:16:59][D][pmsx003:088]: measure_active_: Entering
[22:16:59][D][pmsx003:148]: process_data_: Entering
[22:16:59][D][pmsx003:258]: Got PM1.0 Standard Concentration: 6 µg/m³, PM2.5 Standard Concentration 8 µg/m³, PM10.0 Standard Concentration: 10 µg/m³, PM1.0 Concentration: 6 µg/m³, PM2.5 Concentration 8 µg/m³, PM10.0 Concentration: 10 µg/m³
[22:16:59][D][sensor:093]: 'PM 2.5': Sending state 8.00000 µg/m³ with 0 decimals of accuracy
[22:16:59][D][pmsx003:299]: Got PM0.3 Particles: 519 Count/0.1L, PM0.5 Particles: 447 Count/0.1L, PM1.0 Particles: 72 Count/0.1L, PM2.5 Particles 10 Count/0.1L, PM5.0 Particles: 2 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[...]

cmuellner avatar Jun 04 '25 20:06 cmuellner

[15:32:36][I][app:115]: ESPHome version 2025.5.2 compiled on Jun  4 2025, 15:32:06
[15:32:36][I][app:117]: Project mallocarray.airgradient version 5.2.1
[15:32:36][C][wifi:600]: WiFi:
[15:32:36][C][wifi:428]:   Local MAC: [redacted]
[15:32:36][C][wifi:433]:   SSID: [redacted]
[15:32:36][C][wifi:436]:   IP Address: 192.168.2.121
[15:32:36][C][wifi:439]:   BSSID: [redacted]
[15:32:36][C][wifi:441]:   Hostname: 'ag-one'
[15:32:36][C][wifi:443]:   Signal strength: -43 dB ▂▄▆█
[15:32:36][C][wifi:447]:   Channel: 1
[15:32:36][C][wifi:448]:   Subnet: 255.255.255.0
[15:32:36][C][wifi:449]:   Gateway: 192.168.2.1
[15:32:36][C][wifi:450]:   DNS1: 192.168.2.1
[15:32:36][C][wifi:451]:   DNS2: 4.2.2.2
[15:32:36][C][logger:224]: Logger:
[15:32:36][C][logger:225]:   Max Level: DEBUG
[15:32:36][C][logger:226]:   Initial Level: DEBUG
[15:32:36][C][logger:228]:   Log Baud Rate: 115200
[15:32:36][C][logger:229]:   Hardware UART: USB_SERIAL_JTAG
[15:32:36][C][logger:233]:   Task Log Buffer Size: 768
[15:32:36][C][i2c.idf:083]: I2C Bus:
[15:32:36][C][i2c.idf:084]:   SDA Pin: GPIO7
[15:32:36][C][i2c.idf:085]:   SCL Pin: GPIO6
[15:32:36][C][i2c.idf:086]:   Frequency: 400000 Hz
[15:32:36][C][i2c.idf:092]:   Recovery: bus successfully recovered
[15:32:36][I][i2c.idf:102]: Results from i2c bus scan:
[15:32:36][I][i2c.idf:108]: Found i2c device at address 0x3C
[15:32:36][I][i2c.idf:108]: Found i2c device at address 0x44
[15:32:36][I][i2c.idf:108]: Found i2c device at address 0x59
[15:32:36][C][uart.idf:159]: UART Bus 0:
[15:32:36][C][uart.idf:160]:   TX Pin: GPIO1
[15:32:36][C][uart.idf:161]:   RX Pin: GPIO0
[15:32:36][C][uart.idf:163]:   RX Buffer Size: 256
[15:32:36][C][uart.idf:165]:   Baud Rate: 9600 baud
[15:32:36][C][uart.idf:166]:   Data Bits: 8
[15:32:36][C][uart.idf:167]:   Parity: NONE
[15:32:36][C][uart.idf:168]:   Stop bits: 1
[15:32:36][C][uart.idf:159]: UART Bus 1:
[15:32:36][C][uart.idf:160]:   TX Pin: GPIO21
[15:32:36][C][uart.idf:161]:   RX Pin: GPIO20
[15:32:36][C][uart.idf:163]:   RX Buffer Size: 256
[15:32:36][C][uart.idf:165]:   Baud Rate: 9600 baud
[15:32:36][C][uart.idf:166]:   Data Bits: 8
[15:32:36][C][uart.idf:167]:   Parity: NONE
[15:32:36][C][uart.idf:168]:   Stop bits: 1
[15:32:36][C][gpio.output:010]: GPIO Binary Output:
[15:32:36][C][gpio.output:011]:   Pin: GPIO2
[15:32:36][C][pmsx003:025]: PMSX003:
[15:32:36][C][pmsx003:030]:   PM1.0 'PM 1.0'
[15:32:36][C][pmsx003:030]:     Device Class: 'pm1'
[15:32:36][C][pmsx003:030]:     State Class: 'measurement'
[15:32:36][C][pmsx003:030]:     Unit of Measurement: 'µg/m³'
[15:32:36][C][pmsx003:030]:     Accuracy Decimals: 0
[15:32:36][C][pmsx003:030]:     Icon: 'mdi:chemical-weapon'
[15:32:36][C][pmsx003:031]:   PM2.5 'PM 2.5 Raw'
[15:32:36][C][pmsx003:031]:     Device Class: 'pm25'
[15:32:36][C][pmsx003:031]:     State Class: 'measurement'
[15:32:36][C][pmsx003:031]:     Unit of Measurement: 'µg/m³'
[15:32:36][C][pmsx003:031]:     Accuracy Decimals: 0
[15:32:36][C][pmsx003:031]:     Icon: 'mdi:chemical-weapon'
[15:32:36][C][pmsx003:032]:   PM10.0 'PM 10.0'
[15:32:36][C][pmsx003:032]:     Device Class: 'pm10'
[15:32:36][C][pmsx003:032]:     State Class: 'measurement'
[15:32:36][C][pmsx003:032]:     Unit of Measurement: 'µg/m³'
[15:32:36][C][pmsx003:032]:     Accuracy Decimals: 0
[15:32:36][C][pmsx003:032]:     Icon: 'mdi:chemical-weapon'
[15:32:36][C][pmsx003:034]:   PM0.3um 'PM 0.3'
[15:32:36][C][pmsx003:034]:     State Class: 'measurement'
[15:32:36][C][pmsx003:034]:     Unit of Measurement: '/dL'
[15:32:37][C][pmsx003:034]:     Accuracy Decimals: 0
[15:32:37][C][pmsx003:034]:     Icon: 'mdi:chemical-weapon'
[15:32:37][C][sht4x:059]: SHT4x:
[15:32:37][C][sht4x:060]:   Address: 0x44
[15:32:37][C][copy.sensor:015]: Copy Sensor 'Temperature'
[15:32:37][C][copy.sensor:015]:   Device Class: 'temperature'
[15:32:37][C][copy.sensor:015]:   State Class: 'measurement'
[15:32:37][C][copy.sensor:015]:   Unit of Measurement: '°C'
[15:32:37][C][copy.sensor:015]:   Accuracy Decimals: 2
[15:32:37][C][copy.sensor:015]:   Icon: 'mdi:thermometer'
[15:32:37][C][copy.sensor:015]: Copy Sensor 'Humidity'
[15:32:37][C][copy.sensor:015]:   Device Class: 'humidity'
[15:32:37][C][copy.sensor:015]:   State Class: 'measurement'
[15:32:37][C][copy.sensor:015]:   Unit of Measurement: '%'
[15:32:37][C][copy.sensor:015]:   Accuracy Decimals: 2
[15:32:37][C][copy.sensor:015]:   Icon: 'mdi:water-percent'
[15:32:37][C][captive_portal:089]: Captive Portal:
[15:32:37][C][web_server:285]: Web Server:
[15:32:37][C][web_server:286]:   Address: 192.168.2.121:80
[15:32:37][C][mdns:120]: mDNS:
[15:32:37][C][mdns:121]:   Hostname: ag-one-bcaf6c
[15:32:37][C][esphome.ota:073]: Over-The-Air updates:
[15:32:37][C][esphome.ota:074]:   Address: 192.168.2.121:3232
[15:32:37][C][esphome.ota:075]:   Version: 2
[15:32:37][C][safe_mode:018]: Safe Mode:
[15:32:37][C][safe_mode:019]:   Boot considered successful after 60 seconds
[15:32:37][C][safe_mode:021]:   Invoke after 10 boot attempts
[15:32:37][C][safe_mode:022]:   Remain in safe mode for 300 seconds
[15:32:37][C][api:170]: API Server:
[15:32:37][C][api:171]:   Address: 192.168.2.121:6053
[15:32:37][C][api:178]:   Using noise encryption: NO
[15:32:37][D][api:122]: Accepted 192.168.2.50
[15:32:37][D][api.connection:1533]: Home Assistant 2025.5.2 (192.168.2.50): Connected successfully
[15:32:46][D][sensor:093]: 'Temperature Raw': Sending state 21.62737 °C with 2 decimals of accuracy
[15:32:46][D][sensor:093]: 'Temperature': Sending state 21.62737 °C with 2 decimals of accuracy
[15:32:47][D][sensor:093]: 'Humidity Raw': Sending state 57.26772 % with 2 decimals of accuracy
[15:32:47][D][sensor:093]: 'Humidity': Sending state 57.26772 % with 2 decimals of accuracy
[15:33:02][D][pmsx003:075]: measure_active_initial_: Entering
[15:33:03][D][pmsx003:088]: measure_active_: Entering
[15:33:03][D][pmsx003:148]: process_data_: Entering
[15:33:03][D][pmsx003:088]: measure_active_: Entering
[15:33:03][D][pmsx003:148]: process_data_: Entering
[15:33:04][D][pmsx003:088]: measure_active_: Entering
[15:33:04][D][pmsx003:148]: process_data_: Entering

I have to power cycle it after installing OTA to get it to report anything from the PMS sensor.

With no update_interval, I am getting regular data

[15:34:57][D][api:122]: Accepted 192.168.2.50
[15:34:57][D][api.connection:1533]: Home Assistant 2025.5.2 (192.168.2.50): Connected successfully
[15:35:05][D][pmsx003:075]: measure_active_initial_: Entering
[15:35:06][D][pmsx003:088]: measure_active_: Entering
[15:35:06][D][pmsx003:148]: process_data_: Entering
[15:35:06][D][pmsx003:258]: Got PM1.0 Standard Concentration: 1 µg/m³, PM2.5 Standard Concentration 1 µg/m³, PM10.0 Standard Concentration: 1 µg/m³, PM1.0 Concentration: 1 µg/m³, PM2.5 Concentration 1 µg/m³, PM10.0 Concentration: 1 µg/m³
[15:35:06][D][sensor:093]: 'PM 1.0': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:35:06][D][sensor:093]: 'PM 2.5 Raw': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:35:06][D][sensor:093]: 'PM 10.0': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:35:06][D][sensor:093]: 'PM 0.3': Sending state 264.00000 /dL with 0 decimals of accuracy
[15:35:06][D][pmsx003:299]: Got PM0.3 Particles: 264 Count/0.1L, PM0.5 Particles: 79 Count/0.1L, PM1.0 Particles: 10 Count/0.1L, PM2.5 Particles 1 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[15:35:06][D][pmsx003:141]: clear_data_buffer_: Entering
[15:35:06][D][pmsx003:088]: measure_active_: Entering
[15:35:06][D][pmsx003:148]: process_data_: Entering
[15:35:07][D][pmsx003:088]: measure_active_: Entering
[15:35:07][D][pmsx003:148]: process_data_: Entering
[15:35:07][D][pmsx003:258]: Got PM1.0 Standard Concentration: 1 µg/m³, PM2.5 Standard Concentration 1 µg/m³, PM10.0 Standard Concentration: 1 µg/m³, PM1.0 Concentration: 1 µg/m³, PM2.5 Concentration 1 µg/m³, PM10.0 Concentration: 1 µg/m³
[15:35:07][D][sensor:093]: 'PM 1.0': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:35:07][D][sensor:093]: 'PM 2.5 Raw': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:35:07][D][sensor:093]: 'PM 10.0': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:35:07][D][sensor:093]: 'PM 0.3': Sending state 264.00000 /dL with 0 decimals of accuracy
[15:35:07][D][pmsx003:299]: Got PM0.3 Particles: 264 Count/0.1L, PM0.5 Particles: 79 Count/0.1L, PM1.0 Particles: 10 Count/0.1L, PM2.5 Particles 1 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[15:35:07][D][pmsx003:141]: clear_data_buffer_: Entering
[15:35:07][D][pmsx003:088]: measure_active_: Entering
[15:35:07][D][pmsx003:148]: process_data_: Entering
[15:35:07][D][pmsx003:258]: Got PM1.0 Standard Concentration: 1 µg/m³, PM2.5 Standard Concentration 1 µg/m³, PM10.0 Standard Concentration: 1 µg/m³, PM1.0 Concentration: 1 µg/m³, PM2.5 Concentration 1 µg/m³, PM10.0 Concentration: 1 µg/m³
[15:35:07][D][sensor:093]: 'PM 1.0': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:35:07][D][sensor:093]: 'PM 2.5 Raw': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:35:07][D][sensor:093]: 'PM 10.0': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:35:07][D][sensor:093]: 'PM 0.3': Sending state 264.00000 /dL with 0 decimals of accuracy
[15:35:07][D][pmsx003:299]: Got PM0.3 Particles: 264 Count/0.1L, PM0.5 Particles: 79 Count/0.1L, PM1.0 Particles: 10 Count/0.1L, PM2.5 Particles 1 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[15:35:07][D][pmsx003:141]: clear_data_buffer_: Entering

With update_interval: 30s I only get one reading on the first boot and then nothing else. As far as I know I have the correct pins for UART to the device. Just to make sure I tried flipping the pins in the UART config but no luck.

[15:38:52][D][pmsx003:109]: request_data_: Entering
[15:38:53][D][pmsx003:124]: measure_passive_: Entering
[15:38:53][D][pmsx003:148]: process_data_: Entering
[15:38:53][D][pmsx003:258]: Got PM1.0 Standard Concentration: 0 µg/m³, PM2.5 Standard Concentration 1 µg/m³, PM10.0 Standard Concentration: 1 µg/m³, PM1.0 Concentration: 0 µg/m³, PM2.5 Concentration 1 µg/m³, PM10.0 Concentration: 1 µg/m³
[15:38:53][D][sensor:093]: 'PM 1.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[15:38:53][D][sensor:093]: 'PM 2.5 Raw': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:38:53][D][sensor:093]: 'PM 10.0': Sending state 1.00000 µg/m³ with 0 decimals of accuracy
[15:38:53][D][sensor:093]: 'PM 0.3': Sending state 210.00000 /dL with 0 decimals of accuracy
[15:38:53][D][pmsx003:299]: Got PM0.3 Particles: 210 Count/0.1L, PM0.5 Particles: 56 Count/0.1L, PM1.0 Particles: 6 Count/0.1L, PM2.5 Particles 0 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[15:38:53][D][pmsx003:141]: clear_data_buffer_: Entering
[15:38:53][D][pmsx003:099]: wakeup_device_: Entering
[15:39:22][I][safe_mode:041]: Boot seems successful; resetting boot loop counter
[15:39:22][D][esp32.preferences:114]: Saving 1 preferences to flash...
[15:39:22][D][esp32.preferences:142]: Saving 1 preferences to flash: 0 cached, 1 written, 0 failed
[15:39:23][D][pmsx003:109]: request_data_: Entering
[15:39:23][D][pmsx003:124]: measure_passive_: Entering
[15:39:23][D][pmsx003:148]: process_data_: Entering
[15:39:23][D][pmsx003:099]: wakeup_device_: Entering
[15:39:28][D][sensor:093]: 'Temperature Raw': Sending state 21.67811 °C with 2 decimals of accuracy
[15:39:28][D][sensor:093]: 'Temperature': Sending state 21.67811 °C with 2 decimals of accuracy
[15:39:28][D][sensor:093]: 'Humidity Raw': Sending state 57.44701 % with 2 decimals of accuracy
[15:39:28][D][sensor:093]: 'Humidity': Sending state 57.44701 % with 2 decimals of accuracy
[15:39:53][D][pmsx003:109]: request_data_: Entering
[15:39:54][D][pmsx003:124]: measure_passive_: Entering
[15:39:54][D][pmsx003:148]: process_data_: Entering
[15:39:54][D][pmsx003:099]: wakeup_device_: Entering
[15:40:24][D][pmsx003:109]: request_data_: Entering
[15:40:24][D][pmsx003:124]: measure_passive_: Entering
[15:40:24][D][pmsx003:148]: process_data_: Entering
[15:40:24][D][pmsx003:099]: wakeup_device_: Entering
[15:40:28][D][sensor:093]: 'Temperature Raw': Sending state 21.65141 °C with 2 decimals of accuracy
[15:40:28][D][sensor:093]: 'Temperature': Sending state 21.65141 °C with 2 decimals of accuracy
[15:40:28][D][sensor:093]: 'Humidity Raw': Sending state 57.51377 % with 2 decimals of accuracy
[15:40:28][D][sensor:093]: 'Humidity': Sending state 57.51377 % with 2 decimals of accuracy

MallocArray avatar Jun 04 '25 20:06 MallocArray

I have to power cycle it after installing OTA to get it to report anything from the PMS sensor.

So, it works after a power cycle, but does not work after an OTA update. I'm using a USB connection here and have not seen such an effect. I'll try to reproduce the issue with OTA updates.

Thanks for testing, and good to see progress here.

cmuellner avatar Jun 04 '25 21:06 cmuellner

i have removed log lines from other sensors:

[23:00:57][I][app:115]: ESPHome version 2025.5.2 compiled on Jun  4 2025, 22:56:36
...
[23:00:57][C][pmsx003:025]: PMSX003:
[23:00:57][C][pmsx003:030]:   PM1.0 'PMS5003T PM <1.0µm Concentration'
[23:00:57][C][pmsx003:030]:     Device Class: 'pm1'
[23:00:57][C][pmsx003:030]:     State Class: 'measurement'
[23:00:57][C][pmsx003:030]:     Unit of Measurement: 'µg/m³'
[23:00:57][C][pmsx003:030]:     Accuracy Decimals: 0
[23:00:57][C][pmsx003:030]:     Icon: 'mdi:chemical-weapon'
[23:00:57][C][pmsx003:031]:   PM2.5 'PMS5003T PM <2.5µm Concentration'
[23:00:57][C][pmsx003:031]:     Device Class: 'pm25'
[23:00:57][C][pmsx003:031]:     State Class: 'measurement'
[23:00:57][C][pmsx003:031]:     Unit of Measurement: 'µg/m³'
[23:00:57][C][pmsx003:031]:     Accuracy Decimals: 0
[23:00:57][C][pmsx003:031]:     Icon: 'mdi:chemical-weapon'
[23:00:57][C][pmsx003:032]:   PM10.0 'PMS5003T PM <10.0µm Concentration'
[23:00:57][C][pmsx003:032]:     Device Class: 'pm10'
[23:00:57][C][pmsx003:032]:     State Class: 'measurement'
[23:00:57][C][pmsx003:032]:     Unit of Measurement: 'µg/m³'
[23:00:57][C][pmsx003:032]:     Accuracy Decimals: 0
[23:00:57][C][pmsx003:032]:     Icon: 'mdi:chemical-weapon'
[23:00:57][C][pmsx003:043]:   Temperature 'PMS5003T Temperature'
[23:00:57][C][pmsx003:043]:     Device Class: 'temperature'
[23:00:57][C][pmsx003:043]:     State Class: 'measurement'
[23:00:57][C][pmsx003:043]:     Unit of Measurement: '°C'
[23:00:57][C][pmsx003:043]:     Accuracy Decimals: 1
[23:00:57][C][pmsx003:044]:   Humidity 'PMS5003T Humidity'
[23:00:57][C][pmsx003:044]:     Device Class: 'humidity'
[23:00:57][C][pmsx003:044]:     State Class: 'measurement'
[23:00:57][C][pmsx003:044]:     Unit of Measurement: '%'
[23:00:57][C][pmsx003:044]:     Accuracy Decimals: 1
...
[23:00:57][D][api:122]: Accepted 10.0.0.210
[23:01:23][D][pmsx003:109]: request_data_: Entering
[23:01:24][D][pmsx003:124]: measure_passive_: Entering
[23:01:24][D][pmsx003:148]: process_data_: Entering
...
[23:05:54][D][pmsx003:099]: wakeup_device_: Entering
...
[23:06:24][D][pmsx003:109]: request_data_: Entering
[23:06:24][D][pmsx003:124]: measure_passive_: Entering
[23:06:24][D][pmsx003:148]: process_data_: Entering

there is no log line of reporting values

part of the config:

  - platform: pmsx003
    type: PMS5003T
    pm_1_0:
      name: "PMS5003T PM <1.0µm Concentration"
      filters:
      - throttle: 5s
    pm_2_5:
      name: "PMS5003T PM <2.5µm Concentration"
      filters:
      - throttle: 5s
    pm_10_0:
      name: "PMS5003T PM <10.0µm Concentration"
      filters:
      - throttle: 5s
    temperature:
      name: "PMS5003T Temperature"
      filters:
      - throttle: 5s
    humidity:
      name: "PMS5003T Humidity"
      filters:
      - throttle: 5s
    update_interval: 300s

ximex avatar Jun 04 '25 21:06 ximex

there is no log line of reporting values

Did you update via OTA as well? Have you tested after a power cycle?

cmuellner avatar Jun 04 '25 21:06 cmuellner

yes was an update via Wifi. didn't do any manual reboot

ximex avatar Jun 04 '25 21:06 ximex

After five successful OTA updates (without any update interval), I added an update interval of 60 seconds and updated again. Then I could reproduce the issue: no data is coming and the fan is not spinning. An attempt to switch back to active mode (commenting out the update interval again) did not succeed.

I'll do some more testing to find out:

  • What causes this state
  • If this is an issue of the code change in the PR
  • If we can avoid that state or recover from here

Thanks to both of you for the valuable input!

cmuellner avatar Jun 04 '25 21:06 cmuellner

I think I found the issue:

diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp
index 538e6a675..b6532417b 100644
--- a/esphome/components/pmsx003/pmsx003.cpp
+++ b/esphome/components/pmsx003/pmsx003.cpp
@@ -113,7 +113,7 @@ void PMSX003Component::request_data_() {
     this->read_byte(&this->data_[0]);
 
   // Request new data.
-  // this->send_command_(PMS_CMD_MANUAL_MEASUREMENT, 0);
+  this->send_command_(PMS_CMD_MANUAL_MEASUREMENT, 0);
 
   // Wait a bit until we receive data.
   uint32_t timeout_ms = PMS_RECEIVE_TIMEOUT_MS;

I did some testing to see if it is necessary to request data in passive mode (yes, it is - at least sometimes). And I forgot I still had a line commented out when creating this PR.

Updated PR:

  • Re-enabled accidentally commented out line
  • Dropped debug helper commit
  • Rebased

With this change, I am no longer able to enter a state in which the sensor does not provide data. Please do another round of testing!

cmuellner avatar Jun 04 '25 22:06 cmuellner

Looking good. After an OTA with no reboot, it is returning values every 30 seconds

[17:50:36][D][pmsx003:242]: Got PM1.0 Standard Concentration: 0 µg/m³, PM2.5 Standard Concentration 0 µg/m³, PM10.0 Standard Concentration: 0 µg/m³, PM1.0 Concentration: 0 µg/m³, PM2.5 Concentration 0 µg/m³, PM10.0 Concentration: 0 µg/m³
[17:50:36][D][sensor:093]: 'PM 1.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:50:36][D][sensor:093]: 'PM 2.5 Raw': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:50:36][D][sensor:093]: 'PM 10.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:50:36][D][sensor:093]: 'PM 0.3': Sending state 0.00000 /dL with 0 decimals of accuracy
[17:50:36][D][pmsx003:283]: Got PM0.3 Particles: 0 Count/0.1L, PM0.5 Particles: 0 Count/0.1L, PM1.0 Particles: 0 Count/0.1L, PM2.5 Particles 0 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[17:51:06][D][pmsx003:242]: Got PM1.0 Standard Concentration: 0 µg/m³, PM2.5 Standard Concentration 0 µg/m³, PM10.0 Standard Concentration: 0 µg/m³, PM1.0 Concentration: 0 µg/m³, PM2.5 Concentration 0 µg/m³, PM10.0 Concentration: 0 µg/m³
[17:51:06][D][sensor:093]: 'PM 1.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:51:06][D][sensor:093]: 'PM 2.5 Raw': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:51:06][D][sensor:093]: 'PM 10.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:51:06][D][sensor:093]: 'PM 0.3': Sending state 0.00000 /dL with 0 decimals of accuracy
[17:51:06][D][pmsx003:283]: Got PM0.3 Particles: 0 Count/0.1L, PM0.5 Particles: 0 Count/0.1L, PM1.0 Particles: 0 Count/0.1L, PM2.5 Particles 0 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[17:51:27][D][sensor:093]: 'Temperature Raw': Sending state 21.70214 °C with 2 decimals of accuracy
[17:51:27][D][sensor:093]: 'Temperature': Sending state 21.70214 °C with 2 decimals of accuracy
[17:51:27][D][sensor:093]: 'Humidity Raw': Sending state 57.07698 % with 2 decimals of accuracy
[17:51:27][D][sensor:093]: 'Humidity': Sending state 57.07698 % with 2 decimals of accuracy
[17:51:37][D][pmsx003:242]: Got PM1.0 Standard Concentration: 0 µg/m³, PM2.5 Standard Concentration 0 µg/m³, PM10.0 Standard Concentration: 0 µg/m³, PM1.0 Concentration: 0 µg/m³, PM2.5 Concentration 0 µg/m³, PM10.0 Concentration: 0 µg/m³
[17:51:37][D][sensor:093]: 'PM 1.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:51:37][D][sensor:093]: 'PM 2.5 Raw': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:51:37][D][sensor:093]: 'PM 10.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:51:37][D][sensor:093]: 'PM 0.3': Sending state 0.00000 /dL with 0 decimals of accuracy
[17:51:37][D][pmsx003:283]: Got PM0.3 Particles: 0 Count/0.1L, PM0.5 Particles: 0 Count/0.1L, PM1.0 Particles: 0 Count/0.1L, PM2.5 Particles 0 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L
[17:52:07][D][pmsx003:242]: Got PM1.0 Standard Concentration: 0 µg/m³, PM2.5 Standard Concentration 0 µg/m³, PM10.0 Standard Concentration: 0 µg/m³, PM1.0 Concentration: 0 µg/m³, PM2.5 Concentration 0 µg/m³, PM10.0 Concentration: 0 µg/m³
[17:52:07][D][sensor:093]: 'PM 1.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:52:07][D][sensor:093]: 'PM 2.5 Raw': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:52:07][D][sensor:093]: 'PM 10.0': Sending state 0.00000 µg/m³ with 0 decimals of accuracy
[17:52:07][D][sensor:093]: 'PM 0.3': Sending state 0.00000 /dL with 0 decimals of accuracy
[17:52:07][D][pmsx003:283]: Got PM0.3 Particles: 0 Count/0.1L, PM0.5 Particles: 0 Count/0.1L, PM1.0 Particles: 0 Count/0.1L, PM2.5 Particles 0 Count/0.1L, PM5.0 Particles: 0 Count/0.1L, PM10.0 Particles 0 Count/0.1L

MallocArray avatar Jun 04 '25 22:06 MallocArray

my one also works now (with 5min update interval). but it's outside so i havn't checked if the fan stops etc

ximex avatar Jun 04 '25 23:06 ximex

@cmuellner could you merge dev and resolve conflicts?

ximex avatar Jun 11 '25 23:06 ximex

@cmuellner could you merge dev and resolve conflicts?

Done. Great to see that get_16_bit_uint_ became inlinable and uses encode_uint16!

By the way, I have the code for this PR running here for a week without any issues (with an update interval of 300 seconds).

cmuellner avatar Jun 12 '25 07:06 cmuellner

Hi @ximex, When do you plan to review this? Or do you object to the changes (or part of them) in this PR?

cmuellner avatar Jun 18 '25 16:06 cmuellner

There is nothing wrong with a state machine in loop. That's what most components use. The uint32_t thing is also not a problem. Rollover works just fine. Generally, how you've done it is ok, but I'm not happy with how you're collecting the serial data. You're assuming that you're going to get it all available at once and that is not guaranteed.

ssieb avatar Jun 19 '25 21:06 ssieb

There is nothing wrong with a state machine in loop. That's what most components use. The uint32_t thing is also not a problem. Rollover works just fine. Generally, how you've done it is ok, but I'm not happy with how you're collecting the serial data. You're assuming that you're going to get it all available at once and that is not guaranteed.

In active mode, it works like before (collecting fragments in this->data_ until the frame is complete).

In passive mode, measure_passive_() is called 500 ms after sending the request for data. The datasheet does not guarantee any response times, but 500 ms is IMO reasonably huge time to assume completion. If you think this is insufficient, I can check if we received a complete frame in measure_passive_() after calling process_data_() and set up the callback for measure_passive_ again in this case.

cmuellner avatar Jun 20 '25 05:06 cmuellner