ESPHome 2025.12.0
Loading...
Searching...
No Matches
sps30.cpp
Go to the documentation of this file.
1#include "esphome/core/hal.h"
2#include "esphome/core/log.h"
3#include "sps30.h"
4
5namespace esphome {
6namespace sps30 {
7
8static const char *const TAG = "sps30";
9
10static const uint16_t SPS30_CMD_GET_ARTICLE_CODE = 0xD025;
11static const uint16_t SPS30_CMD_GET_SERIAL_NUMBER = 0xD033;
12static const uint16_t SPS30_CMD_GET_FIRMWARE_VERSION = 0xD100;
13static const uint16_t SPS30_CMD_START_CONTINUOUS_MEASUREMENTS = 0x0010;
14static const uint16_t SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG = 0x0300;
15static const uint16_t SPS30_CMD_GET_DATA_READY_STATUS = 0x0202;
16static const uint16_t SPS30_CMD_READ_MEASUREMENT = 0x0300;
17static const uint16_t SPS30_CMD_STOP_MEASUREMENTS = 0x0104;
18static const uint16_t SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS = 0x8004;
19static const uint16_t SPS30_CMD_START_FAN_CLEANING = 0x5607;
20static const uint16_t SPS30_CMD_SOFT_RESET = 0xD304;
21static const size_t SERIAL_NUMBER_LENGTH = 8;
22static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5;
23static const uint32_t SPS30_WARM_UP_SEC = 30;
24
26 this->write_command(SPS30_CMD_SOFT_RESET);
28 this->set_timeout(500, [this]() {
30 if (!this->get_register(SPS30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version_, 1)) {
31 this->error_code_ = FIRMWARE_VERSION_READ_FAILED;
32 this->mark_failed();
33 return;
34 }
36 uint16_t raw_serial_number[8];
37 if (!this->get_register(SPS30_CMD_GET_SERIAL_NUMBER, raw_serial_number, 8, 1)) {
38 this->error_code_ = SERIAL_NUMBER_READ_FAILED;
39 this->mark_failed();
40 return;
41 }
42
43 for (size_t i = 0; i < 8; ++i) {
44 this->serial_number_[i * 2] = static_cast<char>(raw_serial_number[i] >> 8);
45 this->serial_number_[i * 2 + 1] = uint16_t(uint16_t(raw_serial_number[i] & 0xFF));
46 }
47 ESP_LOGV(TAG, " Serial number: %s", this->serial_number_);
48
49 bool result;
50 if (this->fan_interval_.has_value()) {
51 // override default value
52 result = this->write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS, this->fan_interval_.value());
53 } else {
54 result = this->write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS);
55 }
56
57 this->set_timeout(20, [this, result]() {
58 if (result) {
59 uint16_t secs[2];
60 if (this->read_data(secs, 2)) {
61 this->fan_interval_ = secs[0] << 16 | secs[1];
62 }
63 }
67 this->next_state_ms_ = millis() + SPS30_WARM_UP_SEC * 1000;
68 this->next_state_ = READ;
69 this->setup_complete_ = true;
70 });
71 });
72}
73
75 ESP_LOGCONFIG(TAG, "SPS30:");
76 LOG_I2C_DEVICE(this);
77 if (this->is_failed()) {
78 switch (this->error_code_) {
80 ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
81 break;
83 ESP_LOGW(TAG, "Measurement Initialization failed");
84 break;
86 ESP_LOGW(TAG, "Unable to request serial number");
87 break;
89 ESP_LOGW(TAG, "Unable to read serial number");
90 break;
92 ESP_LOGW(TAG, "Unable to request firmware version");
93 break;
95 ESP_LOGW(TAG, "Unable to read firmware version");
96 break;
97 default:
98 ESP_LOGW(TAG, "Unknown setup error");
99 break;
100 }
101 }
102 LOG_UPDATE_INTERVAL(this);
103 ESP_LOGCONFIG(TAG,
104 " Serial number: %s\n"
105 " Firmware version v%0d.%0d",
106 this->serial_number_, this->raw_firmware_version_ >> 8, this->raw_firmware_version_ & 0xFF);
107 if (this->idle_interval_.has_value()) {
108 ESP_LOGCONFIG(TAG, " Idle interval: %us", this->idle_interval_.value() / 1000);
109 }
110 LOG_SENSOR(" ", "PM1.0 Weight Concentration", this->pm_1_0_sensor_);
111 LOG_SENSOR(" ", "PM2.5 Weight Concentration", this->pm_2_5_sensor_);
112 LOG_SENSOR(" ", "PM4 Weight Concentration", this->pm_4_0_sensor_);
113 LOG_SENSOR(" ", "PM10 Weight Concentration", this->pm_10_0_sensor_);
114 LOG_SENSOR(" ", "PM1.0 Number Concentration", this->pmc_1_0_sensor_);
115 LOG_SENSOR(" ", "PM2.5 Number Concentration", this->pmc_2_5_sensor_);
116 LOG_SENSOR(" ", "PM4 Number Concentration", this->pmc_4_0_sensor_);
117 LOG_SENSOR(" ", "PM10 Number Concentration", this->pmc_10_0_sensor_);
118 LOG_SENSOR(" ", "PM typical size", this->pm_size_sensor_);
119}
120
122 if (!this->setup_complete_)
123 return;
125 if (this->status_has_warning()) {
126 ESP_LOGD(TAG, "Reconnecting");
127 if (this->write_command(SPS30_CMD_SOFT_RESET)) {
128 ESP_LOGD(TAG, "Soft-reset successful; waiting 500 ms");
129 this->set_timeout(500, [this]() {
132 this->status_clear_warning();
134 ESP_LOGD(TAG, "Reconnected; resuming continuous measurement");
135 });
136 } else {
137 ESP_LOGD(TAG, "Soft-reset failed");
138 }
139 return;
140 }
141
142 // If its not time to take an action, do nothing.
143 const uint32_t update_start_ms = millis();
144 if (this->next_state_ != NONE && (int32_t) (this->next_state_ms_ - update_start_ms) > 0) {
145 ESP_LOGD(TAG, "Sensor waiting for %ums before transitioning to state %d.", (this->next_state_ms_ - update_start_ms),
146 this->next_state_);
147 return;
148 }
149
150 switch (this->next_state_) {
151 case WAKE:
152 this->start_measurement();
153 return;
154 case NONE:
155 return;
156 case READ:
157 // Read logic continues below
158 break;
159 }
160
162 if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) {
163 this->status_set_warning();
164 return;
165 }
166
167 uint16_t raw_read_status;
168 if (!this->read_data(&raw_read_status, 1) || raw_read_status == 0x00) {
169 ESP_LOGD(TAG, "Not ready");
173 if (this->skipped_data_read_cycles_ > MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR) {
174 ESP_LOGD(TAG, "Exceeded max attempts; will reinitialize");
175 this->status_set_warning();
176 }
177 return;
178 }
179
180 if (!this->write_command(SPS30_CMD_READ_MEASUREMENT)) {
181 ESP_LOGW(TAG, "Error reading status");
182 this->status_set_warning();
183 return;
184 }
185
186 this->set_timeout(50, [this]() {
187 uint16_t raw_data[20];
188 if (!this->read_data(raw_data, 20)) {
189 ESP_LOGW(TAG, "Error reading data");
190 this->status_set_warning();
191 return;
192 }
193
194 union uint32_float_t {
195 uint32_t uint32;
196 float value;
197 };
198
200 uint32_float_t pm_1_0{.uint32 = (((uint32_t(raw_data[0])) << 16) | (uint32_t(raw_data[1])))};
201 uint32_float_t pm_2_5{.uint32 = (((uint32_t(raw_data[2])) << 16) | (uint32_t(raw_data[3])))};
202 uint32_float_t pm_4_0{.uint32 = (((uint32_t(raw_data[4])) << 16) | (uint32_t(raw_data[5])))};
203 uint32_float_t pm_10_0{.uint32 = (((uint32_t(raw_data[6])) << 16) | (uint32_t(raw_data[7])))};
204
206 uint32_float_t pmc_0_5{.uint32 = (((uint32_t(raw_data[8])) << 16) | (uint32_t(raw_data[9])))};
207 uint32_float_t pmc_1_0{.uint32 = (((uint32_t(raw_data[10])) << 16) | (uint32_t(raw_data[11])))};
208 uint32_float_t pmc_2_5{.uint32 = (((uint32_t(raw_data[12])) << 16) | (uint32_t(raw_data[13])))};
209 uint32_float_t pmc_4_0{.uint32 = (((uint32_t(raw_data[14])) << 16) | (uint32_t(raw_data[15])))};
210 uint32_float_t pmc_10_0{.uint32 = (((uint32_t(raw_data[16])) << 16) | (uint32_t(raw_data[17])))};
211
213 uint32_float_t pm_size{.uint32 = (((uint32_t(raw_data[18])) << 16) | (uint32_t(raw_data[19])))};
214
215 if (this->pm_1_0_sensor_ != nullptr)
216 this->pm_1_0_sensor_->publish_state(pm_1_0.value);
217 if (this->pm_2_5_sensor_ != nullptr)
218 this->pm_2_5_sensor_->publish_state(pm_2_5.value);
219 if (this->pm_4_0_sensor_ != nullptr)
220 this->pm_4_0_sensor_->publish_state(pm_4_0.value);
221 if (this->pm_10_0_sensor_ != nullptr)
222 this->pm_10_0_sensor_->publish_state(pm_10_0.value);
223
224 if (this->pmc_0_5_sensor_ != nullptr)
225 this->pmc_0_5_sensor_->publish_state(pmc_0_5.value);
226 if (this->pmc_1_0_sensor_ != nullptr)
227 this->pmc_1_0_sensor_->publish_state(pmc_1_0.value);
228 if (this->pmc_2_5_sensor_ != nullptr)
229 this->pmc_2_5_sensor_->publish_state(pmc_2_5.value);
230 if (this->pmc_4_0_sensor_ != nullptr)
231 this->pmc_4_0_sensor_->publish_state(pmc_4_0.value);
232 if (this->pmc_10_0_sensor_ != nullptr)
233 this->pmc_10_0_sensor_->publish_state(pmc_10_0.value);
234
235 if (this->pm_size_sensor_ != nullptr)
236 this->pm_size_sensor_->publish_state(pm_size.value);
237
238 this->status_clear_warning();
239 this->skipped_data_read_cycles_ = 0;
240
241 // Stop measurements and wait if we have an idle interval. If not using idle mode, let the next state just execute
242 // on next update.
243 if (this->idle_interval_.has_value()) {
244 this->stop_measurement();
245 this->next_state_ms_ = millis() + this->idle_interval_.value();
246 this->next_state_ = WAKE;
247 } else {
248 this->next_state_ms_ = millis();
249 }
250 });
251}
252
254 if (!this->write_command(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS, SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG)) {
255 ESP_LOGE(TAG, "Error initiating measurements");
256 return false;
257 }
258 ESP_LOGD(TAG, "Started measurements");
259
260 // Notify the state machine to wait the warm up interval before reading
261 this->next_state_ms_ = millis() + SPS30_WARM_UP_SEC * 1000;
262 this->next_state_ = READ;
263 return true;
264}
265
267
269 if (!write_command(SPS30_CMD_STOP_MEASUREMENTS)) {
270 ESP_LOGE(TAG, "Error stopping measurements");
271 return false;
272 } else {
273 ESP_LOGD(TAG, "Stopped measurements");
274 // Exit the state machine if measurement is stopped.
275 this->next_state_ms_ = 0;
276 this->next_state_ = NONE;
277 }
278 return true;
279}
280
282 if (!this->write_command(SPS30_CMD_START_FAN_CLEANING)) {
283 this->status_set_warning();
284 ESP_LOGE(TAG, "Start fan cleaning failed (%d)", this->last_error_);
285 return false;
286 } else {
287 ESP_LOGD(TAG, "Fan auto clean started");
288 }
289 return true;
290}
291
292} // namespace sps30
293} // namespace esphome
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_set_warning(const char *message=nullptr)
bool status_has_warning() const
void status_clear_warning()
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
bool has_value() const
Definition optional.h:92
value_type const & value() const
Definition optional.h:94
i2c::ErrorCode last_error_
last error code from I2C operation
bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay=0)
get data words from I2C register.
bool write_command(T i2c_register)
Write a command to the I2C device.
bool read_data(uint16_t *data, uint8_t len)
Read data words from I2C device.
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:77
sensor::Sensor * pm_2_5_sensor_
Definition sps30.h:57
optional< uint32_t > fan_interval_
Definition sps30.h:66
sensor::Sensor * pmc_4_0_sensor_
Definition sps30.h:63
sensor::Sensor * pmc_2_5_sensor_
Definition sps30.h:62
sensor::Sensor * pm_10_0_sensor_
Definition sps30.h:59
uint8_t skipped_data_read_cycles_
Terminating NULL character.
Definition sps30.h:39
enum esphome::sps30::SPS30Component::NextState NONE
sensor::Sensor * pm_1_0_sensor_
Definition sps30.h:56
sensor::Sensor * pm_size_sensor_
Definition sps30.h:65
void dump_config() override
Definition sps30.cpp:74
sensor::Sensor * pmc_0_5_sensor_
Definition sps30.h:60
sensor::Sensor * pm_4_0_sensor_
Definition sps30.h:58
optional< uint32_t > idle_interval_
Definition sps30.h:67
sensor::Sensor * pmc_10_0_sensor_
Definition sps30.h:64
sensor::Sensor * pmc_1_0_sensor_
Definition sps30.h:61
const char *const TAG
Definition spi.cpp:8
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:30