ESPHome 2025.10.3
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;
23
25 this->write_command(SPS30_CMD_SOFT_RESET);
27 this->set_timeout(500, [this]() {
29 if (!this->get_register(SPS30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version_, 1)) {
30 this->error_code_ = FIRMWARE_VERSION_READ_FAILED;
31 this->mark_failed();
32 return;
33 }
35 uint16_t raw_serial_number[8];
36 if (!this->get_register(SPS30_CMD_GET_SERIAL_NUMBER, raw_serial_number, 8, 1)) {
37 this->error_code_ = SERIAL_NUMBER_READ_FAILED;
38 this->mark_failed();
39 return;
40 }
41
42 for (size_t i = 0; i < 8; ++i) {
43 this->serial_number_[i * 2] = static_cast<char>(raw_serial_number[i] >> 8);
44 this->serial_number_[i * 2 + 1] = uint16_t(uint16_t(raw_serial_number[i] & 0xFF));
45 }
46 ESP_LOGV(TAG, " Serial number: %s", this->serial_number_);
47
48 bool result;
49 if (this->fan_interval_.has_value()) {
50 // override default value
51 result = this->write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS, this->fan_interval_.value());
52 } else {
53 result = this->write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS);
54 }
55
56 this->set_timeout(20, [this, result]() {
57 if (result) {
58 uint16_t secs[2];
59 if (this->read_data(secs, 2)) {
60 this->fan_interval_ = secs[0] << 16 | secs[1];
61 }
62 }
66 this->setup_complete_ = true;
67 });
68 });
69}
70
72 ESP_LOGCONFIG(TAG, "SPS30:");
73 LOG_I2C_DEVICE(this);
74 if (this->is_failed()) {
75 switch (this->error_code_) {
77 ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
78 break;
80 ESP_LOGW(TAG, "Measurement Initialization failed");
81 break;
83 ESP_LOGW(TAG, "Unable to request serial number");
84 break;
86 ESP_LOGW(TAG, "Unable to read serial number");
87 break;
89 ESP_LOGW(TAG, "Unable to request firmware version");
90 break;
92 ESP_LOGW(TAG, "Unable to read firmware version");
93 break;
94 default:
95 ESP_LOGW(TAG, "Unknown setup error");
96 break;
97 }
98 }
99 LOG_UPDATE_INTERVAL(this);
100 ESP_LOGCONFIG(TAG,
101 " Serial number: %s\n"
102 " Firmware version v%0d.%0d",
103 this->serial_number_, this->raw_firmware_version_ >> 8, this->raw_firmware_version_ & 0xFF);
104 LOG_SENSOR(" ", "PM1.0 Weight Concentration", this->pm_1_0_sensor_);
105 LOG_SENSOR(" ", "PM2.5 Weight Concentration", this->pm_2_5_sensor_);
106 LOG_SENSOR(" ", "PM4 Weight Concentration", this->pm_4_0_sensor_);
107 LOG_SENSOR(" ", "PM10 Weight Concentration", this->pm_10_0_sensor_);
108 LOG_SENSOR(" ", "PM1.0 Number Concentration", this->pmc_1_0_sensor_);
109 LOG_SENSOR(" ", "PM2.5 Number Concentration", this->pmc_2_5_sensor_);
110 LOG_SENSOR(" ", "PM4 Number Concentration", this->pmc_4_0_sensor_);
111 LOG_SENSOR(" ", "PM10 Number Concentration", this->pmc_10_0_sensor_);
112 LOG_SENSOR(" ", "PM typical size", this->pm_size_sensor_);
113}
114
116 if (!this->setup_complete_)
117 return;
119 if (this->status_has_warning()) {
120 ESP_LOGD(TAG, "Reconnecting");
121 if (this->write_command(SPS30_CMD_SOFT_RESET)) {
122 ESP_LOGD(TAG, "Soft-reset successful; waiting 500 ms");
123 this->set_timeout(500, [this]() {
126 this->status_clear_warning();
128 ESP_LOGD(TAG, "Reconnected; resuming continuous measurement");
129 });
130 } else {
131 ESP_LOGD(TAG, "Soft-reset failed");
132 }
133 return;
134 }
136 if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) {
137 this->status_set_warning();
138 return;
139 }
140
141 uint16_t raw_read_status;
142 if (!this->read_data(&raw_read_status, 1) || raw_read_status == 0x00) {
143 ESP_LOGD(TAG, "Not ready");
147 if (this->skipped_data_read_cycles_ > MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR) {
148 ESP_LOGD(TAG, "Exceeded max attempts; will reinitialize");
149 this->status_set_warning();
150 }
151 return;
152 }
153
154 if (!this->write_command(SPS30_CMD_READ_MEASUREMENT)) {
155 ESP_LOGW(TAG, "Error reading status");
156 this->status_set_warning();
157 return;
158 }
159
160 this->set_timeout(50, [this]() {
161 uint16_t raw_data[20];
162 if (!this->read_data(raw_data, 20)) {
163 ESP_LOGW(TAG, "Error reading data");
164 this->status_set_warning();
165 return;
166 }
167
168 union uint32_float_t {
169 uint32_t uint32;
170 float value;
171 };
172
174 uint32_float_t pm_1_0{.uint32 = (((uint32_t(raw_data[0])) << 16) | (uint32_t(raw_data[1])))};
175 uint32_float_t pm_2_5{.uint32 = (((uint32_t(raw_data[2])) << 16) | (uint32_t(raw_data[3])))};
176 uint32_float_t pm_4_0{.uint32 = (((uint32_t(raw_data[4])) << 16) | (uint32_t(raw_data[5])))};
177 uint32_float_t pm_10_0{.uint32 = (((uint32_t(raw_data[6])) << 16) | (uint32_t(raw_data[7])))};
178
180 uint32_float_t pmc_0_5{.uint32 = (((uint32_t(raw_data[8])) << 16) | (uint32_t(raw_data[9])))};
181 uint32_float_t pmc_1_0{.uint32 = (((uint32_t(raw_data[10])) << 16) | (uint32_t(raw_data[11])))};
182 uint32_float_t pmc_2_5{.uint32 = (((uint32_t(raw_data[12])) << 16) | (uint32_t(raw_data[13])))};
183 uint32_float_t pmc_4_0{.uint32 = (((uint32_t(raw_data[14])) << 16) | (uint32_t(raw_data[15])))};
184 uint32_float_t pmc_10_0{.uint32 = (((uint32_t(raw_data[16])) << 16) | (uint32_t(raw_data[17])))};
185
187 uint32_float_t pm_size{.uint32 = (((uint32_t(raw_data[18])) << 16) | (uint32_t(raw_data[19])))};
188
189 if (this->pm_1_0_sensor_ != nullptr)
190 this->pm_1_0_sensor_->publish_state(pm_1_0.value);
191 if (this->pm_2_5_sensor_ != nullptr)
192 this->pm_2_5_sensor_->publish_state(pm_2_5.value);
193 if (this->pm_4_0_sensor_ != nullptr)
194 this->pm_4_0_sensor_->publish_state(pm_4_0.value);
195 if (this->pm_10_0_sensor_ != nullptr)
196 this->pm_10_0_sensor_->publish_state(pm_10_0.value);
197
198 if (this->pmc_0_5_sensor_ != nullptr)
199 this->pmc_0_5_sensor_->publish_state(pmc_0_5.value);
200 if (this->pmc_1_0_sensor_ != nullptr)
201 this->pmc_1_0_sensor_->publish_state(pmc_1_0.value);
202 if (this->pmc_2_5_sensor_ != nullptr)
203 this->pmc_2_5_sensor_->publish_state(pmc_2_5.value);
204 if (this->pmc_4_0_sensor_ != nullptr)
205 this->pmc_4_0_sensor_->publish_state(pmc_4_0.value);
206 if (this->pmc_10_0_sensor_ != nullptr)
207 this->pmc_10_0_sensor_->publish_state(pmc_10_0.value);
208
209 if (this->pm_size_sensor_ != nullptr)
210 this->pm_size_sensor_->publish_state(pm_size.value);
211
212 this->status_clear_warning();
213 this->skipped_data_read_cycles_ = 0;
214 });
215}
216
218 if (!this->write_command(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS, SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG)) {
219 ESP_LOGE(TAG, "Error initiating measurements");
220 return false;
221 }
222 return true;
223}
224
226 if (!this->write_command(SPS30_CMD_START_FAN_CLEANING)) {
227 this->status_set_warning();
228 ESP_LOGE(TAG, "Start fan cleaning failed (%d)", this->last_error_);
229 return false;
230 } else {
231 ESP_LOGD(TAG, "Fan auto clean started");
232 }
233 return true;
234}
235
236} // namespace sps30
237} // 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:73
sensor::Sensor * pm_2_5_sensor_
Definition sps30.h:51
optional< uint32_t > fan_interval_
Definition sps30.h:60
sensor::Sensor * pmc_4_0_sensor_
Definition sps30.h:57
sensor::Sensor * pmc_2_5_sensor_
Definition sps30.h:56
sensor::Sensor * pm_10_0_sensor_
Definition sps30.h:53
uint8_t skipped_data_read_cycles_
Terminating NULL character.
Definition sps30.h:36
sensor::Sensor * pm_1_0_sensor_
Definition sps30.h:50
sensor::Sensor * pm_size_sensor_
Definition sps30.h:59
void dump_config() override
Definition sps30.cpp:71
sensor::Sensor * pmc_0_5_sensor_
Definition sps30.h:54
sensor::Sensor * pm_4_0_sensor_
Definition sps30.h:52
sensor::Sensor * pmc_10_0_sensor_
Definition sps30.h:58
sensor::Sensor * pmc_1_0_sensor_
Definition sps30.h:55
const char *const TAG
Definition spi.cpp:8
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7