12#ifdef SEN6X_STATUS_SENSOR_COUNT
14static uint8_t status_bit(
Sen6xStatus status, SEN6XComponent::Sen6xType
type) {
25 return type == SEN6XComponent::SEN66 ? 9 : 12;
35static constexpr uint8_t POLL_RETRIES = 24;
36static constexpr uint32_t I2C_READ_DELAY = 20;
37static constexpr uint32_t CMD_EXEC_DELAY = 20;
38static constexpr uint32_t POLL_INTERVAL = 50;
40static constexpr uint32_t TIMEOUT_POLL = 1;
41static constexpr uint32_t TIMEOUT_SETUP_STEP = 2;
42static constexpr uint32_t TIMEOUT_STARTUP = 3;
45static constexpr uint32_t START_MEASUREMENT_DELAY = 50;
46static constexpr uint32_t STOP_MEASUREMENT_DELAY = 1400;
47static constexpr uint32_t FAN_CLEANING_DELAY = 10000;
48static constexpr uint32_t SHT_HEATER_DELAY = 20000;
49static constexpr uint32_t CO2_CONDITIONING_DELAY = 24000;
51static constexpr float AMBIENT_PRESSURE_MIN_HPA = 700.0f;
52static constexpr float AMBIENT_PRESSURE_MAX_HPA = 1200.0f;
53static constexpr uint16_t SEN6X_CMD_GET_DATA_READY_STATUS = 0x0202;
54static constexpr uint16_t SEN6X_CMD_GET_FIRMWARE_VERSION = 0xD100;
55static constexpr uint16_t SEN6X_CMD_GET_PRODUCT_NAME = 0xD014;
56static constexpr uint16_t SEN6X_CMD_GET_SERIAL_NUMBER = 0xD033;
57static constexpr uint16_t SEN6X_CMD_READ_AND_CLEAR_DEVICE_STATUS = 0xD210;
59static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT = 0x0300;
60static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN62 = 0x04A3;
61static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN63C = 0x0471;
62static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN65 = 0x0446;
63static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN68 = 0x0467;
64static constexpr uint16_t SEN6X_CMD_READ_MEASUREMENT_SEN69C = 0x04B5;
66#ifdef USE_SEN6X_NUMBER_CONCENTRATION
67static constexpr uint16_t SEN6X_CMD_READ_NUMBER_CONCENTRATION = 0x0316;
69static constexpr uint16_t SEN6X_CMD_START_MEASUREMENTS = 0x0021;
70static constexpr uint16_t SEN6X_CMD_STOP_MEASUREMENTS = 0x0104;
71static constexpr uint16_t SEN6X_CMD_START_FAN_CLEANING = 0x5607;
72static constexpr uint16_t SEN6X_CMD_ACTIVATE_SHT_HEATER = 0x6765;
73static constexpr uint16_t SEN6X_CMD_RESET = 0xD304;
74static constexpr uint16_t SEN6X_CMD_VOC_ALGORITHM_TUNING = 0x60D0;
75static constexpr uint16_t SEN6X_CMD_NOX_ALGORITHM_TUNING = 0x60E1;
76static constexpr uint16_t SEN6X_CMD_TEMPERATURE_COMPENSATION = 0x60B2;
77static constexpr uint16_t SEN6X_CMD_TEMPERATURE_ACCELERATION = 0x6100;
78static constexpr uint16_t SEN6X_CMD_CO2_AUTOMATIC_SELF_CAL = 0x6711;
79static constexpr uint16_t SEN6X_CMD_AMBIENT_PRESSURE = 0x6720;
80static constexpr uint16_t SEN6X_CMD_SENSOR_ALTITUDE = 0x6736;
82static inline void set_read_command_and_words(SEN6XComponent::Sen6xType
type, uint16_t &read_cmd, uint8_t &read_words) {
83 read_cmd = SEN6X_CMD_READ_MEASUREMENT;
86 case SEN6XComponent::SEN62:
87 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN62;
90 case SEN6XComponent::SEN63C:
91 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN63C;
94 case SEN6XComponent::SEN65:
95 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN65;
98 case SEN6XComponent::SEN66:
99 read_cmd = SEN6X_CMD_READ_MEASUREMENT;
102 case SEN6XComponent::SEN68:
103 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN68;
106 case SEN6XComponent::SEN69C:
107 read_cmd = SEN6X_CMD_READ_MEASUREMENT_SEN69C;
116 ESP_LOGCONFIG(TAG,
"Setting up sen6x...");
122 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
130 uint16_t raw_serial_number[16];
131 if (!this->
get_register(SEN6X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 16, 20)) {
132 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
141 uint16_t raw_product_name[16];
142 if (!this->
get_register(SEN6X_CMD_GET_PRODUCT_NAME, raw_product_name, 16, 20)) {
143 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
153 if (inferred_type == UNKNOWN) {
154 ESP_LOGE(TAG,
"Unknown product '%s'", this->
product_name_.c_str());
158 ESP_LOGD(TAG,
"Type inferred from product: %s", this->
product_name_.c_str());
159 }
else if (this->
sen6x_type_ != inferred_type && inferred_type != UNKNOWN) {
160 ESP_LOGW(TAG,
"Configured type (used) mismatches product '%s'", this->
product_name_.c_str());
169 if (this->voc_sensor_ && !has_voc_nox) {
170 ESP_LOGE(TAG,
"VOC requires SEN65, SEN66, SEN68, or SEN69C");
171 this->voc_sensor_ =
nullptr;
173 if (this->nox_sensor_ && !has_voc_nox) {
174 ESP_LOGE(TAG,
"NOx requires SEN65, SEN66, SEN68, or SEN69C");
175 this->nox_sensor_ =
nullptr;
177 if (this->co2_sensor_ && !has_co2) {
178 ESP_LOGE(TAG,
"CO2 requires SEN63C, SEN66, or SEN69C");
179 this->co2_sensor_ =
nullptr;
181 if (this->hcho_sensor_ && !has_hcho) {
182 ESP_LOGE(TAG,
"Formaldehyde requires SEN68 or SEN69C");
183 this->hcho_sensor_ =
nullptr;
185#ifdef SEN6X_STATUS_SENSOR_COUNT
193 ESP_LOGE(TAG,
"'%s' needs a variant with that feature", LOG_STR_ARG(entry.sensor->get_log_name()));
196 entry.bit = status_bit(entry.status, this->sen6x_type_);
197 this->status_sensors_[kept++] = entry;
199 this->status_sensors_.resize(kept);
204 uint16_t raw_firmware_version = 0;
205 if (!this->
get_register(SEN6X_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 20)) {
206 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
260 if (this->co2_sensor_ ==
nullptr || !this->
co2_asc_.has_value())
281 uint16_t params[4] = {
static_cast<uint16_t
>(compensation.
offset),
287 uint16_t params[4] = {acceleration.
k, acceleration.
p, acceleration.
t1, acceleration.
t2};
297 ESP_LOGE(TAG,
"Write 0x%04X failed, error %d", SEN6X_CMD_START_MEASUREMENTS, this->
last_error_);
304 ESP_LOGD(TAG,
"Initialized");
311 ESP_LOGE(TAG,
"Write 0x%04X failed, error %d", i2c_command, this->
last_error_);
329void SEN6XComponent::dump_config() {
336 " Startup delay: %" PRIu32
" ms",
337 this->
product_name_.c_str(), this->serial_number_.c_str(), this->firmware_version_major_,
338 this->firmware_version_minor_, this->address_, this->startup_delay_ms_);
339 LOG_UPDATE_INTERVAL(
this);
342 ESP_LOGCONFIG(TAG,
" Temperature compensation: offset=%.2f slope=%.4f time_constant=%us", comp.offset / 200.0f,
343 comp.normalized_offset_slope / 10000.0f, comp.time_constant);
347 ESP_LOGCONFIG(TAG,
" Temperature acceleration: K=%.1f P=%.1f T1=%.1f T2=%.1f", accel.k / 10.0f, accel.p / 10.0f,
348 accel.t1 / 10.0f, accel.t2 / 10.0f);
351 if (this->co2_sensor_ !=
nullptr) {
353 ESP_LOGCONFIG(TAG,
" CO2 automatic self-calibration: %s", ONOFF(this->
co2_asc_.value()));
359 ESP_LOGCONFIG(TAG,
" Ambient pressure compensation: %u hPa", this->
ambient_pressure_);
362 ESP_LOGCONFIG(TAG,
" Ambient pressure compensation source: %s",
366 LOG_SENSOR(
" ",
"PM 1.0", this->pm_1_0_sensor_);
367 LOG_SENSOR(
" ",
"PM 2.5", this->pm_2_5_sensor_);
368 LOG_SENSOR(
" ",
"PM 4.0", this->pm_4_0_sensor_);
369 LOG_SENSOR(
" ",
"PM 10.0", this->pm_10_0_sensor_);
370#ifdef USE_SEN6X_NUMBER_CONCENTRATION
371 LOG_SENSOR(
" ",
"PMC 0.5", this->pmc_0_5_sensor_);
372 LOG_SENSOR(
" ",
"PMC 1.0", this->pmc_1_0_sensor_);
373 LOG_SENSOR(
" ",
"PMC 2.5", this->pmc_2_5_sensor_);
374 LOG_SENSOR(
" ",
"PMC 4.0", this->pmc_4_0_sensor_);
375 LOG_SENSOR(
" ",
"PMC 10.0", this->pmc_10_0_sensor_);
377 LOG_SENSOR(
" ",
"Temperature", this->temperature_sensor_);
378 LOG_SENSOR(
" ",
"Humidity", this->humidity_sensor_);
379 LOG_SENSOR(
" ",
"VOC", this->voc_sensor_);
380 LOG_SENSOR(
" ",
"NOx", this->nox_sensor_);
381 LOG_SENSOR(
" ",
"HCHO", this->hcho_sensor_);
382 LOG_SENSOR(
" ",
"CO2", this->co2_sensor_);
383#ifdef SEN6X_STATUS_SENSOR_COUNT
384 for (
const auto &entry : this->status_sensors_) {
385 LOG_BINARY_SENSOR(
" ",
"Device status", entry.sensor);
390void SEN6XComponent::update() {
398 this->
set_timeout(TIMEOUT_POLL, wait, [
this]() { this->update(); });
442 ESP_LOGD(TAG,
"Data not ready");
450 ESP_LOGV(TAG,
"Data ready polling attempt %u",
456 ESP_LOGD(TAG,
"write data ready status error (%d)", this->
last_error_);
460 this->
set_timeout(TIMEOUT_POLL, I2C_READ_DELAY, [
this]() {
461 uint16_t raw_read_status;
462 if (!this->
read_data(&raw_read_status, 1)) {
464 ESP_LOGD(TAG,
"read data ready status error (%d)", this->
last_error_);
468 if ((raw_read_status & 0x0001) == 0) {
481 ESP_LOGD(TAG,
"Read measurement failed (%d)", this->
last_error_);
489 uint16_t measurements[10];
493 ESP_LOGD(TAG,
"Read data failed (%d)", this->
last_error_);
496 int8_t voc_index = -1;
497 int8_t nox_index = -1;
498 int8_t hcho_index = -1;
499 int8_t co2_index = -1;
500 bool co2_uint16 =
false;
532 float pm_1_0 = measurements[0] / 10.0f;
533 if (measurements[0] == 0xFFFF)
535 float pm_2_5 = measurements[1] / 10.0f;
536 if (measurements[1] == 0xFFFF)
538 float pm_4_0 = measurements[2] / 10.0f;
539 if (measurements[2] == 0xFFFF)
541 float pm_10_0 = measurements[3] / 10.0f;
542 if (measurements[3] == 0xFFFF)
544 float humidity =
static_cast<int16_t
>(measurements[4]) / 100.0f;
545 if (measurements[4] == 0x7FFF)
547 float temperature =
static_cast<int16_t
>(measurements[5]) / 200.0f;
548 if (measurements[5] == 0x7FFF)
556 if (voc_index >= 0) {
557 voc =
static_cast<int16_t
>(measurements[voc_index]) / 10.0f;
558 if (measurements[voc_index] == 0x7FFF)
561 if (nox_index >= 0) {
562 nox =
static_cast<int16_t
>(measurements[nox_index]) / 10.0f;
563 if (measurements[nox_index] == 0x7FFF)
567 if (hcho_index >= 0) {
568 const uint16_t hcho_raw = measurements[hcho_index];
569 hcho = hcho_raw / 10.0f;
570 if (hcho_raw == 0xFFFF)
574 if (co2_index >= 0) {
576 const uint16_t co2_raw = measurements[co2_index];
577 co2 =
static_cast<float>(co2_raw);
578 if (co2_raw == 0xFFFF)
581 const int16_t co2_raw =
static_cast<int16_t
>(measurements[co2_index]);
582 co2 =
static_cast<float>(co2_raw);
583 if (co2_raw == 0x7FFF)
589 ESP_LOGD(TAG,
"Startup delay, ignoring values");
594 if (this->pm_1_0_sensor_ !=
nullptr)
595 this->pm_1_0_sensor_->publish_state(pm_1_0);
596 if (this->pm_2_5_sensor_ !=
nullptr)
597 this->pm_2_5_sensor_->publish_state(pm_2_5);
598 if (this->pm_4_0_sensor_ !=
nullptr)
599 this->pm_4_0_sensor_->publish_state(pm_4_0);
600 if (this->pm_10_0_sensor_ !=
nullptr)
601 this->pm_10_0_sensor_->publish_state(pm_10_0);
602 if (this->temperature_sensor_ !=
nullptr)
603 this->temperature_sensor_->publish_state(
temperature);
604 if (this->humidity_sensor_ !=
nullptr)
605 this->humidity_sensor_->publish_state(humidity);
606 if (this->voc_sensor_ !=
nullptr)
607 this->voc_sensor_->publish_state(voc);
608 if (this->nox_sensor_ !=
nullptr)
609 this->nox_sensor_->publish_state(nox);
610 if (this->hcho_sensor_ !=
nullptr)
611 this->hcho_sensor_->publish_state(hcho);
612 if (this->co2_sensor_ !=
nullptr)
613 this->co2_sensor_->publish_state(co2);
615#ifdef USE_SEN6X_NUMBER_CONCENTRATION
629#ifdef SEN6X_STATUS_SENSOR_COUNT
630 if (!this->status_sensors_.empty()) {
636 if (this->
write_command(SEN6X_CMD_READ_AND_CLEAR_DEVICE_STATUS)) {
640 ESP_LOGD(TAG,
"Read device status failed (%d)", this->
last_error_);
646#ifdef SEN6X_STATUS_SENSOR_COUNT
651 for (
const auto &entry : this->status_sensors_)
652 entry.sensor->publish_state(((
status >> entry.bit) & 1) != 0);
654 ESP_LOGD(TAG,
"Read device status failed (%d)", this->
last_error_);
660#ifdef USE_SEN6X_NUMBER_CONCENTRATION
662 return this->pmc_0_5_sensor_ !=
nullptr || this->pmc_1_0_sensor_ !=
nullptr || this->pmc_2_5_sensor_ !=
nullptr ||
663 this->pmc_4_0_sensor_ !=
nullptr || this->pmc_10_0_sensor_ !=
nullptr;
667 if (!this->
write_command(SEN6X_CMD_READ_NUMBER_CONCENTRATION)) {
669 ESP_LOGD(TAG,
"Read number concentration failed (%d)", this->
last_error_);
677 sensor::Sensor *sensors[] = {this->pmc_0_5_sensor_, this->pmc_1_0_sensor_, this->pmc_2_5_sensor_,
678 this->pmc_4_0_sensor_, this->pmc_10_0_sensor_};
679 uint16_t measurements[std::size(sensors)];
682 ESP_LOGD(TAG,
"Read data failed (%d)", this->
last_error_);
686 for (
size_t i = 0; i < std::size(sensors); i++) {
687 if (sensors[i] !=
nullptr)
688 sensors[i]->
publish_state(measurements[i] == 0xFFFF ? NAN : measurements[i] / 10.0f);
699 if (std::isnan(pressure_hpa))
702 if (pressure_hpa < AMBIENT_PRESSURE_MIN_HPA || pressure_hpa > AMBIENT_PRESSURE_MAX_HPA) {
704 ESP_LOGW(TAG,
"Ambient pressure out of range: %.0f hPa", pressure_hpa);
710 const auto value =
static_cast<uint16_t
>(lroundf(pressure_hpa));
734 this->
co2_wait_ = CO2_CONDITIONING_DELAY;
741void SEN6XComponent::start_measurement() {
746 ESP_LOGW(TAG,
"Device busy");
751 ESP_LOGW(TAG,
"Start measurement failed (%d)", this->
last_error_);
757void SEN6XComponent::stop_measurement() {
761 ESP_LOGW(TAG,
"Device busy");
768 ESP_LOGW(TAG,
"Stop measurement failed (%d)", this->
last_error_);
775void SEN6XComponent::start_fan_cleaning() {
780 ESP_LOGW(TAG,
"Fan cleaning requires idle mode; stop the measurement first");
784 ESP_LOGW(TAG,
"Device busy");
789 ESP_LOGW(TAG,
"Fan cleaning failed (%d)", this->
last_error_);
795void SEN6XComponent::activate_sht_heater() {
800 ESP_LOGW(TAG,
"SHT heater requires idle mode; stop the measurement first");
804 ESP_LOGW(TAG,
"Device busy");
809 ESP_LOGW(TAG,
"SHT heater failed (%d)", this->
last_error_);
816 if (product_name ==
"SEN62")
818 if (product_name ==
"SEN63C")
820 if (product_name ==
"SEN65")
822 if (product_name ==
"SEN66")
824 if (product_name ==
"SEN68")
826 if (product_name ==
"SEN69C")