ESPHome 2025.5.0
Loading...
Searching...
No Matches
pmsx003.cpp
Go to the documentation of this file.
1#include "pmsx003.h"
2#include "esphome/core/log.h"
4
5namespace esphome {
6namespace pmsx003 {
7
8static const char *const TAG = "pmsx003";
9
10static const uint8_t START_CHARACTER_1 = 0x42;
11static const uint8_t START_CHARACTER_2 = 0x4D;
12
13static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on in ms
14
15static const uint16_t PMS_CMD_MEASUREMENT_MODE_PASSIVE =
16 0x0000; // use `PMS_CMD_MANUAL_MEASUREMENT` to trigger a measurement
17static const uint16_t PMS_CMD_MEASUREMENT_MODE_ACTIVE = 0x0001; // automatically perform measurements
18static const uint16_t PMS_CMD_SLEEP_MODE_SLEEP = 0x0000; // go to sleep mode
19static const uint16_t PMS_CMD_SLEEP_MODE_WAKEUP = 0x0001; // wake up from sleep mode
20
22 ESP_LOGCONFIG(TAG, "PMSX003:");
23 LOG_SENSOR(" ", "PM1.0STD", this->pm_1_0_std_sensor_);
24 LOG_SENSOR(" ", "PM2.5STD", this->pm_2_5_std_sensor_);
25 LOG_SENSOR(" ", "PM10.0STD", this->pm_10_0_std_sensor_);
26
27 LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_);
28 LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
29 LOG_SENSOR(" ", "PM10.0", this->pm_10_0_sensor_);
30
31 LOG_SENSOR(" ", "PM0.3um", this->pm_particles_03um_sensor_);
32 LOG_SENSOR(" ", "PM0.5um", this->pm_particles_05um_sensor_);
33 LOG_SENSOR(" ", "PM1.0um", this->pm_particles_10um_sensor_);
34 LOG_SENSOR(" ", "PM2.5um", this->pm_particles_25um_sensor_);
35 LOG_SENSOR(" ", "PM5.0um", this->pm_particles_50um_sensor_);
36 LOG_SENSOR(" ", "PM10.0um", this->pm_particles_100um_sensor_);
37
38 LOG_SENSOR(" ", "Formaldehyde", this->formaldehyde_sensor_);
39
40 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
41 LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
42 this->check_uart_settings(9600);
43}
44
46 const uint32_t now = App.get_loop_component_start_time();
47
48 // If we update less often than it takes the device to stabilise, spin the fan down
49 // rather than running it constantly. It does take some time to stabilise, so we
50 // need to keep track of what state we're in.
51 if (this->update_interval_ > PMS_STABILISING_MS) {
52 if (this->initialised_ == 0) {
53 this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_PASSIVE);
54 this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP);
55 this->initialised_ = 1;
56 }
57 switch (this->state_) {
59 // Power on the sensor now so it'll be ready when we hit the update time
60 if (now - this->last_update_ < (this->update_interval_ - PMS_STABILISING_MS))
61 return;
62
64 this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP);
65 this->fan_on_time_ = now;
66 return;
68 // wait for the sensor to be stable
69 if (now - this->fan_on_time_ < PMS_STABILISING_MS)
70 return;
71 // consume any command responses that are in the serial buffer
72 while (this->available())
73 this->read_byte(&this->data_[0]);
74 // Trigger a new read
77 break;
79 // Just go ahead and read stuff
80 break;
81 }
82 } else if (now - this->last_update_ < this->update_interval_) {
83 // Otherwise just leave the sensor powered up and come back when we hit the update
84 // time
85 return;
86 }
87
88 if (now - this->last_transmission_ >= 500) {
89 // last transmission too long ago. Reset RX index.
90 this->data_index_ = 0;
91 }
92
93 if (this->available() == 0)
94 return;
95
96 this->last_transmission_ = now;
97 while (this->available() != 0) {
98 this->read_byte(&this->data_[this->data_index_]);
99 auto check = this->check_byte_();
100 if (!check.has_value()) {
101 // finished
102 this->parse_data_();
103 this->data_index_ = 0;
104 this->last_update_ = now;
105 } else if (!*check) {
106 // wrong data
107 this->data_index_ = 0;
108 } else {
109 // next byte
110 this->data_index_++;
111 }
112 }
113}
114
116 const uint8_t index = this->data_index_;
117 const uint8_t byte = this->data_[index];
118
119 if (index == 0 || index == 1) {
120 const uint8_t start_char = index == 0 ? START_CHARACTER_1 : START_CHARACTER_2;
121 if (byte == start_char) {
122 return true;
123 }
124
125 ESP_LOGW(TAG, "Start character %u mismatch: 0x%02X != 0x%02X", index + 1, byte, START_CHARACTER_1);
126 return false;
127 }
128
129 if (index == 2) {
130 return true;
131 }
132
133 const uint16_t payload_length = this->get_16_bit_uint_(2);
134 if (index == 3) {
135 if (this->check_payload_length_(payload_length)) {
136 return true;
137 } else {
138 ESP_LOGW(TAG, "Payload length %u doesn't match. Are you using the correct PMSX003 type?", payload_length);
139 return false;
140 }
141 }
142
143 // start (16bit) + length (16bit) + DATA (payload_length - 16bit) + checksum (16bit)
144 const uint16_t total_size = 4 + payload_length;
145
146 if (index < total_size - 1) {
147 return true;
148 }
149
150 // checksum is without checksum bytes
151 uint16_t checksum = 0;
152 for (uint16_t i = 0; i < total_size - 2; i++) {
153 checksum += this->data_[i];
154 }
155
156 const uint16_t check = this->get_16_bit_uint_(total_size - 2);
157 if (checksum != check) {
158 ESP_LOGW(TAG, "PMSX003 checksum mismatch! 0x%02X != 0x%02X", checksum, check);
159 return false;
160 }
161
162 return {};
163}
164
165bool PMSX003Component::check_payload_length_(uint16_t payload_length) {
166 switch (this->type_) {
168 // The expected payload length is typically 28 bytes.
169 // However, a 20-byte payload check was already present in the code.
170 // No official documentation was found confirming this.
171 // Retaining this check to avoid breaking existing behavior.
172 return payload_length == 28 || payload_length == 20; // 2*13+2
175 return payload_length == 28; // 2*13+2 (Data 13 not set/reserved)
177 return payload_length == 36; // 2*17+2 (Data 16 not set/reserved)
178 }
179 return false;
180}
181
183 uint8_t send_data[7] = {
184 START_CHARACTER_1, // Start Byte 1
185 START_CHARACTER_2, // Start Byte 2
186 cmd, // Command
187 uint8_t((data >> 8) & 0xFF), // Data 1
188 uint8_t((data >> 0) & 0xFF), // Data 2
189 0, // Verify Byte 1
190 0, // Verify Byte 2
191 };
192
193 // Calculate checksum
194 uint16_t checksum = 0;
195 for (uint8_t i = 0; i < 5; i++) {
196 checksum += send_data[i];
197 }
198 send_data[5] = (checksum >> 8) & 0xFF; // Verify Byte 1
199 send_data[6] = (checksum >> 0) & 0xFF; // Verify Byte 2
200
201 for (auto send_byte : send_data) {
202 this->write_byte(send_byte);
203 }
204}
205
207 // Particle Matter
208 const uint16_t pm_1_0_std_concentration = this->get_16_bit_uint_(4);
209 const uint16_t pm_2_5_std_concentration = this->get_16_bit_uint_(6);
210 const uint16_t pm_10_0_std_concentration = this->get_16_bit_uint_(8);
211
212 const uint16_t pm_1_0_concentration = this->get_16_bit_uint_(10);
213 const uint16_t pm_2_5_concentration = this->get_16_bit_uint_(12);
214 const uint16_t pm_10_0_concentration = this->get_16_bit_uint_(14);
215
216 const uint16_t pm_particles_03um = this->get_16_bit_uint_(16);
217 const uint16_t pm_particles_05um = this->get_16_bit_uint_(18);
218 const uint16_t pm_particles_10um = this->get_16_bit_uint_(20);
219 const uint16_t pm_particles_25um = this->get_16_bit_uint_(22);
220
221 ESP_LOGD(TAG,
222 "Got PM1.0 Standard Concentration: %u µg/m³, PM2.5 Standard Concentration %u µg/m³, PM10.0 Standard "
223 "Concentration: %u µg/m³, PM1.0 Concentration: %u µg/m³, PM2.5 Concentration %u µg/m³, PM10.0 "
224 "Concentration: %u µg/m³",
225 pm_1_0_std_concentration, pm_2_5_std_concentration, pm_10_0_std_concentration, pm_1_0_concentration,
226 pm_2_5_concentration, pm_10_0_concentration);
227
228 if (this->pm_1_0_std_sensor_ != nullptr)
229 this->pm_1_0_std_sensor_->publish_state(pm_1_0_std_concentration);
230 if (this->pm_2_5_std_sensor_ != nullptr)
231 this->pm_2_5_std_sensor_->publish_state(pm_2_5_std_concentration);
232 if (this->pm_10_0_std_sensor_ != nullptr)
233 this->pm_10_0_std_sensor_->publish_state(pm_10_0_std_concentration);
234
235 if (this->pm_1_0_sensor_ != nullptr)
236 this->pm_1_0_sensor_->publish_state(pm_1_0_concentration);
237 if (this->pm_2_5_sensor_ != nullptr)
238 this->pm_2_5_sensor_->publish_state(pm_2_5_concentration);
239 if (this->pm_10_0_sensor_ != nullptr)
240 this->pm_10_0_sensor_->publish_state(pm_10_0_concentration);
241
242 if (this->pm_particles_03um_sensor_ != nullptr)
243 this->pm_particles_03um_sensor_->publish_state(pm_particles_03um);
244 if (this->pm_particles_05um_sensor_ != nullptr)
245 this->pm_particles_05um_sensor_->publish_state(pm_particles_05um);
246 if (this->pm_particles_10um_sensor_ != nullptr)
247 this->pm_particles_10um_sensor_->publish_state(pm_particles_10um);
248 if (this->pm_particles_25um_sensor_ != nullptr)
249 this->pm_particles_25um_sensor_->publish_state(pm_particles_25um);
250
251 if (this->type_ == PMSX003_TYPE_5003T) {
252 ESP_LOGD(TAG,
253 "Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, "
254 "PM2.5 Particles %u Count/0.1L",
255 pm_particles_03um, pm_particles_05um, pm_particles_10um, pm_particles_25um);
256 } else {
257 // Note the pm particles 50um & 100um are not returned,
258 // as PMS5003T uses those data values for temperature and humidity.
259 const uint16_t pm_particles_50um = this->get_16_bit_uint_(24);
260 const uint16_t pm_particles_100um = this->get_16_bit_uint_(26);
261
262 ESP_LOGD(TAG,
263 "Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, "
264 "PM2.5 Particles %u Count/0.1L, PM5.0 Particles: %u Count/0.1L, PM10.0 Particles %u Count/0.1L",
265 pm_particles_03um, pm_particles_05um, pm_particles_10um, pm_particles_25um, pm_particles_50um,
266 pm_particles_100um);
267
268 if (this->pm_particles_50um_sensor_ != nullptr)
269 this->pm_particles_50um_sensor_->publish_state(pm_particles_50um);
270 if (this->pm_particles_100um_sensor_ != nullptr)
271 this->pm_particles_100um_sensor_->publish_state(pm_particles_100um);
272 }
273
274 // Formaldehyde
275 if (this->type_ == PMSX003_TYPE_5003ST || this->type_ == PMSX003_TYPE_5003S) {
276 const uint16_t formaldehyde = this->get_16_bit_uint_(28);
277
278 ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde);
279
280 if (this->formaldehyde_sensor_ != nullptr)
281 this->formaldehyde_sensor_->publish_state(formaldehyde);
282 }
283
284 // Temperature and Humidity
285 if (this->type_ == PMSX003_TYPE_5003ST || this->type_ == PMSX003_TYPE_5003T) {
286 const uint8_t temperature_offset = (this->type_ == PMSX003_TYPE_5003T) ? 24 : 30;
287
288 const float temperature = static_cast<int16_t>(this->get_16_bit_uint_(temperature_offset)) / 10.0f;
289 const float humidity = this->get_16_bit_uint_(temperature_offset + 2) / 10.0f;
290
291 ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity);
292
293 if (this->temperature_sensor_ != nullptr)
294 this->temperature_sensor_->publish_state(temperature);
295 if (this->humidity_sensor_ != nullptr)
296 this->humidity_sensor_->publish_state(humidity);
297 }
298
299 // Firmware Version and Error Code
300 if (this->type_ == PMSX003_TYPE_5003ST) {
301 const uint8_t firmware_version = this->data_[36];
302 const uint8_t error_code = this->data_[37];
303
304 ESP_LOGD(TAG, "Got Firmware Version: 0x%02X, Error Code: 0x%02X", firmware_version, error_code);
305 }
306
307 // Spin down the sensor again if we aren't going to need it until more time has
308 // passed than it takes to stabilise
309 if (this->update_interval_ > PMS_STABILISING_MS) {
310 this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_SLEEP);
312 }
313
314 this->status_clear_warning();
315}
316
317uint16_t PMSX003Component::get_16_bit_uint_(uint8_t start_index) {
318 return (uint16_t(this->data_[start_index]) << 8) | uint16_t(this->data_[start_index + 1]);
319}
320
321} // namespace pmsx003
322} // namespace esphome
uint8_t checksum
Definition bl0906.h:3
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void status_clear_warning()
sensor::Sensor * pm_10_0_sensor_
Definition pmsx003.h:100
sensor::Sensor * pm_1_0_std_sensor_
Definition pmsx003.h:93
uint16_t get_16_bit_uint_(uint8_t start_index)
Definition pmsx003.cpp:317
sensor::Sensor * pm_particles_100um_sensor_
Definition pmsx003.h:108
sensor::Sensor * pm_2_5_sensor_
Definition pmsx003.h:99
sensor::Sensor * pm_particles_03um_sensor_
Definition pmsx003.h:103
sensor::Sensor * formaldehyde_sensor_
Definition pmsx003.h:111
sensor::Sensor * pm_2_5_std_sensor_
Definition pmsx003.h:94
sensor::Sensor * humidity_sensor_
Definition pmsx003.h:115
sensor::Sensor * pm_1_0_sensor_
Definition pmsx003.h:98
sensor::Sensor * pm_particles_10um_sensor_
Definition pmsx003.h:105
sensor::Sensor * pm_10_0_std_sensor_
Definition pmsx003.h:95
sensor::Sensor * pm_particles_25um_sensor_
Definition pmsx003.h:106
sensor::Sensor * pm_particles_05um_sensor_
Definition pmsx003.h:104
sensor::Sensor * temperature_sensor_
Definition pmsx003.h:114
sensor::Sensor * pm_particles_50um_sensor_
Definition pmsx003.h:107
void send_command_(PMSX0003Command cmd, uint16_t data)
Definition pmsx003.cpp:182
bool check_payload_length_(uint16_t payload_length)
Definition pmsx003.cpp:165
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:39
void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits=1, UARTParityOptions parity=UART_CONFIG_PARITY_NONE, uint8_t data_bits=8)
Check that the configuration of the UART bus matches the provided values and otherwise print a warnin...
Definition uart.cpp:13
bool read_byte(uint8_t *data)
Definition uart.h:29
void write_byte(uint8_t data)
Definition uart.h:19
@ PMS_CMD_MEASUREMENT_MODE
Definition pmsx003.h:11
@ PMS_CMD_MANUAL_MEASUREMENT
Definition pmsx003.h:13
@ PMSX003_STATE_STABILISING
Definition pmsx003.h:26
@ PMSX003_STATE_WAITING
Definition pmsx003.h:27
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t temperature
Definition sun_gtil2.cpp:12