[stcc4] Add component for the Sensirion STCC4 CO2 sensor (#19434)

Co-authored-by: will <will-tm@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
Jeff Brown
2026-10-01 15:17:48 -05:00
committed by GitHub
co-authored by will J. Nick Koston
parent 2d37c6f58b
commit 4075c15bea
11 changed files with 548 additions and 3 deletions
+1
View File
@@ -541,6 +541,7 @@ esphome/components/st7735/* @SenexCrenshaw
esphome/components/st7789v/* @kbx81
esphome/components/st7920/* @marsjan155
esphome/components/statsd/* @Links2004
esphome/components/stcc4/* @j9brown
esphome/components/stts22h/* @B48D81EFCC
esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter
@@ -65,14 +65,15 @@ bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len
}
bool SensirionI2CDevice::get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, const uint8_t len,
const uint8_t delay_ms) {
const uint8_t delay_ms, uint8_t sensirion_options) {
if (!this->write_command_(reg, command_len, nullptr, 0)) {
ESP_LOGE(TAG, "Write failed: reg=0x%X (%d) err=%d,", reg, command_len, this->last_error_);
return false;
}
delay(delay_ms);
bool result = this->read_data(data, len);
if (!result) {
if (!result &&
(this->last_error_ != i2c::ERROR_NOT_ACKNOWLEDGED || !(sensirion_options & SENSIRION_OPTION_READ_MAY_NACK))) {
ESP_LOGE(TAG, "Read failed: reg=0x%X err=%d,", reg, this->last_error_);
}
return result;
@@ -16,6 +16,9 @@ namespace esphome::sensirion_common {
*/
static const uint8_t CRC_POLYNOMIAL = 0x31; // default for Sensirion
/// When reading a register and the device reports NACK because the value is not ready yet, don't log it as an error.
static constexpr uint8_t SENSIRION_OPTION_READ_MAY_NACK = 1u << 0;
class SensirionI2CDevice : public i2c::I2CDevice {
public:
enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 };
@@ -139,9 +142,11 @@ class SensirionI2CDevice : public i2c::I2CDevice {
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the I2C command and reading the result
* @param sensirion_options options for the request
* @return true if reading succeeded
*/
bool get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, uint8_t delay);
bool get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, uint8_t delay,
uint8_t sensirion_options = 0);
/** last error code from I2C operation
*/
+141
View File
@@ -0,0 +1,141 @@
import esphome.codegen as cg
from esphome.components import i2c, sensirion_common, sensor
from esphome.components.const import CONF_HUMIDITY_SOURCE
import esphome.config_validation as cv
from esphome.const import (
CONF_AMBIENT_PRESSURE_COMPENSATION,
CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE,
CONF_CO2,
CONF_HUMIDITY,
CONF_ID,
CONF_MEASUREMENT_MODE,
CONF_TEMPERATURE,
CONF_TEMPERATURE_SOURCE,
CONF_UPDATE_INTERVAL,
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
ICON_MOLECULE_CO2,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PARTS_PER_MILLION,
UNIT_PERCENT,
)
from esphome.types import ConfigType
CODEOWNERS = ["@j9brown"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
stcc4_ns = cg.esphome_ns.namespace("stcc4")
STCC4Component = stcc4_ns.class_(
"STCC4Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
MeasurementMode = stcc4_ns.enum("MeasurementMode", is_class=True)
MEASUREMENT_MODE_OPTIONS = {
"continuous": MeasurementMode.CONTINUOUS,
"single_shot": MeasurementMode.SINGLE_SHOT,
}
def validate_config(config: ConfigType) -> ConfigType:
if config[CONF_MEASUREMENT_MODE] == "continuous":
if CONF_UPDATE_INTERVAL in config:
raise cv.Invalid(
"update_interval must not be specified in continuous measurement mode"
)
elif CONF_UPDATE_INTERVAL not in config:
config[CONF_UPDATE_INTERVAL] = cv.update_interval("60s")
return config
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(STCC4Component),
cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_MOLECULE_CO2,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Inclusive(CONF_TEMPERATURE_SOURCE, "rht_compensation"): cv.use_id(
sensor.Sensor
),
cv.Inclusive(CONF_HUMIDITY_SOURCE, "rht_compensation"): cv.use_id(
sensor.Sensor
),
cv.Exclusive(
CONF_AMBIENT_PRESSURE_COMPENSATION, "ambient_pressure_compensation"
): cv.All(cv.pressure, cv.float_range(min=0.4, max=1.1)),
cv.Exclusive(
CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE,
"ambient_pressure_compensation",
): cv.use_id(sensor.Sensor),
cv.Optional(CONF_MEASUREMENT_MODE, default="continuous"): cv.enum(
MEASUREMENT_MODE_OPTIONS, lower=True
),
cv.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x64))
.add_extra(validate_config)
)
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_CO2, var.set_co2_sensor)
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
if (temperature_source := config.get(CONF_TEMPERATURE_SOURCE)) is not None:
sens = await cg.get_variable(temperature_source)
cg.add(var.set_temperature_source(sens))
if (humidity_source := config.get(CONF_HUMIDITY_SOURCE)) is not None:
sens = await cg.get_variable(humidity_source)
cg.add(var.set_humidity_source(sens))
if (
ambient_pressure_compensation := config.get(CONF_AMBIENT_PRESSURE_COMPENSATION)
) is not None:
cg.add(
var.set_ambient_pressure_compensation(
ambient_pressure_compensation * 1000 # convert bar to hPa
)
)
if (
ambient_pressure_compensation_source := config.get(
CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE
)
) is not None:
sens = await cg.get_variable(ambient_pressure_compensation_source)
cg.add(var.set_ambient_pressure_source(sens))
cg.add(var.set_measurement_mode(config[CONF_MEASUREMENT_MODE]))
+294
View File
@@ -0,0 +1,294 @@
#include <cstdint>
#include "stcc4.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome::stcc4 {
static const char *const TAG = "stcc4";
// I2C Commands
static constexpr uint16_t STCC4_CMD_START_CONTINUOUS_MEASUREMENT = 0x218b;
static constexpr uint16_t STCC4_CMD_STOP_CONTINUOUS_MEASUREMENT = 0x3f86;
static constexpr uint16_t STCC4_CMD_MEASURE_SINGLE_SHOT = 0x219d;
static constexpr uint16_t STCC4_CMD_READ_MEASUREMENT = 0xec05;
static constexpr uint16_t STCC4_CMD_GET_PRODUCT_ID = 0x365b;
static constexpr uint16_t STCC4_CMD_SET_RHT_COMPENSATION = 0xe000;
static constexpr uint16_t STCC4_CMD_SET_PRESSURE_COMPENSATION = 0xe016;
// Exit sleep is an 8-bit command (single byte 0x00), not 16-bit
static constexpr uint8_t STCC4_CMD_EXIT_SLEEP_MODE = 0x00;
static constexpr uint32_t STCC4_PRODUCT_ID = 0x0901018a;
// Timeout for determining when the device is ready for use, in milliseconds.
// While waiting for the previous measurement to finish, the device will NACK all I2C requests
// and it can take up to 1200 ms for the operation to complete according to the datasheet.
static constexpr uint32_t READY_TIMEOUT_MS = 1200;
// Poll interval for determining when the device is ready for use, in milliseconds.
static constexpr uint32_t READY_POLL_INTERVAL_MS = 100;
// Convert units the the device's representation according to the datasheet.
constexpr uint16_t temperature_in_c_to_ticks(float temperature_in_c) {
return uint16_t((std::clamp(temperature_in_c, -45.f, 130.f) + 45.f) * 65535.f / 175.f);
}
constexpr float temperature_in_ticks_to_c(uint16_t temperature_in_ticks) {
return temperature_in_ticks * 175.f / 65535.f - 45.f;
}
constexpr uint16_t humidity_in_percent_to_ticks(float humidity_in_percent) {
return uint16_t((std::clamp(humidity_in_percent, 0.f, 100.f) + 6.f) * 65535.f / 125.f);
}
constexpr float humidity_in_ticks_to_percent(uint16_t humidity_in_ticks) {
return humidity_in_ticks * 125.f / 65535.f - 6.f;
}
constexpr uint16_t pressure_in_hpa_to_pa_2(float pressure_in_hpa) {
return uint16_t(std::clamp(pressure_in_hpa, 400.f, 1100.f) * 50.f);
}
constexpr float pressure_in_pa_2_to_hpa(uint16_t pressure_in_pa_2) { return pressure_in_pa_2 / 50.f; }
void STCC4Component::setup() {
this->stop_poller(); // not ready yet
// Wait 100 ms after power up before attempting to communicate with the sensor
this->set_timeout(100, [this]() {
// Send exit sleep mode command (8-bit, NACK expected), wait 5 ms to exit sleep
this->write_command(STCC4_CMD_EXIT_SLEEP_MODE);
this->set_timeout(5, [this]() { this->poll_until_ready_for_setup_or_timeout_(millis()); });
});
}
void STCC4Component::poll_until_ready_for_setup_or_timeout_(uint32_t start_time) {
// Stop continuous measurements in case they were previously running
// The device may NACK this request if it is not ready to communicate yet
if (this->write_command(STCC4_CMD_STOP_CONTINUOUS_MEASUREMENT)) {
// Read product ID to verify communication (6 words: 2 for product_id + 4 for serial)
// The device may NACK this request if it is not ready to communicate yet
uint16_t raw_product_id[6];
if (this->get_register(STCC4_CMD_GET_PRODUCT_ID, raw_product_id, 6, 1)) {
uint32_t product_id = (uint32_t(raw_product_id[0]) << 16) | raw_product_id[1];
uint64_t serial_number = (uint64_t(raw_product_id[2]) << 48) | (uint64_t(raw_product_id[3]) << 32) |
(uint64_t(raw_product_id[4]) << 16) | raw_product_id[5];
ESP_LOGD(TAG, "Product ID: 0x%08" PRIX32 ", Serial: 0x%016" PRIX64, product_id, serial_number);
if (product_id != STCC4_PRODUCT_ID) {
ESP_LOGE(TAG, "Unsupported product ID");
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
// Set static ambient pressure compensation if configured
if (this->ambient_pressure_in_pa_2_ != 0) {
if (!this->write_ambient_pressure_compensation_(this->ambient_pressure_in_pa_2_)) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
}
// Apply the dynamic compensation sources' current values, if configured
if (this->temperature_source_ != nullptr && this->humidity_source_ != nullptr) {
this->update_rht_compensation_from_source_();
}
if (this->ambient_pressure_source_ != nullptr) {
this->update_ambient_pressure_compensation_from_source_();
}
if (this->measurement_mode_ == MeasurementMode::SINGLE_SHOT) {
this->start_poller();
this->finish_setup_();
return;
}
// Start continuous measurement
if (!this->write_command(STCC4_CMD_START_CONTINUOUS_MEASUREMENT)) {
ESP_LOGE(TAG, "Failed to start continuous measurement");
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
this->schedule_continuous_update_(false);
this->finish_setup_();
return;
}
}
if (millis() - start_time < READY_TIMEOUT_MS) {
ESP_LOGVV(TAG, "Retry sync");
this->set_timeout(READY_POLL_INTERVAL_MS,
[this, start_time]() { this->poll_until_ready_for_setup_or_timeout_(start_time); });
return;
}
ESP_LOGE(TAG, "Failed to stop continuous measurements and read product ID");
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
}
void STCC4Component::finish_setup_() {
this->ready_ = true;
// Follow the sources only once measuring started, so a failed setup stops writing to the device
if (this->temperature_source_ != nullptr && this->humidity_source_ != nullptr) {
this->temperature_source_->add_on_state_callback([this](float) { this->update_rht_compensation_from_source_(); });
this->humidity_source_->add_on_state_callback([this](float) { this->update_rht_compensation_from_source_(); });
}
if (this->ambient_pressure_source_ != nullptr) {
this->ambient_pressure_source_->add_on_state_callback(
[this](float) { this->update_ambient_pressure_compensation_from_source_(); });
}
}
void STCC4Component::dump_config() {
ESP_LOGCONFIG(TAG, "STCC4:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
}
ESP_LOGCONFIG(TAG, " Measurement mode: %s",
this->measurement_mode_ == MeasurementMode::CONTINUOUS ? LOG_STR_LITERAL("Continuous (1s)")
: LOG_STR_LITERAL("Single shot"));
if (this->ambient_pressure_source_ != nullptr) {
ESP_LOGCONFIG(TAG, " Dynamic ambient pressure compensation using '%s'",
this->ambient_pressure_source_->get_name().c_str());
} else if (this->ambient_pressure_in_pa_2_ != 0) {
ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %f hPa",
pressure_in_pa_2_to_hpa(this->ambient_pressure_in_pa_2_));
}
if (this->temperature_source_ != nullptr) {
ESP_LOGCONFIG(TAG, " Temperature compensation using '%s'", this->temperature_source_->get_name().c_str());
}
if (this->humidity_source_ != nullptr) {
ESP_LOGCONFIG(TAG, " Humidity compensation using '%s'", this->humidity_source_->get_name().c_str());
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
void STCC4Component::update() {
if (!this->ready_ || this->measurement_mode_ != MeasurementMode::SINGLE_SHOT)
return;
// Perform single-shot measurement, wait 500 ms for the measurement to be ready
if (!this->write_command(STCC4_CMD_MEASURE_SINGLE_SHOT)) {
ESP_LOGW(TAG, "Failed to start single shot measurement");
this->status_set_warning();
return;
}
this->set_timeout(500, [this]() {
if (this->read_measurement_(0)) {
this->status_clear_warning();
} else {
ESP_LOGW(TAG, "Failed to read measurement data");
this->status_set_warning();
}
});
}
void STCC4Component::schedule_continuous_update_(bool retry_for_clock_drift) {
// In continuous measurement mode, the STCC4 produces a sample every 1000 ms according to its
// internal clock. The datasheet recommends retrying 150 ms after a failed read to compensate
// for clock drift between the host and the device.
this->set_timeout(retry_for_clock_drift ? 150 : 1000, [this, retry_for_clock_drift]() {
if (this->read_measurement_(retry_for_clock_drift ? 0 : sensirion_common::SENSIRION_OPTION_READ_MAY_NACK)) {
this->status_clear_warning();
this->schedule_continuous_update_(false);
} else if (!retry_for_clock_drift) {
this->schedule_continuous_update_(true);
} else {
this->status_set_warning();
this->schedule_continuous_update_(false);
}
});
}
bool STCC4Component::read_measurement_(uint8_t sensirion_options) {
// Read measurement data: 4 words (CO2, temperature, humidity, status)
uint16_t raw_data[4];
if (!this->get_register_(STCC4_CMD_READ_MEASUREMENT, ADDR_16_BIT, raw_data, 4, 1, sensirion_options)) {
return false;
}
// CO2 value is in ppm as int16 (ignore negative values during warm-up)
const int16_t co2_raw = int16_t(raw_data[0]);
if (this->co2_sensor_ != nullptr && co2_raw >= 0) {
this->co2_sensor_->publish_state(co2_raw);
}
if (this->temperature_sensor_ != nullptr) {
this->temperature_sensor_->publish_state(temperature_in_ticks_to_c(raw_data[1]));
}
if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->publish_state(humidity_in_ticks_to_percent(raw_data[2]));
}
return true;
}
void STCC4Component::update_rht_compensation_from_source_() {
const float temperature_in_c = this->temperature_source_->state;
const float humidity_in_percent = this->humidity_source_->state;
if (std::isnan(temperature_in_c) || std::isnan(humidity_in_percent))
return;
const uint16_t temperature_in_ticks = temperature_in_c_to_ticks(temperature_in_c);
const uint16_t humidity_in_ticks = humidity_in_percent_to_ticks(humidity_in_percent);
if ((this->temperature_in_ticks_ != temperature_in_ticks || this->humidity_in_ticks_ != humidity_in_ticks) &&
this->write_rht_compensation_(temperature_in_ticks, humidity_in_ticks)) {
this->temperature_in_ticks_ = temperature_in_ticks;
this->humidity_in_ticks_ = humidity_in_ticks;
}
}
void STCC4Component::set_ambient_pressure_compensation(float pressure_in_hpa) {
this->ambient_pressure_in_pa_2_ = pressure_in_hpa_to_pa_2(pressure_in_hpa);
}
void STCC4Component::update_ambient_pressure_compensation_from_source_() {
const float pressure_in_hpa = this->ambient_pressure_source_->state;
if (std::isnan(pressure_in_hpa))
return;
if (pressure_in_hpa < 100.f || pressure_in_hpa > 10000.f) {
// Some pressure sensors report values in Pa instead of hPa and there's no way to check at compile time.
// Warn if the value seems far outside of the expected range.
if (!this->ambient_pressure_unit_warning_logged_) {
this->ambient_pressure_unit_warning_logged_ = true;
ESP_LOGW(TAG, "Ambient pressure compensation sensor might have incompatible units: got %f hPa", pressure_in_hpa);
}
return; // skip this update
} else {
this->ambient_pressure_unit_warning_logged_ = false;
}
const uint16_t ambient_pressure_in_pa_2 = pressure_in_hpa_to_pa_2(pressure_in_hpa);
if (this->ambient_pressure_in_pa_2_ != ambient_pressure_in_pa_2 &&
this->write_ambient_pressure_compensation_(ambient_pressure_in_pa_2)) {
this->ambient_pressure_in_pa_2_ = ambient_pressure_in_pa_2;
}
}
bool STCC4Component::write_rht_compensation_(uint16_t temperature_in_ticks, uint16_t humidity_in_ticks) {
ESP_LOGVV(TAG, "Set RHT compensation: %f °C, %f %%RH", temperature_in_ticks_to_c(temperature_in_ticks),
humidity_in_ticks_to_percent(humidity_in_ticks));
const uint16_t data[2] = {temperature_in_ticks, humidity_in_ticks};
if (!this->write_command(STCC4_CMD_SET_RHT_COMPENSATION, data, 2)) {
ESP_LOGE(TAG, "Failed to set RHT compensation");
return false;
}
return true;
}
bool STCC4Component::write_ambient_pressure_compensation_(uint16_t pressure_in_pa_2) {
ESP_LOGVV(TAG, "Set pressure compensation: %f hPa", pressure_in_pa_2_to_hpa(pressure_in_pa_2));
if (!this->write_command(STCC4_CMD_SET_PRESSURE_COMPENSATION, pressure_in_pa_2)) {
ESP_LOGE(TAG, "Failed to set ambient pressure compensation");
return false;
}
return true;
}
} // namespace esphome::stcc4
+55
View File
@@ -0,0 +1,55 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome::stcc4 {
enum class MeasurementMode : uint8_t {
CONTINUOUS = 0,
SINGLE_SHOT,
};
class STCC4Component final : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
void setup() override;
void dump_config() override;
void update() override;
void set_co2_sensor(sensor::Sensor *co2) { this->co2_sensor_ = co2; }
void set_temperature_sensor(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; }
void set_humidity_sensor(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; }
void set_temperature_source(sensor::Sensor *temperature) { this->temperature_source_ = temperature; }
void set_humidity_source(sensor::Sensor *humidity) { this->humidity_source_ = humidity; }
void set_ambient_pressure_compensation(float pressure_in_hpa);
void set_ambient_pressure_source(sensor::Sensor *pressure) { this->ambient_pressure_source_ = pressure; }
void set_measurement_mode(MeasurementMode mode) { this->measurement_mode_ = mode; }
protected:
void poll_until_ready_for_setup_or_timeout_(uint32_t start_time);
void finish_setup_();
void schedule_continuous_update_(bool retry_for_clock_drift);
bool read_measurement_(uint8_t sensirion_options);
void update_rht_compensation_from_source_();
void update_ambient_pressure_compensation_from_source_();
bool write_rht_compensation_(uint16_t temperature_in_ticks, uint16_t humidity_in_ticks);
bool write_ambient_pressure_compensation_(uint16_t pressure_in_pa_2);
sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *temperature_source_{nullptr};
sensor::Sensor *humidity_source_{nullptr};
sensor::Sensor *ambient_pressure_source_{nullptr};
MeasurementMode measurement_mode_{MeasurementMode::CONTINUOUS};
uint16_t temperature_in_ticks_{0};
uint16_t humidity_in_ticks_{0};
uint16_t ambient_pressure_in_pa_2_{0};
bool ambient_pressure_unit_warning_logged_{false};
bool ready_{false};
};
} // namespace esphome::stcc4
+36
View File
@@ -0,0 +1,36 @@
sensor:
# SHT4x for external RHT compensation (boards without onboard SHT4x)
- platform: sht4x
i2c_id: i2c_bus
temperature:
name: SHT4x Temperature
id: sht4x_temperature
humidity:
name: SHT4x Humidity
id: sht4x_humidity
- platform: template
id: ambient_pressure
unit_of_measurement: "hPa"
lambda: 'return 1013;'
# STCC4 CO2 sensor with onboard temperature/humidity and external compensation
- platform: stcc4
i2c_id: i2c_bus
id: stcc4_sensor_single_shot
co2:
name: STCC4 CO2
temperature:
name: STCC4 Temperature
humidity:
name: STCC4 Humidity
temperature_source: sht4x_temperature
humidity_source: sht4x_humidity
measurement_mode: single_shot
ambient_pressure_compensation: 1013 mbar
update_interval: 60s
- platform: stcc4
i2c_id: i2c_bus
address: 0x65
id: stcc4_sensor_continuous
measurement_mode: continuous
ambient_pressure_compensation_source: ambient_pressure
@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
<<: !include common.yaml
@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
<<: !include common.yaml
@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
<<: !include common.yaml