ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
pmsa003i.cpp
Go to the documentation of this file.
1#include "pmsa003i.h"
3#include "esphome/core/log.h"
4#include <cstring>
5
7
8ESPHOME_LOG_TAG(TAG, "pmsa003i");
9
10static const uint8_t COUNT_PAYLOAD_BYTES = 28;
11static const uint8_t COUNT_PAYLOAD_LENGTH_BYTES = 2;
12static const uint8_t COUNT_START_CHARACTER_BYTES = 2;
13static const uint8_t COUNT_DATA_BYTES = COUNT_START_CHARACTER_BYTES + COUNT_PAYLOAD_LENGTH_BYTES + COUNT_PAYLOAD_BYTES;
14static const uint8_t CHECKSUM_START_INDEX = COUNT_DATA_BYTES - 2;
15static const uint8_t COUNT_16_BIT_VALUES = (COUNT_PAYLOAD_LENGTH_BYTES + COUNT_PAYLOAD_BYTES) / 2;
16static const uint8_t START_CHARACTER_1 = 0x42;
17static const uint8_t START_CHARACTER_2 = 0x4D;
18
19// Timeout for determining when the device is ready for use, in milliseconds.
20// The PMSA003I typically takes 2.3 seconds to perform its first measurement after a cold power up and
21// I2C requests performed during that time will be NACKed. Use a slightly longer timeout to tolerate
22// timing variation. Note that although we consider the device ready as soon as it responds to I2C
23// requests, it may take 30 seconds or more for the data to stabilize according to the datasheet.
24static const uint32_t READY_TIMEOUT_MS = 3000;
25
26// Poll interval for determining when the device is ready for use, in milliseconds.
27static const uint32_t READY_POLL_INTERVAL_MS = 100;
28
30 // Stop polling until the device is actually ready to prevent spurious I2C warnings during premature updates.
31 this->stop_poller();
33}
34
36 // Check whether the device is responding and identifies itself as expected.
37 // We don't care about the actual sensor readings or the packet CRC in this case.
38 uint8_t buffer[2];
39 if (this->read(buffer, sizeof(buffer)) == i2c::ERROR_OK && buffer[0] == START_CHARACTER_1 &&
40 buffer[1] == START_CHARACTER_2) {
41 ESP_LOGD(TAG, "PMSA003I is ready");
42 this->start_poller();
43 return;
44 }
45
46 if (millis() - start_time < READY_TIMEOUT_MS) {
47 this->set_timeout(READY_POLL_INTERVAL_MS, [this, start_time]() { this->poll_until_ready_or_timeout_(start_time); });
48 } else {
49 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
50 }
51}
52
54 ESP_LOGCONFIG(TAG, "PMSA003I:");
55 LOG_I2C_DEVICE(this);
56}
57
59 PM25AQIData data;
60
61 bool successful_read = this->read_data_(&data);
62
63 // Update sensors
64 if (successful_read) {
66 ESP_LOGV(TAG, "Read success. Updating sensors.");
67
68 if (this->standard_units_) {
69 if (this->pm_1_0_sensor_ != nullptr)
71 if (this->pm_2_5_sensor_ != nullptr)
73 if (this->pm_10_0_sensor_ != nullptr)
75 } else {
76 if (this->pm_1_0_sensor_ != nullptr)
78 if (this->pm_2_5_sensor_ != nullptr)
80 if (this->pm_10_0_sensor_ != nullptr)
82 }
83
84 if (this->pmc_0_3_sensor_ != nullptr)
86 if (this->pmc_0_5_sensor_ != nullptr)
88 if (this->pmc_1_0_sensor_ != nullptr)
90 if (this->pmc_2_5_sensor_ != nullptr)
92 if (this->pmc_5_0_sensor_ != nullptr)
94 if (this->pmc_10_0_sensor_ != nullptr)
96 } else {
97 this->status_set_warning();
98 ESP_LOGV(TAG, "Read failure. Skipping update.");
99 }
100}
101
103 uint8_t buffer[COUNT_DATA_BYTES];
104
105 const i2c::ErrorCode error = this->read(buffer, COUNT_DATA_BYTES);
106 if (error != i2c::ERROR_OK) {
107 ESP_LOGW(TAG, "I2C error %d", error);
108 return false;
109 }
110
111 // https://github.com/adafruit/Adafruit_PM25AQI
112
113 // Check that start byte is correct!
114 if (buffer[0] != START_CHARACTER_1 || buffer[1] != START_CHARACTER_2) {
115 ESP_LOGW(TAG, "Start character mismatch: %02X %02X != %02X %02X", buffer[0], buffer[1], START_CHARACTER_1,
116 START_CHARACTER_2);
117 return false;
118 }
119
120 const uint16_t payload_length = encode_uint16(buffer[2], buffer[3]);
121 if (payload_length != COUNT_PAYLOAD_BYTES) {
122 ESP_LOGW(TAG, "Payload length mismatch: %u != %u", payload_length, COUNT_PAYLOAD_BYTES);
123 return false;
124 }
125
126 // Calculate checksum
127 const uint16_t checksum = sum16_bytes(buffer, CHECKSUM_START_INDEX);
128
129 const uint16_t check = encode_uint16(buffer[CHECKSUM_START_INDEX], buffer[CHECKSUM_START_INDEX + 1]);
130 if (checksum != check) {
131 ESP_LOGW(TAG, "Checksum mismatch: %u != %u", checksum, check);
132 return false;
133 }
134
135 // The data comes in endian'd, this solves it so it works on all platforms
136 uint16_t buffer_u16[COUNT_16_BIT_VALUES];
137 for (uint8_t i = 0; i < COUNT_16_BIT_VALUES; i++) {
138 const uint8_t buffer_index = COUNT_START_CHARACTER_BYTES + i * 2;
139 buffer_u16[i] = encode_uint16(buffer[buffer_index], buffer[buffer_index + 1]);
140 }
141
142 // put it into a nice struct :)
143 memcpy((void *) data, (void *) buffer_u16, COUNT_16_BIT_VALUES * 2);
144
145 return true;
146}
147
148} // namespace esphome::pmsa003i
uint8_t checksum
Definition bl0906.h:3
void mark_failed()
Mark this component as failed.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void status_clear_warning()
Definition component.h:296
ErrorCode read(uint8_t *data, size_t len) const
reads an array of bytes from the device using an I2CBus
Definition i2c.h:175
void poll_until_ready_or_timeout_(uint32_t start_time)
Definition pmsa003i.cpp:35
bool read_data_(PM25AQIData *data)
Definition pmsa003i.cpp:102
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
uint32_t buffer
Definition gif.cpp:50
ErrorCode
Error codes returned by I2CBus and I2CDevice methods.
Definition i2c_bus.h:13
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:15
ESPHOME_LOG_TAG(TAG, "pmsa003i")
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:998
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
constexpr uint16_t sum16_bytes(const uint8_t *data, size_t len, uint16_t sum=0)
Add up the bytes of data with size len, truncated to 16 bits.
Definition helpers.h:899
static void uint32_t
! Structure holding Plantower's standard packet
Definition pmsa003i.h:11
uint16_t particles_50um
5.0um Particle Count
Definition pmsa003i.h:23
uint16_t pm25_standard
Standard PM2.5.
Definition pmsa003i.h:14
uint16_t pm10_env
Environmental PM1.0.
Definition pmsa003i.h:16
uint16_t particles_25um
2.5um Particle Count
Definition pmsa003i.h:22
uint16_t pm100_env
Environmental PM10.0.
Definition pmsa003i.h:18
uint16_t particles_03um
0.3um Particle Count
Definition pmsa003i.h:19
uint16_t particles_05um
0.5um Particle Count
Definition pmsa003i.h:20
uint16_t particles_10um
1.0um Particle Count
Definition pmsa003i.h:21
uint16_t particles_100um
10.0um Particle Count
Definition pmsa003i.h:24
uint16_t pm10_standard
Standard PM1.0.
Definition pmsa003i.h:13
uint16_t pm25_env
Environmental PM2.5.
Definition pmsa003i.h:17
uint16_t pm100_standard
Standard PM10.0.
Definition pmsa003i.h:15