ESPHome 2025.5.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;
23
25 ESP_LOGCONFIG(TAG, "Setting up sps30...");
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_LOGD(TAG, " Serial Number: '%s'", this->serial_number_);
48
49 bool result;
50 if (this->fan_interval_.has_value()) {
51 // override default value
52 result = write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS, this->fan_interval_.value());
53 } else {
54 result = write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS);
55 }
56 if (result) {
57 delay(20);
58 uint16_t secs[2];
59 if (this->read_data(secs, 2)) {
60 fan_interval_ = secs[0] << 16 | secs[1];
61 }
62 }
63
67 });
68}
69
71 ESP_LOGCONFIG(TAG, "sps30:");
72 LOG_I2C_DEVICE(this);
73 if (this->is_failed()) {
74 switch (this->error_code_) {
76 ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
77 break;
79 ESP_LOGW(TAG, "Measurement Initialization failed!");
80 break;
82 ESP_LOGW(TAG, "Unable to request sensor serial number");
83 break;
85 ESP_LOGW(TAG, "Unable to read sensor serial number");
86 break;
88 ESP_LOGW(TAG, "Unable to request sensor firmware version");
89 break;
91 ESP_LOGW(TAG, "Unable to read sensor firmware version");
92 break;
93 default:
94 ESP_LOGW(TAG, "Unknown setup error!");
95 break;
96 }
97 }
98 LOG_UPDATE_INTERVAL(this);
99 ESP_LOGCONFIG(TAG, " Serial Number: '%s'", this->serial_number_);
100 ESP_LOGCONFIG(TAG, " Firmware version v%0d.%0d", (raw_firmware_version_ >> 8),
101 uint16_t(raw_firmware_version_ & 0xFF));
102 LOG_SENSOR(" ", "PM1.0 Weight Concentration", this->pm_1_0_sensor_);
103 LOG_SENSOR(" ", "PM2.5 Weight Concentration", this->pm_2_5_sensor_);
104 LOG_SENSOR(" ", "PM4 Weight Concentration", this->pm_4_0_sensor_);
105 LOG_SENSOR(" ", "PM10 Weight Concentration", this->pm_10_0_sensor_);
106 LOG_SENSOR(" ", "PM1.0 Number Concentration", this->pmc_1_0_sensor_);
107 LOG_SENSOR(" ", "PM2.5 Number Concentration", this->pmc_2_5_sensor_);
108 LOG_SENSOR(" ", "PM4 Number Concentration", this->pmc_4_0_sensor_);
109 LOG_SENSOR(" ", "PM10 Number Concentration", this->pmc_10_0_sensor_);
110 LOG_SENSOR(" ", "PM typical size", this->pm_size_sensor_);
111}
112
115 if (this->status_has_warning()) {
116 ESP_LOGD(TAG, "Trying to reconnect the sensor...");
117 if (this->write_command(SPS30_CMD_SOFT_RESET)) {
118 ESP_LOGD(TAG, "Sensor has soft-reset successfully. Waiting for reconnection in 500ms...");
119 this->set_timeout(500, [this]() {
122 this->status_clear_warning();
124 ESP_LOGD(TAG, "Sensor reconnected successfully. Resuming continuous measurement!");
125 });
126 } else {
127 ESP_LOGD(TAG, "Sensor soft-reset failed. Is the sensor offline?");
128 }
129 return;
130 }
132 if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) {
133 this->status_set_warning();
134 return;
135 }
136
137 uint16_t raw_read_status;
138 if (!this->read_data(&raw_read_status, 1) || raw_read_status == 0x00) {
139 ESP_LOGD(TAG, "Sensor measurement not ready yet.");
143 if (this->skipped_data_read_cycles_ > MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR) {
144 ESP_LOGD(TAG, "Sensor exceeded max allowed attempts. Sensor communication will be reinitialized.");
145 this->status_set_warning();
146 }
147 return;
148 }
149
150 if (!this->write_command(SPS30_CMD_READ_MEASUREMENT)) {
151 ESP_LOGW(TAG, "Error reading measurement status!");
152 this->status_set_warning();
153 return;
154 }
155
156 this->set_timeout(50, [this]() {
157 uint16_t raw_data[20];
158 if (!this->read_data(raw_data, 20)) {
159 ESP_LOGW(TAG, "Error reading measurement data!");
160 this->status_set_warning();
161 return;
162 }
163
164 union uint32_float_t {
165 uint32_t uint32;
166 float value;
167 };
168
170 uint32_float_t pm_1_0{.uint32 = (((uint32_t(raw_data[0])) << 16) | (uint32_t(raw_data[1])))};
171 uint32_float_t pm_2_5{.uint32 = (((uint32_t(raw_data[2])) << 16) | (uint32_t(raw_data[3])))};
172 uint32_float_t pm_4_0{.uint32 = (((uint32_t(raw_data[4])) << 16) | (uint32_t(raw_data[5])))};
173 uint32_float_t pm_10_0{.uint32 = (((uint32_t(raw_data[6])) << 16) | (uint32_t(raw_data[7])))};
174
176 uint32_float_t pmc_0_5{.uint32 = (((uint32_t(raw_data[8])) << 16) | (uint32_t(raw_data[9])))};
177 uint32_float_t pmc_1_0{.uint32 = (((uint32_t(raw_data[10])) << 16) | (uint32_t(raw_data[11])))};
178 uint32_float_t pmc_2_5{.uint32 = (((uint32_t(raw_data[12])) << 16) | (uint32_t(raw_data[13])))};
179 uint32_float_t pmc_4_0{.uint32 = (((uint32_t(raw_data[14])) << 16) | (uint32_t(raw_data[15])))};
180 uint32_float_t pmc_10_0{.uint32 = (((uint32_t(raw_data[16])) << 16) | (uint32_t(raw_data[17])))};
181
183 uint32_float_t pm_size{.uint32 = (((uint32_t(raw_data[18])) << 16) | (uint32_t(raw_data[19])))};
184
185 if (this->pm_1_0_sensor_ != nullptr)
186 this->pm_1_0_sensor_->publish_state(pm_1_0.value);
187 if (this->pm_2_5_sensor_ != nullptr)
188 this->pm_2_5_sensor_->publish_state(pm_2_5.value);
189 if (this->pm_4_0_sensor_ != nullptr)
190 this->pm_4_0_sensor_->publish_state(pm_4_0.value);
191 if (this->pm_10_0_sensor_ != nullptr)
192 this->pm_10_0_sensor_->publish_state(pm_10_0.value);
193
194 if (this->pmc_0_5_sensor_ != nullptr)
195 this->pmc_0_5_sensor_->publish_state(pmc_0_5.value);
196 if (this->pmc_1_0_sensor_ != nullptr)
197 this->pmc_1_0_sensor_->publish_state(pmc_1_0.value);
198 if (this->pmc_2_5_sensor_ != nullptr)
199 this->pmc_2_5_sensor_->publish_state(pmc_2_5.value);
200 if (this->pmc_4_0_sensor_ != nullptr)
201 this->pmc_4_0_sensor_->publish_state(pmc_4_0.value);
202 if (this->pmc_10_0_sensor_ != nullptr)
203 this->pmc_10_0_sensor_->publish_state(pmc_10_0.value);
204
205 if (this->pm_size_sensor_ != nullptr)
206 this->pm_size_sensor_->publish_state(pm_size.value);
207
208 this->status_clear_warning();
209 this->skipped_data_read_cycles_ = 0;
210 });
211}
212
214 uint8_t data[4];
215 data[0] = SPS30_CMD_START_CONTINUOUS_MEASUREMENTS & 0xFF;
216 data[1] = 0x03;
217 data[2] = 0x00;
218 data[3] = sht_crc_(0x03, 0x00);
219 if (!this->write_command(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS, SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG)) {
220 ESP_LOGE(TAG, "Error initiating measurements");
221 return false;
222 }
223 return true;
224}
225
227 if (!write_command(SPS30_CMD_START_FAN_CLEANING)) {
228 this->status_set_warning();
229 ESP_LOGE(TAG, "write error start fan (%d)", this->last_error_);
230 return false;
231 } else {
232 ESP_LOGD(TAG, "Fan auto clean started");
233 }
234 return true;
235}
236
237} // namespace sps30
238} // namespace esphome
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
bool status_has_warning() const
void status_set_warning(const char *message="unspecified")
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.
Definition component.cpp:72
bool has_value() const
Definition optional.h:87
value_type const & value() const
Definition optional.h:89
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.
uint8_t sht_crc_(uint16_t data)
8-bit CRC checksum that is transmitted after each data word for read and write operation
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:39
uint16_t raw_firmware_version_
Terminating NULL character.
Definition sps30.h:35
sensor::Sensor * pm_2_5_sensor_
Definition sps30.h:50
optional< uint32_t > fan_interval_
Definition sps30.h:59
sensor::Sensor * pmc_4_0_sensor_
Definition sps30.h:56
sensor::Sensor * pmc_2_5_sensor_
Definition sps30.h:55
sensor::Sensor * pm_10_0_sensor_
Definition sps30.h:52
sensor::Sensor * pm_1_0_sensor_
Definition sps30.h:49
sensor::Sensor * pm_size_sensor_
Definition sps30.h:58
void dump_config() override
Definition sps30.cpp:70
sensor::Sensor * pmc_0_5_sensor_
Definition sps30.h:53
sensor::Sensor * pm_4_0_sensor_
Definition sps30.h:51
sensor::Sensor * pmc_10_0_sensor_
Definition sps30.h:57
sensor::Sensor * pmc_1_0_sensor_
Definition sps30.h:54
const char *const TAG
Definition spi.cpp:8
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:28