[pmsx003] Replace state machine with asynchronous callbacks
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:
- [n/a ] Documentation added/updated in esphome-docs.
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)
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.
Updated PR:
- Adding trailing
_after new method names to address linter errors
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
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
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
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.
Updated PR:
- Fixing type ("measurment" -> "measurement")
- Rebased on origin/dev
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
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.
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'm not sure either, but it is certainly related to changes in this PR. I removed the
update_intervalso it is using the default of every second and still get nothing.Removing the PR external_component restores functionality with both no
update_intervaland 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
@ximex, it would be great if you could quickly test this PR.
i did a very quick test. my esp32c6 is crashing and rebooting. more debugging maybe on friday
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
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
[...]
[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
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.
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
there is no log line of reporting values
Did you update via OTA as well? Have you tested after a power cycle?
yes was an update via Wifi. didn't do any manual reboot
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!
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!
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
my one also works now (with 5min update interval). but it's outside so i havn't checked if the fan stops etc
@cmuellner could you merge dev and resolve conflicts?
@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).
Hi @ximex, When do you plan to review this? Or do you object to the changes (or part of them) in this PR?
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.
There is nothing wrong with a state machine in
loop. That's what most components use. Theuint32_tthing 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.