10static const char *
const TAG =
"sen6x";
12static constexpr uint16_t SEN6X_CMD_GET_DATA_READY_STATUS = 0x0202;
13static constexpr uint16_t SEN6X_CMD_GET_FIRMWARE_VERSION = 0xD100;
14static constexpr uint16_t SEN6X_CMD_GET_PRODUCT_NAME = 0xD014;
15static constexpr uint16_t SEN6X_CMD_GET_SERIAL_NUMBER = 0xD033;
17static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT = 0x0300;
18static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN62 = 0x04A3;
19static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN63C = 0x0471;
20static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN65 = 0x0446;
21static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN68 = 0x0467;
22static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN69C = 0x04B5;
24static constexpr uint16_t SEN6X_CMD_START_MEASUREMENTS = 0x0021;
25static constexpr uint16_t SEN6X_CMD_RESET = 0xD304;
27static inline void set_read_command_and_words(SEN6XComponent::Sen6xType
type, uint16_t &read_cmd, uint8_t &read_words) {
28 read_cmd = SEN6X_CMD_READ_MEASUREMENT;
31 case SEN6XComponent::SEN62:
32 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN62;
35 case SEN6XComponent::SEN63C:
36 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN63C;
39 case SEN6XComponent::SEN65:
40 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN65;
43 case SEN6XComponent::SEN66:
44 read_cmd = SEN6X_CMD_READ_MEASUREMENT;
47 case SEN6XComponent::SEN68:
48 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN68;
51 case SEN6XComponent::SEN69C:
52 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN69C;
61 ESP_LOGCONFIG(TAG,
"Setting up sen6x...");
67 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
75 uint16_t raw_serial_number[16];
76 if (!this->
get_register(SEN6X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 16, 20)) {
77 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
86 uint16_t raw_product_name[16];
87 if (!this->
get_register(SEN6X_CMD_GET_PRODUCT_NAME, raw_product_name, 16, 20)) {
88 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
98 if (inferred_type == UNKNOWN) {
99 ESP_LOGE(TAG,
"Unknown product '%s'", this->
product_name_.c_str());
103 ESP_LOGD(TAG,
"Type inferred from product: %s", this->
product_name_.c_str());
104 }
else if (this->
sen6x_type_ != inferred_type && inferred_type != UNKNOWN) {
105 ESP_LOGW(TAG,
"Configured type (used) mismatches product '%s'", this->
product_name_.c_str());
114 if (this->voc_sensor_ && !has_voc_nox) {
115 ESP_LOGE(TAG,
"VOC requires SEN65, SEN66, SEN68, or SEN69C");
116 this->voc_sensor_ =
nullptr;
118 if (this->nox_sensor_ && !has_voc_nox) {
119 ESP_LOGE(TAG,
"NOx requires SEN65, SEN66, SEN68, or SEN69C");
120 this->nox_sensor_ =
nullptr;
122 if (this->co2_sensor_ && !has_co2) {
123 ESP_LOGE(TAG,
"CO2 requires SEN63C, SEN66, or SEN69C");
124 this->co2_sensor_ =
nullptr;
126 if (this->hcho_sensor_ && !has_hcho) {
127 ESP_LOGE(TAG,
"Formaldehyde requires SEN68 or SEN69C");
128 this->hcho_sensor_ =
nullptr;
133 uint16_t raw_firmware_version = 0;
134 if (!this->
get_register(SEN6X_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 20)) {
135 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
144 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
151 ESP_LOGD(TAG,
"Initialized");
158void SEN6XComponent::dump_config() {
165 this->
product_name_.c_str(), this->serial_number_.c_str(), this->firmware_version_major_,
166 this->firmware_version_minor_, this->address_);
167 LOG_UPDATE_INTERVAL(
this);
168 LOG_SENSOR(
" ",
"PM 1.0", this->pm_1_0_sensor_);
169 LOG_SENSOR(
" ",
"PM 2.5", this->pm_2_5_sensor_);
170 LOG_SENSOR(
" ",
"PM 4.0", this->pm_4_0_sensor_);
171 LOG_SENSOR(
" ",
"PM 10.0", this->pm_10_0_sensor_);
172 LOG_SENSOR(
" ",
"Temperature", this->temperature_sensor_);
173 LOG_SENSOR(
" ",
"Humidity", this->humidity_sensor_);
174 LOG_SENSOR(
" ",
"VOC", this->voc_sensor_);
175 LOG_SENSOR(
" ",
"NOx", this->nox_sensor_);
176 LOG_SENSOR(
" ",
"HCHO", this->hcho_sensor_);
177 LOG_SENSOR(
" ",
"CO2", this->co2_sensor_);
180void SEN6XComponent::update() {
187 set_read_command_and_words(this->
sen6x_type_, read_cmd, read_words);
189 const uint8_t poll_retries = 24;
190 auto poll_ready = std::make_shared<std::function<void(uint8_t)>>();
191 *poll_ready = [
this, poll_ready, read_cmd, read_words](uint8_t retries_left) {
192 const uint8_t attempt =
static_cast<uint8_t
>(poll_retries - retries_left + 1);
193 ESP_LOGV(TAG,
"Data ready polling attempt %u", attempt);
197 ESP_LOGD(TAG,
"write data ready status error (%d)", this->
last_error_);
201 this->
set_timeout(20, [
this, poll_ready, retries_left, read_cmd, read_words]() {
202 uint16_t raw_read_status;
203 if (!this->
read_data(&raw_read_status, 1)) {
205 ESP_LOGD(TAG,
"read data ready status error (%d)", this->
last_error_);
209 if ((raw_read_status & 0x0001) == 0) {
210 if (retries_left == 0) {
212 ESP_LOGD(TAG,
"Data not ready");
215 this->
set_timeout(50, [poll_ready, retries_left]() { (*poll_ready)(retries_left - 1); });
221 ESP_LOGD(TAG,
"Read measurement failed (%d)", this->
last_error_);
226 uint16_t measurements[10];
228 if (!this->
read_data(measurements, read_words)) {
230 ESP_LOGD(TAG,
"Read data failed (%d)", this->
last_error_);
233 int8_t voc_index = -1;
234 int8_t nox_index = -1;
235 int8_t hcho_index = -1;
236 int8_t co2_index = -1;
237 bool co2_uint16 =
false;
269 float pm_1_0 = measurements[0] / 10.0f;
270 if (measurements[0] == 0xFFFF)
272 float pm_2_5 = measurements[1] / 10.0f;
273 if (measurements[1] == 0xFFFF)
275 float pm_4_0 = measurements[2] / 10.0f;
276 if (measurements[2] == 0xFFFF)
278 float pm_10_0 = measurements[3] / 10.0f;
279 if (measurements[3] == 0xFFFF)
281 float humidity =
static_cast<int16_t
>(measurements[4]) / 100.0f;
282 if (measurements[4] == 0x7FFF)
284 float temperature =
static_cast<int16_t
>(measurements[5]) / 200.0f;
285 if (measurements[5] == 0x7FFF)
293 if (voc_index >= 0) {
294 voc =
static_cast<int16_t
>(measurements[voc_index]) / 10.0f;
295 if (measurements[voc_index] == 0x7FFF)
298 if (nox_index >= 0) {
299 nox =
static_cast<int16_t
>(measurements[nox_index]) / 10.0f;
300 if (measurements[nox_index] == 0x7FFF)
304 if (hcho_index >= 0) {
305 const uint16_t hcho_raw = measurements[hcho_index];
306 hcho = hcho_raw / 10.0f;
307 if (hcho_raw == 0xFFFF)
311 if (co2_index >= 0) {
313 const uint16_t co2_raw = measurements[co2_index];
314 co2 =
static_cast<float>(co2_raw);
315 if (co2_raw == 0xFFFF)
318 const int16_t co2_raw =
static_cast<int16_t
>(measurements[co2_index]);
319 co2 =
static_cast<float>(co2_raw);
320 if (co2_raw == 0x7FFF)
326 ESP_LOGD(TAG,
"Startup delay, ignoring values");
331 if (this->pm_1_0_sensor_ !=
nullptr)
332 this->pm_1_0_sensor_->publish_state(pm_1_0);
333 if (this->pm_2_5_sensor_ !=
nullptr)
334 this->pm_2_5_sensor_->publish_state(pm_2_5);
335 if (this->pm_4_0_sensor_ !=
nullptr)
336 this->pm_4_0_sensor_->publish_state(pm_4_0);
337 if (this->pm_10_0_sensor_ !=
nullptr)
338 this->pm_10_0_sensor_->publish_state(pm_10_0);
339 if (this->temperature_sensor_ !=
nullptr)
340 this->temperature_sensor_->publish_state(
temperature);
341 if (this->humidity_sensor_ !=
nullptr)
342 this->humidity_sensor_->publish_state(humidity);
343 if (this->voc_sensor_ !=
nullptr)
344 this->voc_sensor_->publish_state(voc);
345 if (this->nox_sensor_ !=
nullptr)
346 this->nox_sensor_->publish_state(nox);
347 if (this->hcho_sensor_ !=
nullptr)
348 this->hcho_sensor_->publish_state(hcho);
349 if (this->co2_sensor_ !=
nullptr)
350 this->co2_sensor_->publish_state(co2);
357 (*poll_ready)(poll_retries);
361 if (product_name ==
"SEN62")
363 if (product_name ==
"SEN63C")
365 if (product_name ==
"SEN65")
367 if (product_name ==
"SEN66")
369 if (product_name ==
"SEN68")
371 if (product_name ==
"SEN69C")