Merge branch 'esphome:dev' into feature-max6921

This commit is contained in:
endym 2024-08-12 12:15:14 +02:00 committed by GitHub
commit b83cedfcae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 1389 additions and 90 deletions

View file

@ -46,7 +46,7 @@ runs:
- name: Build and push to ghcr by digest - name: Build and push to ghcr by digest
id: build-ghcr id: build-ghcr
uses: docker/build-push-action@v6.5.0 uses: docker/build-push-action@v6.6.1
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
@ -69,7 +69,7 @@ runs:
- name: Build and push to dockerhub by digest - name: Build and push to dockerhub by digest
id: build-dockerhub id: build-dockerhub
uses: docker/build-push-action@v6.5.0 uses: docker/build-push-action@v6.6.1
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile

View file

@ -65,6 +65,8 @@ esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme280_base/* @esphome/core esphome/components/bme280_base/* @esphome/core
esphome/components/bme280_spi/* @apbodrov esphome/components/bme280_spi/* @apbodrov
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bme68x_bsec2/* @kbx81 @neffs
esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs
esphome/components/bmi160/* @flaviut esphome/components/bmi160/* @flaviut
esphome/components/bmp3xx/* @latonita esphome/components/bmp3xx/* @latonita
esphome/components/bmp3xx_base/* @latonita @martgras esphome/components/bmp3xx_base/* @latonita @martgras

View file

@ -1336,7 +1336,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
update->check(); update->check();
break; break;
default: default:
ESP_LOGW(TAG, "Unknown update command: %d", msg.command); ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
break; break;
} }
} }

View file

@ -0,0 +1,196 @@
import hashlib
from pathlib import Path
from esphome import core, external_files
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MODEL,
CONF_RAW_DATA_ID,
CONF_SAMPLE_RATE,
CONF_TEMPERATURE_OFFSET,
)
CODEOWNERS = ["@neffs", "@kbx81"]
DOMAIN = "bme68x_bsec2"
BSEC2_LIBRARY_VERSION = "v1.7.2502"
CONF_ALGORITHM_OUTPUT = "algorithm_output"
CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id"
CONF_IAQ_MODE = "iaq_mode"
CONF_OPERATING_AGE = "operating_age"
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
CONF_SUPPLY_VOLTAGE = "supply_voltage"
bme68x_bsec2_ns = cg.esphome_ns.namespace("bme68x_bsec2")
BME68xBSEC2Component = bme68x_bsec2_ns.class_("BME68xBSEC2Component", cg.Component)
MODEL_OPTIONS = ["bme680", "bme688"]
AlgorithmOutput = bme68x_bsec2_ns.enum("AlgorithmOutput")
ALGORITHM_OUTPUT_OPTIONS = {
"classification": AlgorithmOutput.ALGORITHM_OUTPUT_CLASSIFICATION,
"regression": AlgorithmOutput.ALGORITHM_OUTPUT_REGRESSION,
}
OperatingAge = bme68x_bsec2_ns.enum("OperatingAge")
OPERATING_AGE_OPTIONS = {
"4d": OperatingAge.OPERATING_AGE_4D,
"28d": OperatingAge.OPERATING_AGE_28D,
}
SampleRate = bme68x_bsec2_ns.enum("SampleRate")
SAMPLE_RATE_OPTIONS = {
"LP": SampleRate.SAMPLE_RATE_LP,
"ULP": SampleRate.SAMPLE_RATE_ULP,
}
Voltage = bme68x_bsec2_ns.enum("Voltage")
VOLTAGE_OPTIONS = {
"1.8V": Voltage.VOLTAGE_1_8V,
"3.3V": Voltage.VOLTAGE_3_3V,
}
ALGORITHM_OUTPUT_FILE_NAME = {
"classification": "sel",
"regression": "reg",
}
SAMPLE_RATE_FILE_NAME = {
"LP": "3s",
"ULP": "300s",
}
VOLTAGE_FILE_NAME = {
"1.8V": "18v",
"3.3V": "33v",
}
def _compute_local_file_path(url: str) -> Path:
h = hashlib.new("sha256")
h.update(url.encode())
key = h.hexdigest()[:8]
base_dir = external_files.compute_local_file_dir(DOMAIN)
return base_dir / key
def _compute_url(config: dict) -> str:
model = config.get(CONF_MODEL)
operating_age = config.get(CONF_OPERATING_AGE)
sample_rate = SAMPLE_RATE_FILE_NAME[config.get(CONF_SAMPLE_RATE)]
volts = VOLTAGE_FILE_NAME[config.get(CONF_SUPPLY_VOLTAGE)]
if model == "bme688":
algo = ALGORITHM_OUTPUT_FILE_NAME[
config.get(CONF_ALGORITHM_OUTPUT, "classification")
]
filename = "bsec_selectivity"
else:
algo = "iaq"
filename = "bsec_iaq"
return f"https://raw.githubusercontent.com/boschsensortec/Bosch-BSEC2-Library/{BSEC2_LIBRARY_VERSION}/src/config/{model}/{model}_{algo}_{volts}_{sample_rate}_{operating_age}/{filename}.txt"
def download_bme68x_blob(config):
url = _compute_url(config)
path = _compute_local_file_path(url)
external_files.download_content(url, path)
return config
def validate_bme68x(config):
if CONF_ALGORITHM_OUTPUT not in config:
return config
if config[CONF_MODEL] != "bme688":
raise cv.Invalid(f"{CONF_ALGORITHM_OUTPUT} is only valid for BME688")
if config[CONF_ALGORITHM_OUTPUT] == "regression" and (
config[CONF_OPERATING_AGE] != "4d"
or config[CONF_SAMPLE_RATE] != "ULP"
or config[CONF_SUPPLY_VOLTAGE] != "1.8V"
):
raise cv.Invalid(
f" To use '{CONF_ALGORITHM_OUTPUT}: regression', {CONF_OPERATING_AGE} must be '4d', {CONF_SAMPLE_RATE} must be 'ULP' and {CONF_SUPPLY_VOLTAGE} must be '1.8V'"
)
return config
CONFIG_SCHEMA_BASE = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BME68xBSEC2Component),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
cv.Required(CONF_MODEL): cv.one_of(*MODEL_OPTIONS, lower=True),
cv.Optional(CONF_ALGORITHM_OUTPUT): cv.enum(
ALGORITHM_OUTPUT_OPTIONS, lower=True
),
cv.Optional(CONF_OPERATING_AGE, default="28d"): cv.enum(
OPERATING_AGE_OPTIONS, lower=True
),
cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
SAMPLE_RATE_OPTIONS, upper=True
),
cv.Optional(CONF_SUPPLY_VOLTAGE, default="3.3V"): cv.enum(
VOLTAGE_OPTIONS, upper=True
),
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
cv.Optional(
CONF_STATE_SAVE_INTERVAL, default="6hours"
): cv.positive_time_period_minutes,
},
)
.add_extra(cv.only_with_arduino)
.add_extra(validate_bme68x)
.add_extra(download_bme68x_blob)
)
async def to_code_base(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if algo_output := config.get(CONF_ALGORITHM_OUTPUT):
cg.add(var.set_algorithm_output(algo_output))
cg.add(var.set_operating_age(config[CONF_OPERATING_AGE]))
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
cg.add(var.set_voltage(config[CONF_SUPPLY_VOLTAGE]))
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
cg.add(
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
)
path = _compute_local_file_path(_compute_url(config))
try:
with open(path, encoding="utf-8") as f:
bsec2_iaq_config = f.read()
except Exception as e:
raise core.EsphomeError(f"Could not open binary configuration file {path}: {e}")
# Convert retrieved BSEC2 config to an array of ints
rhs = [int(x) for x in bsec2_iaq_config.split(",")]
# Create an array which will reside in program memory and configure the sensor instance to use it
bsec2_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.add(var.set_bsec2_configuration(bsec2_arr, len(rhs)))
# Although this component does not use SPI, the BSEC2 library requires the SPI library
cg.add_library("SPI", None)
cg.add_library(
"BME68x Sensor library",
"1.1.40407",
)
cg.add_library(
"BSEC2 Software Library",
None,
f"https://github.com/boschsensortec/Bosch-BSEC2-Library.git#{BSEC2_LIBRARY_VERSION}",
)
cg.add_define("USE_BSEC2")
return var

View file

@ -0,0 +1,523 @@
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_BSEC2
#include "bme68x_bsec2.h"
#include <string>
namespace esphome {
namespace bme68x_bsec2 {
#define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression")
#define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days")
#define BME68X_BSEC2_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP"))
#define BME68X_BSEC2_VOLTAGE_LOG(v) (v == VOLTAGE_3_3V ? "3.3V" : "1.8V")
static const char *const TAG = "bme68x_bsec2.sensor";
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
void BME68xBSEC2Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up BME68X via BSEC2...");
this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
ESP_LOGE(TAG, "bsec_init_m failed: status %d", this->bsec_status_);
return;
}
bsec_get_version_m(&this->bsec_instance_, &this->version_);
this->bme68x_status_ = bme68x_init(&this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
this->mark_failed();
ESP_LOGE(TAG, "bme68x_init failed: status %d", this->bme68x_status_);
return;
}
if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) {
this->set_config_(this->bsec2_configuration_, this->bsec2_configuration_length_);
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
ESP_LOGE(TAG, "bsec_set_configuration_m failed: status %d", this->bsec_status_);
return;
}
}
this->update_subscription_();
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
ESP_LOGE(TAG, "bsec_update_subscription_m failed: status %d", this->bsec_status_);
return;
}
this->load_state_();
}
void BME68xBSEC2Component::dump_config() {
ESP_LOGCONFIG(TAG, "BME68X via BSEC2:");
ESP_LOGCONFIG(TAG, " BSEC2 version: %d.%d.%d.%d", this->version_.major, this->version_.minor,
this->version_.major_bugfix, this->version_.minor_bugfix);
ESP_LOGCONFIG(TAG, " BSEC2 configuration blob:");
ESP_LOGCONFIG(TAG, " Configured: %s", YESNO(this->bsec2_blob_configured_));
if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) {
ESP_LOGCONFIG(TAG, " Size: %" PRIu32, this->bsec2_configuration_length_);
}
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication failed (BSEC2 status: %d, BME68X status: %d)", this->bsec_status_,
this->bme68x_status_);
}
if (this->algorithm_output_ != ALGORITHM_OUTPUT_IAQ) {
ESP_LOGCONFIG(TAG, " Algorithm output: %s", BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(this->algorithm_output_));
}
ESP_LOGCONFIG(TAG, " Operating age: %s", BME68X_BSEC2_OPERATING_AGE_LOG(this->operating_age_));
ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->sample_rate_));
ESP_LOGCONFIG(TAG, " Voltage: %s", BME68X_BSEC2_VOLTAGE_LOG(this->voltage_));
ESP_LOGCONFIG(TAG, " State save interval: %ims", this->state_save_interval_ms_);
ESP_LOGCONFIG(TAG, " Temperature offset: %.2f", this->temperature_offset_);
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->temperature_sample_rate_));
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->pressure_sample_rate_));
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->humidity_sample_rate_));
LOG_SENSOR(" ", "Gas resistance", this->gas_resistance_sensor_);
LOG_SENSOR(" ", "CO2 equivalent", this->co2_equivalent_sensor_);
LOG_SENSOR(" ", "Breath VOC equivalent", this->breath_voc_equivalent_sensor_);
LOG_SENSOR(" ", "IAQ", this->iaq_sensor_);
LOG_SENSOR(" ", "IAQ static", this->iaq_static_sensor_);
LOG_SENSOR(" ", "Numeric IAQ accuracy", this->iaq_accuracy_sensor_);
#endif
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "IAQ accuracy", this->iaq_accuracy_text_sensor_);
#endif
}
float BME68xBSEC2Component::get_setup_priority() const { return setup_priority::DATA; }
void BME68xBSEC2Component::loop() {
this->run_();
if (this->bsec_status_ < BSEC_OK || this->bme68x_status_ < BME68X_OK) {
this->status_set_error();
} else {
this->status_clear_error();
}
if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) {
this->status_set_warning();
} else {
this->status_clear_warning();
}
// Process a single action from the queue. These are primarily sensor state publishes
// that in totality take too long to send in a single call.
if (this->queue_.size()) {
auto action = std::move(this->queue_.front());
this->queue_.pop();
action();
}
}
void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) {
if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) {
ESP_LOGE(TAG, "Configuration is larger than BSEC_MAX_PROPERTY_BLOB_SIZE");
this->mark_failed();
return;
}
uint8_t work_buffer[BSEC_MAX_PROPERTY_BLOB_SIZE];
this->bsec_status_ = bsec_set_configuration_m(&this->bsec_instance_, config, len, work_buffer, sizeof(work_buffer));
if (this->bsec_status_ == BSEC_OK) {
this->bsec2_blob_configured_ = true;
}
}
float BME68xBSEC2Component::calc_sensor_sample_rate_(SampleRate sample_rate) {
if (sample_rate == SAMPLE_RATE_DEFAULT) {
sample_rate = this->sample_rate_;
}
return sample_rate == SAMPLE_RATE_ULP ? BSEC_SAMPLE_RATE_ULP : BSEC_SAMPLE_RATE_LP;
}
void BME68xBSEC2Component::update_subscription_() {
bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS];
uint8_t num_virtual_sensors = 0;
#ifdef USE_SENSOR
if (this->iaq_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_IAQ;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->iaq_static_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_STATIC_IAQ;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->co2_equivalent_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->breath_voc_equivalent_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->pressure_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->pressure_sample_rate_);
num_virtual_sensors++;
}
if (this->gas_resistance_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->temperature_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->temperature_sample_rate_);
num_virtual_sensors++;
}
if (this->humidity_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->humidity_sample_rate_);
num_virtual_sensors++;
}
#endif
bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
this->bsec_status_ = bsec_update_subscription_m(&this->bsec_instance_, virtual_sensors, num_virtual_sensors,
sensor_settings, &num_sensor_settings);
}
void BME68xBSEC2Component::run_() {
int64_t curr_time_ns = this->get_time_ns_();
if (curr_time_ns < this->next_call_ns_) {
return;
}
this->op_mode_ = this->bsec_settings_.op_mode;
uint8_t status;
ESP_LOGV(TAG, "Performing sensor run");
struct bme68x_conf bme68x_conf;
this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_);
if (this->bsec_status_ < BSEC_OK) {
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_);
return;
}
this->next_call_ns_ = this->bsec_settings_.next_call;
if (this->bsec_settings_.trigger_measurement) {
bme68x_get_conf(&bme68x_conf, &this->bme68x_);
bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling;
bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling;
bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling;
bme68x_set_conf(&bme68x_conf, &this->bme68x_);
switch (this->bsec_settings_.op_mode) {
case BME68X_FORCED_MODE:
this->bme68x_heatr_conf_.enable = BME68X_ENABLE;
this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature;
this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration;
status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
this->op_mode_ = BME68X_FORCED_MODE;
this->sleep_mode_ = false;
ESP_LOGV(TAG, "Using forced mode");
break;
case BME68X_PARALLEL_MODE:
if (this->op_mode_ != this->bsec_settings_.op_mode) {
this->bme68x_heatr_conf_.enable = BME68X_ENABLE;
this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile;
this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile;
this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len;
this->bme68x_heatr_conf_.shared_heatr_dur =
BSEC_TOTAL_HEAT_DUR -
(bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000));
status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
this->op_mode_ = BME68X_PARALLEL_MODE;
this->sleep_mode_ = false;
ESP_LOGV(TAG, "Using parallel mode");
}
break;
case BME68X_SLEEP_MODE:
if (!this->sleep_mode_) {
bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_);
this->sleep_mode_ = true;
ESP_LOGV(TAG, "Using sleep mode");
}
break;
}
uint32_t meas_dur = 0;
meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
ESP_LOGV(TAG, "Queueing read in %uus", meas_dur);
this->set_timeout("read", meas_dur / 1000, [this, curr_time_ns]() { this->read_(curr_time_ns); });
} else {
ESP_LOGV(TAG, "Measurement not required");
this->read_(curr_time_ns);
}
}
void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
ESP_LOGV(TAG, "Reading data");
if (this->bsec_settings_.trigger_measurement) {
uint8_t current_op_mode;
this->bme68x_status_ = bme68x_get_op_mode(&current_op_mode, &this->bme68x_);
if (current_op_mode == BME68X_SLEEP_MODE) {
ESP_LOGV(TAG, "Still in sleep mode, doing nothing");
return;
}
}
if (!this->bsec_settings_.process_data) {
ESP_LOGV(TAG, "Data processing not required");
return;
}
struct bme68x_data data[3];
uint8_t nFields = 0;
this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
ESP_LOGW(TAG, "Failed to get sensor data (BME68X error code %d)", this->bme68x_status_);
return;
}
if (nFields < 1) {
ESP_LOGD(TAG, "BME68X did not provide new data");
return;
}
for (uint8_t i = 0; i < nFields; i++) {
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
uint8_t num_inputs = 0;
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_TEMPERATURE)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
inputs[num_inputs].signal = data[i].temperature;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HEATSOURCE)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
inputs[num_inputs].signal = this->temperature_offset_;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HUMIDITY)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
inputs[num_inputs].signal = data[i].humidity;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PRESSURE)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
inputs[num_inputs].signal = data[i].pressure;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_GASRESISTOR)) {
if (data[i].status & BME68X_GASM_VALID_MSK) {
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
inputs[num_inputs].signal = data[i].gas_resistance;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
} else {
ESP_LOGD(TAG, "BME68X did not report gas data");
}
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PROFILE_PART) &&
(data[i].status & BME68X_GASM_VALID_MSK)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_PROFILE_PART;
inputs[num_inputs].signal = (this->op_mode_ == BME68X_FORCED_MODE) ? 0 : data[i].gas_index;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (num_inputs < 1) {
ESP_LOGD(TAG, "No signal inputs available for BSEC2");
return;
}
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "BSEC2 failed to process signals (BSEC2 error code %d)", this->bsec_status_);
return;
}
if (num_outputs < 1) {
ESP_LOGD(TAG, "No signal outputs provided by BSEC2");
return;
}
this->publish_(outputs, num_outputs);
}
}
void BME68xBSEC2Component::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
ESP_LOGV(TAG, "Publishing sensor states");
bool update_accuracy = false;
uint8_t max_accuracy = 0;
for (uint8_t i = 0; i < num_outputs; i++) {
float signal = outputs[i].signal;
switch (outputs[i].sensor_id) {
case BSEC_OUTPUT_IAQ:
max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
update_accuracy = true;
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_STATIC_IAQ:
max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
update_accuracy = true;
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_static_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_CO2_EQUIVALENT:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->co2_equivalent_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->breath_voc_equivalent_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_RAW_PRESSURE:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->pressure_sensor_, signal / 100.0f); });
#endif
break;
case BSEC_OUTPUT_RAW_GAS:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->gas_resistance_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->temperature_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->humidity_sensor_, signal); });
#endif
break;
}
}
if (update_accuracy) {
#ifdef USE_SENSOR
this->queue_push_(
[this, max_accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, max_accuracy, true); });
#endif
#ifdef USE_TEXT_SENSOR
this->queue_push_([this, max_accuracy]() {
this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[max_accuracy]);
});
#endif
// Queue up an opportunity to save state
this->queue_push_([this, max_accuracy]() { this->save_state_(max_accuracy); });
}
}
int64_t BME68xBSEC2Component::get_time_ns_() {
int64_t time_ms = millis();
if (this->last_time_ms_ > time_ms) {
this->millis_overflow_counter_++;
}
this->last_time_ms_ = time_ms;
return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000);
}
#ifdef USE_SENSOR
void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value, bool change_only) {
if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) {
return;
}
sensor->publish_state(value);
}
#endif
#ifdef USE_TEXT_SENSOR
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
if (!sensor || (sensor->has_state() && sensor->state == value)) {
return;
}
sensor->publish_state(value);
}
#endif
void BME68xBSEC2Component::load_state_() {
uint32_t hash = this->get_hash();
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
if (this->bsec_state_.load(&state)) {
ESP_LOGV(TAG, "Loading state");
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
this->bsec_status_ =
bsec_set_state_m(&this->bsec_instance_, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "Failed to load state (BSEC2 error code %d)", this->bsec_status_);
}
ESP_LOGI(TAG, "Loaded state");
}
}
void BME68xBSEC2Component::save_state_(uint8_t accuracy) {
if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
return;
}
ESP_LOGV(TAG, "Saving state");
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
this->bsec_status_ = bsec_get_state_m(&this->bsec_instance_, 0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer,
BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "Failed fetch state for save (BSEC2 error code %d)", this->bsec_status_);
return;
}
if (!this->bsec_state_.save(&state)) {
ESP_LOGW(TAG, "Failed to save state");
return;
}
this->last_state_save_ms_ = millis();
ESP_LOGI(TAG, "Saved state");
}
} // namespace bme68x_bsec2
} // namespace esphome
#endif

View file

@ -0,0 +1,163 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/preferences.h"
#ifdef USE_BSEC2
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#include <cinttypes>
#include <queue>
#include <bsec2.h>
namespace esphome {
namespace bme68x_bsec2 {
enum AlgorithmOutput {
ALGORITHM_OUTPUT_IAQ,
ALGORITHM_OUTPUT_CLASSIFICATION,
ALGORITHM_OUTPUT_REGRESSION,
};
enum OperatingAge {
OPERATING_AGE_4D,
OPERATING_AGE_28D,
};
enum SampleRate {
SAMPLE_RATE_LP = 0,
SAMPLE_RATE_ULP = 1,
SAMPLE_RATE_DEFAULT = 2,
};
enum Voltage {
VOLTAGE_1_8V,
VOLTAGE_3_3V,
};
class BME68xBSEC2Component : public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
void set_algorithm_output(AlgorithmOutput algorithm_output) { this->algorithm_output_ = algorithm_output; }
void set_operating_age(OperatingAge operating_age) { this->operating_age_ = operating_age; }
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
void set_voltage(Voltage voltage) { this->voltage_ = voltage; }
void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; }
void set_temperature_sample_rate(SampleRate sample_rate) { this->temperature_sample_rate_ = sample_rate; }
void set_pressure_sample_rate(SampleRate sample_rate) { this->pressure_sample_rate_ = sample_rate; }
void set_humidity_sample_rate(SampleRate sample_rate) { this->humidity_sample_rate_ = sample_rate; }
void set_bsec2_configuration(const uint8_t *data, const uint32_t len) {
this->bsec2_configuration_ = data;
this->bsec2_configuration_length_ = len;
}
void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
#ifdef USE_SENSOR
void set_temperature_sensor(sensor::Sensor *sensor) { this->temperature_sensor_ = sensor; }
void set_pressure_sensor(sensor::Sensor *sensor) { this->pressure_sensor_ = sensor; }
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
void set_gas_resistance_sensor(sensor::Sensor *sensor) { this->gas_resistance_sensor_ = sensor; }
void set_iaq_sensor(sensor::Sensor *sensor) { this->iaq_sensor_ = sensor; }
void set_iaq_static_sensor(sensor::Sensor *sensor) { this->iaq_static_sensor_ = sensor; }
void set_iaq_accuracy_sensor(sensor::Sensor *sensor) { this->iaq_accuracy_sensor_ = sensor; }
void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; }
void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
#endif
#ifdef USE_TEXT_SENSOR
void set_iaq_accuracy_text_sensor(text_sensor::TextSensor *sensor) { this->iaq_accuracy_text_sensor_ = sensor; }
#endif
virtual uint32_t get_hash() = 0;
protected:
void set_config_(const uint8_t *config, u_int32_t len);
float calc_sensor_sample_rate_(SampleRate sample_rate);
void update_subscription_();
void run_();
void read_(int64_t trigger_time_ns);
void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
int64_t get_time_ns_();
#ifdef USE_SENSOR
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
#endif
#ifdef USE_TEXT_SENSOR
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
#endif
void load_state_();
void save_state_(uint8_t accuracy);
void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
struct bme68x_dev bme68x_;
bsec_bme_settings_t bsec_settings_;
bsec_version_t version_;
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
struct bme68x_heatr_conf bme68x_heatr_conf_;
uint8_t op_mode_; // operating mode of sensor
bool sleep_mode_;
bsec_library_return_t bsec_status_{BSEC_OK};
int8_t bme68x_status_{BME68X_OK};
int64_t last_time_ms_{0};
uint32_t millis_overflow_counter_{0};
int64_t next_call_ns_{0};
std::queue<std::function<void()>> queue_;
uint8_t const *bsec2_configuration_{nullptr};
uint32_t bsec2_configuration_length_{0};
bool bsec2_blob_configured_{false};
ESPPreferenceObject bsec_state_;
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
uint32_t last_state_save_ms_ = 0;
float temperature_offset_{0};
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
OperatingAge operating_age_{OPERATING_AGE_28D};
Voltage voltage_{VOLTAGE_3_3V};
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
#ifdef USE_SENSOR
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *gas_resistance_sensor_{nullptr};
sensor::Sensor *iaq_sensor_{nullptr};
sensor::Sensor *iaq_static_sensor_{nullptr};
sensor::Sensor *iaq_accuracy_sensor_{nullptr};
sensor::Sensor *co2_equivalent_sensor_{nullptr};
sensor::Sensor *breath_voc_equivalent_sensor_{nullptr};
#endif
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr};
#endif
};
} // namespace bme68x_bsec2
} // namespace esphome
#endif

View file

@ -0,0 +1,130 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_GAS_RESISTANCE,
CONF_HUMIDITY,
CONF_IAQ_ACCURACY,
CONF_PRESSURE,
CONF_SAMPLE_RATE,
CONF_TEMPERATURE,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
ICON_GAS_CYLINDER,
ICON_GAUGE,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_OHM,
UNIT_PARTS_PER_MILLION,
UNIT_PERCENT,
)
from . import CONF_BME68X_BSEC2_ID, SAMPLE_RATE_OPTIONS, BME68xBSEC2Component
DEPENDENCIES = ["bme68x_bsec2"]
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_IAQ = "iaq"
CONF_IAQ_STATIC = "iaq_static"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
ICON_TEST_TUBE = "mdi:test-tube"
UNIT_IAQ = "IAQ"
TYPES = [
CONF_TEMPERATURE,
CONF_PRESSURE,
CONF_HUMIDITY,
CONF_GAS_RESISTANCE,
CONF_IAQ,
CONF_IAQ_STATIC,
CONF_IAQ_ACCURACY,
CONF_CO2_EQUIVALENT,
CONF_BREATH_VOC_EQUIVALENT,
]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
icon=ICON_GAUGE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_OHM,
icon=ICON_GAS_CYLINDER,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IAQ): sensor.sensor_schema(
unit_of_measurement=UNIT_IAQ,
icon=ICON_GAUGE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IAQ_STATIC): sensor.sensor_schema(
unit_of_measurement=UNIT_IAQ,
icon=ICON_GAUGE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema(
icon=ICON_ACCURACY,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
async def setup_conf(config, key, hub):
if conf := config.get(key):
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
if sample_rate := conf.get(CONF_SAMPLE_RATE):
cg.add(getattr(hub, f"set_{key}_sample_rate")(sample_rate))
async def to_code(config):
hub = await cg.get_variable(config[CONF_BME68X_BSEC2_ID])
for key in TYPES:
await setup_conf(config, key, hub)

View file

@ -0,0 +1,33 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_IAQ_ACCURACY
from . import CONF_BME68X_BSEC2_ID, BME68xBSEC2Component
DEPENDENCIES = ["bme68x_bsec2"]
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
TYPES = [CONF_IAQ_ACCURACY]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component),
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.text_sensor_schema(
icon=ICON_ACCURACY
),
}
)
async def setup_conf(config, key, hub):
if conf := config.get(key):
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
async def to_code(config):
hub = await cg.get_variable(config[CONF_BME68X_BSEC2_ID])
for key in TYPES:
await setup_conf(config, key, hub)

View file

@ -0,0 +1,28 @@
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components.bme68x_bsec2 import (
CONFIG_SCHEMA_BASE,
BME68xBSEC2Component,
to_code_base,
)
import esphome.config_validation as cv
CODEOWNERS = ["@neffs", "@kbx81"]
AUTO_LOAD = ["bme68x_bsec2"]
DEPENDENCIES = ["i2c"]
bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c")
BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_(
"BME68xBSEC2I2CComponent", BME68xBSEC2Component, i2c.I2CDevice
)
CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(
cv.Schema({cv.GenerateID(): cv.declare_id(BME68xBSEC2I2CComponent)})
).extend(i2c.i2c_device_schema(0x76))
async def to_code(config):
var = await to_code_base(config)
await i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,53 @@
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_BSEC2
#include "bme68x_bsec2_i2c.h"
#include "esphome/components/i2c/i2c.h"
#include <cinttypes>
namespace esphome {
namespace bme68x_bsec2_i2c {
static const char *const TAG = "bme68x_bsec2_i2c.sensor";
void BME68xBSEC2I2CComponent::setup() {
// must set up our bme68x_dev instance before calling setup()
this->bme68x_.intf_ptr = (void *) this;
this->bme68x_.intf = BME68X_I2C_INTF;
this->bme68x_.read = BME68xBSEC2I2CComponent::read_bytes_wrapper;
this->bme68x_.write = BME68xBSEC2I2CComponent::write_bytes_wrapper;
this->bme68x_.delay_us = BME68xBSEC2I2CComponent::delay_us;
this->bme68x_.amb_temp = 25;
BME68xBSEC2Component::setup();
}
void BME68xBSEC2I2CComponent::dump_config() {
LOG_I2C_DEVICE(this);
BME68xBSEC2Component::dump_config();
}
uint32_t BME68xBSEC2I2CComponent::get_hash() { return fnv1_hash("bme68x_bsec_state_" + to_string(this->address_)); }
int8_t BME68xBSEC2I2CComponent::read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr) {
ESP_LOGVV(TAG, "read_bytes_wrapper: reg = %u", a_register);
return static_cast<BME68xBSEC2I2CComponent *>(intfPtr)->read_bytes(a_register, data, len) ? 0 : -1;
}
int8_t BME68xBSEC2I2CComponent::write_bytes_wrapper(uint8_t a_register, const uint8_t *data, uint32_t len,
void *intfPtr) {
ESP_LOGVV(TAG, "write_bytes_wrapper: reg = %u", a_register);
return static_cast<BME68xBSEC2I2CComponent *>(intfPtr)->write_bytes(a_register, data, len) ? 0 : -1;
}
void BME68xBSEC2I2CComponent::delay_us(uint32_t period, void *intfPtr) {
ESP_LOGVV(TAG, "Delaying for %" PRIu32 "us", period);
delayMicroseconds(period);
}
} // namespace bme68x_bsec2_i2c
} // namespace esphome
#endif

View file

@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/preferences.h"
#ifdef USE_BSEC2
#include "esphome/components/bme68x_bsec2/bme68x_bsec2.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace bme68x_bsec2_i2c {
class BME68xBSEC2I2CComponent : public bme68x_bsec2::BME68xBSEC2Component, public i2c::I2CDevice {
void setup() override;
void dump_config() override;
uint32_t get_hash() override;
static int8_t read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr);
static int8_t write_bytes_wrapper(uint8_t a_register, const uint8_t *data, uint32_t len, void *intfPtr);
static void delay_us(uint32_t period, void *intfPtr);
};
} // namespace bme68x_bsec2_i2c
} // namespace esphome
#endif

View file

@ -675,5 +675,36 @@ void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; } void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; } const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
const LogString *text_align_to_string(TextAlign textalign) {
switch (textalign) {
case TextAlign::TOP_LEFT:
return LOG_STR("TOP_LEFT");
case TextAlign::TOP_CENTER:
return LOG_STR("TOP_CENTER");
case TextAlign::TOP_RIGHT:
return LOG_STR("TOP_RIGHT");
case TextAlign::CENTER_LEFT:
return LOG_STR("CENTER_LEFT");
case TextAlign::CENTER:
return LOG_STR("CENTER");
case TextAlign::CENTER_RIGHT:
return LOG_STR("CENTER_RIGHT");
case TextAlign::BASELINE_LEFT:
return LOG_STR("BASELINE_LEFT");
case TextAlign::BASELINE_CENTER:
return LOG_STR("BASELINE_CENTER");
case TextAlign::BASELINE_RIGHT:
return LOG_STR("BASELINE_RIGHT");
case TextAlign::BOTTOM_LEFT:
return LOG_STR("BOTTOM_LEFT");
case TextAlign::BOTTOM_CENTER:
return LOG_STR("BOTTOM_CENTER");
case TextAlign::BOTTOM_RIGHT:
return LOG_STR("BOTTOM_RIGHT");
default:
return LOG_STR("UNKNOWN");
}
}
} // namespace display } // namespace display
} // namespace esphome } // namespace esphome

View file

@ -8,6 +8,7 @@
#include "esphome/core/color.h" #include "esphome/core/color.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/time.h" #include "esphome/core/time.h"
#include "esphome/core/log.h"
#include "display_color_utils.h" #include "display_color_utils.h"
#ifdef USE_GRAPH #ifdef USE_GRAPH
@ -737,5 +738,7 @@ class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *>
DisplayPage *to_{nullptr}; DisplayPage *to_{nullptr};
}; };
const LogString *text_align_to_string(TextAlign textalign);
} // namespace display } // namespace display
} // namespace esphome } // namespace esphome

View file

@ -1,12 +1,10 @@
import esphome.codegen as cg
from esphome.components import improv_base from esphome.components import improv_base
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import VARIANT_ESP32S3
VARIANT_ESP32S3,
)
from esphome.components.logger import USB_CDC from esphome.components.logger import USB_CDC
from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_LOGGER
import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_LOGGER
from esphome.core import CORE from esphome.core import CORE
import esphome.final_validate as fv import esphome.final_validate as fv
@ -19,11 +17,7 @@ improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component) ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
cv.Schema( cv.Schema({cv.GenerateID(): cv.declare_id(ImprovSerialComponent)})
{
cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
}
)
.extend(improv_base.IMPROV_SCHEMA) .extend(improv_base.IMPROV_SCHEMA)
.extend(cv.COMPONENT_SCHEMA) .extend(cv.COMPONENT_SCHEMA)
) )

View file

@ -170,7 +170,11 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
} }
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() { std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
#ifdef ESPHOME_PROJECT_NAME
std::vector<std::string> infos = {ESPHOME_PROJECT_NAME, ESPHOME_PROJECT_VERSION, ESPHOME_VARIANT, App.get_name()};
#else
std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()}; std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
#endif
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false); std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
return data; return data;
}; };

View file

@ -47,6 +47,7 @@ from .types import (
IdleTrigger, IdleTrigger,
ObjUpdateAction, ObjUpdateAction,
lv_font_t, lv_font_t,
lv_group_t,
lv_style_t, lv_style_t,
lvgl_ns, lvgl_ns,
) )
@ -335,8 +336,9 @@ CONFIG_SCHEMA = (
cv.Optional(df.CONF_THEME): cv.Schema( cv.Optional(df.CONF_THEME): cv.Schema(
{cv.Optional(name): obj_schema(w) for name, w in WIDGET_TYPES.items()} {cv.Optional(name): obj_schema(w) for name, w in WIDGET_TYPES.items()}
), ),
cv.GenerateID(df.CONF_TOUCHSCREENS): touchscreen_schema, cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema,
cv.GenerateID(df.CONF_ENCODERS): ENCODERS_CONFIG, cv.Optional(df.CONF_ENCODERS, default=None): ENCODERS_CONFIG,
cv.GenerateID(df.CONF_DEFAULT_GROUP): cv.declare_id(lv_group_t),
} }
) )
.extend(DISP_BG_SCHEMA) .extend(DISP_BG_SCHEMA)

View file

@ -386,6 +386,7 @@ CONF_COLOR_DEPTH = "color_depth"
CONF_CONTROL = "control" CONF_CONTROL = "control"
CONF_DEFAULT = "default" CONF_DEFAULT = "default"
CONF_DEFAULT_FONT = "default_font" CONF_DEFAULT_FONT = "default_font"
CONF_DEFAULT_GROUP = "default_group"
CONF_DIR = "dir" CONF_DIR = "dir"
CONF_DISPLAYS = "displays" CONF_DISPLAYS = "displays"
CONF_ENCODERS = "encoders" CONF_ENCODERS = "encoders"

View file

@ -5,6 +5,7 @@ import esphome.config_validation as cv
from esphome.const import CONF_GROUP, CONF_ID, CONF_SENSOR from esphome.const import CONF_GROUP, CONF_ID, CONF_SENSOR
from .defines import ( from .defines import (
CONF_DEFAULT_GROUP,
CONF_ENCODERS, CONF_ENCODERS,
CONF_ENTER_BUTTON, CONF_ENTER_BUTTON,
CONF_LEFT_BUTTON, CONF_LEFT_BUTTON,
@ -38,7 +39,10 @@ ENCODERS_CONFIG = cv.ensure_list(
async def encoders_to_code(var, config): async def encoders_to_code(var, config):
for enc_conf in config.get(CONF_ENCODERS, ()): default_group = lv_Pvariable(lv_group_t, config[CONF_DEFAULT_GROUP])
lv_assign(default_group, lv_expr.group_create())
lv.group_set_default(default_group)
for enc_conf in config[CONF_ENCODERS]:
lvgl_components_required.add("KEY_LISTENER") lvgl_components_required.add("KEY_LISTENER")
lpt = enc_conf[CONF_LONG_PRESS_TIME].total_milliseconds lpt = enc_conf[CONF_LONG_PRESS_TIME].total_milliseconds
lprt = enc_conf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds lprt = enc_conf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds
@ -60,6 +64,6 @@ async def encoders_to_code(var, config):
if group := enc_conf.get(CONF_GROUP): if group := enc_conf.get(CONF_GROUP):
group = lv_Pvariable(lv_group_t, group) group = lv_Pvariable(lv_group_t, group)
lv_assign(group, lv_expr.group_create()) lv_assign(group, lv_expr.group_create())
lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group)
else: else:
lv.indev_drv_register(listener.get_drv()) group = default_group
lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group)

View file

@ -19,7 +19,7 @@ class LVGLNumber : public number::Number {
} }
protected: protected:
void control(float value) { void control(float value) override {
if (this->control_lambda_ != nullptr) if (this->control_lambda_ != nullptr)
this->control_lambda_(value); this->control_lambda_(value);
else else

View file

@ -19,7 +19,7 @@ class LVGLSwitch : public switch_::Switch {
} }
protected: protected:
void write_state(bool value) { void write_state(bool value) override {
if (this->state_lambda_ != nullptr) if (this->state_lambda_ != nullptr)
this->state_lambda_(value); this->state_lambda_(value);
else else

View file

@ -19,7 +19,7 @@ class LVGLText : public text::Text {
} }
protected: protected:
void control(const std::string &value) { void control(const std::string &value) override {
if (this->control_lambda_ != nullptr) if (this->control_lambda_ != nullptr)
this->control_lambda_(value); this->control_lambda_(value);
else else

View file

@ -34,7 +34,7 @@ def touchscreen_schema(config):
async def touchscreens_to_code(var, config): async def touchscreens_to_code(var, config):
for tconf in config.get(CONF_TOUCHSCREENS, ()): for tconf in config[CONF_TOUCHSCREENS]:
lvgl_components_required.add(CONF_TOUCHSCREEN) lvgl_components_required.add(CONF_TOUCHSCREEN)
touchscreen = await cg.get_variable(tconf[CONF_TOUCHSCREEN_ID]) touchscreen = await cg.get_variable(tconf[CONF_TOUCHSCREEN_ID])
lpt = tconf[CONF_LONG_PRESS_TIME].total_milliseconds lpt = tconf[CONF_LONG_PRESS_TIME].total_milliseconds

View file

@ -7,30 +7,24 @@ namespace esphome {
namespace media_player { namespace media_player {
#define MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(ACTION_CLASS, ACTION_COMMAND) \ template<MediaPlayerCommand Command, typename... Ts>
template<typename... Ts> class ACTION_CLASS : public Action<Ts...>, public Parented<MediaPlayer> { \ class MediaPlayerCommandAction : public Action<Ts...>, public Parented<MediaPlayer> {
void play(Ts... x) override { \ public:
this->parent_->make_call().set_command(MediaPlayerCommand::MEDIA_PLAYER_COMMAND_##ACTION_COMMAND).perform(); \ void play(Ts... x) override { this->parent_->make_call().set_command(Command).perform(); }
} \ };
};
#define MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(TRIGGER_CLASS, TRIGGER_STATE) \ template<typename... Ts>
class TRIGGER_CLASS : public Trigger<> { \ using PlayAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_PLAY, Ts...>;
public: \ template<typename... Ts>
explicit TRIGGER_CLASS(MediaPlayer *player) { \ using PauseAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_PAUSE, Ts...>;
player->add_on_state_callback([this, player]() { \ template<typename... Ts>
if (player->state == MediaPlayerState::MEDIA_PLAYER_STATE_##TRIGGER_STATE) \ using StopAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_STOP, Ts...>;
this->trigger(); \ template<typename... Ts>
}); \ using ToggleAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_TOGGLE, Ts...>;
} \ template<typename... Ts>
}; using VolumeUpAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_VOLUME_UP, Ts...>;
template<typename... Ts>
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(PlayAction, PLAY) using VolumeDownAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_VOLUME_DOWN, Ts...>;
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(PauseAction, PAUSE)
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(StopAction, STOP)
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(ToggleAction, TOGGLE)
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(VolumeUpAction, VOLUME_UP)
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(VolumeDownAction, VOLUME_DOWN)
template<typename... Ts> class PlayMediaAction : public Action<Ts...>, public Parented<MediaPlayer> { template<typename... Ts> class PlayMediaAction : public Action<Ts...>, public Parented<MediaPlayer> {
TEMPLATABLE_VALUE(std::string, media_url) TEMPLATABLE_VALUE(std::string, media_url)
@ -49,10 +43,20 @@ class StateTrigger : public Trigger<> {
} }
}; };
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE) template<MediaPlayerState State> class MediaPlayerStateTrigger : public Trigger<> {
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING) public:
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED) explicit MediaPlayerStateTrigger(MediaPlayer *player) {
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(AnnouncementTrigger, ANNOUNCING) player->add_on_state_callback([this, player]() {
if (player->state == State)
this->trigger();
});
}
};
using IdleTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>;
using PlayTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING>;
using PauseTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED>;
using AnnouncementTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING>;
template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> { template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> {
public: public:

View file

@ -10,7 +10,7 @@ SmlFile::SmlFile(bytes buffer) : buffer_(std::move(buffer)) {
this->pos_ = 0; this->pos_ = 0;
while (this->pos_ < this->buffer_.size()) { while (this->pos_ < this->buffer_.size()) {
if (this->buffer_[this->pos_] == 0x00) if (this->buffer_[this->pos_] == 0x00)
break; // fill byte detected -> no more messages break; // EndOfSmlMsg
SmlNode message = SmlNode(); SmlNode message = SmlNode();
if (!this->setup_node(&message)) if (!this->setup_node(&message))
@ -20,40 +20,66 @@ SmlFile::SmlFile(bytes buffer) : buffer_(std::move(buffer)) {
} }
bool SmlFile::setup_node(SmlNode *node) { bool SmlFile::setup_node(SmlNode *node) {
uint8_t type = this->buffer_[this->pos_] >> 4; // type including overlength info // If the TL field is 0x00, this is the end of the message
uint8_t length = this->buffer_[this->pos_] & 0x0f; // length including TL bytes // (see 6.3.1 of SML protocol definition)
bool is_list = (type & 0x07) == SML_LIST; if (this->buffer_[this->pos_] == 0x00) {
bool has_extended_length = type & 0x08; // we have a long list/value (>15 entries) // Increment past this byte and signal that the message is done
uint8_t parse_length = length;
if (has_extended_length) {
length = (length << 4) + (this->buffer_[this->pos_ + 1] & 0x0f);
parse_length = length;
this->pos_ += 1; this->pos_ += 1;
return true;
} }
if (this->pos_ + parse_length >= this->buffer_.size()) // Extract data from initial TL field
uint8_t type = (this->buffer_[this->pos_] >> 4) & 0x07; // type without overlength info
bool overlength = (this->buffer_[this->pos_] >> 4) & 0x08; // overlength information
uint8_t length = this->buffer_[this->pos_] & 0x0f; // length (including TL bytes)
// Check if we need additional length bytes
if (overlength) {
// Shift the current length to the higher nibble
// and add the lower nibble of the next byte to the length
length = (length << 4) + (this->buffer_[this->pos_ + 1] & 0x0f);
// We are basically done with the first TL field now,
// so increment past that, we now point to the second TL field
this->pos_ += 1;
// Decrement the length for value fields (not lists),
// since the byte we just handled is counted as part of the field
// in case of values but not for lists
if (type != SML_LIST)
length -= 1;
// Technically, this is not enough, the standard allows for more than two length fields.
// However I don't think it is very common to have more than 255 entries in a list
}
// We are done with the last TL field(s), so advance the position
this->pos_ += 1;
// and decrement the length for non-list fields
if (type != SML_LIST)
length -= 1;
// Check if the buffer length is long enough
if (this->pos_ + length > this->buffer_.size())
return false; return false;
node->type = type & 0x07; node->type = type;
node->nodes.clear(); node->nodes.clear();
node->value_bytes.clear(); node->value_bytes.clear();
// if the list is a has_extended_length list with e.g. 16 elements this is a 0x00 byte but not the end of message if (type == SML_LIST) {
if (!has_extended_length && this->buffer_[this->pos_] == 0x00) { // end of message node->nodes.reserve(length);
this->pos_ += 1; for (size_t i = 0; i != length; i++) {
} else if (is_list) { // list
this->pos_ += 1;
node->nodes.reserve(parse_length);
for (size_t i = 0; i != parse_length; i++) {
SmlNode child_node = SmlNode(); SmlNode child_node = SmlNode();
if (!this->setup_node(&child_node)) if (!this->setup_node(&child_node))
return false; return false;
node->nodes.emplace_back(child_node); node->nodes.emplace_back(child_node);
} }
} else { // value } else {
node->value_bytes = // Value starts at the current position
bytes(this->buffer_.begin() + this->pos_ + 1, this->buffer_.begin() + this->pos_ + parse_length); // Value ends "length" bytes later,
this->pos_ += parse_length; // (since the TL field is counted but already subtracted from length)
node->value_bytes = bytes(this->buffer_.begin() + this->pos_, this->buffer_.begin() + this->pos_ + length);
// Increment the pointer past all consumed bytes
this->pos_ += length;
} }
return true; return true;
} }
@ -101,7 +127,7 @@ int64_t bytes_to_int(const bytes &buffer) {
// see https://stackoverflow.com/questions/42534749/signed-extension-from-24-bit-to-32-bit-in-c // see https://stackoverflow.com/questions/42534749/signed-extension-from-24-bit-to-32-bit-in-c
if (buffer.size() < 8) { if (buffer.size() < 8) {
const int bits = buffer.size() * 8; const int bits = buffer.size() * 8;
const uint64_t m = 1u << (bits - 1); const uint64_t m = 1ull << (bits - 1);
tmp = (tmp ^ m) - m; tmp = (tmp ^ m) - m;
} }

View file

@ -13,7 +13,7 @@ class SpiLedStrip : public light::AddressableLight,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
spi::DATA_RATE_1MHZ> { spi::DATA_RATE_1MHZ> {
public: public:
void setup() { this->spi_setup(); } void setup() override { this->spi_setup(); }
int32_t size() const override { return this->num_leds_; } int32_t size() const override { return this->num_leds_; }
@ -43,7 +43,7 @@ class SpiLedStrip : public light::AddressableLight,
memset(this->buf_, 0, 4); memset(this->buf_, 0, 4);
} }
void dump_config() { void dump_config() override {
esph_log_config(TAG, "SPI LED Strip:"); esph_log_config(TAG, "SPI LED Strip:");
esph_log_config(TAG, " LEDs: %d", this->num_leds_); esph_log_config(TAG, " LEDs: %d", this->num_leds_);
if (this->data_rate_ >= spi::DATA_RATE_1MHZ) if (this->data_rate_ >= spi::DATA_RATE_1MHZ)

View file

@ -158,6 +158,7 @@
#endif #endif
// Disabled feature flags // Disabled feature flags
// #define USE_BSEC // Requires a library with proprietary license. // #define USE_BSEC // Requires a library with proprietary license
// #define USE_BSEC2 // Requires a library with proprietary license
#define USE_DASHBOARD_IMPORT #define USE_DASHBOARD_IMPORT

View file

@ -115,9 +115,10 @@ def clang_options(idedata):
pids = set() pids = set()
def run_tidy(executable, args, options, tmpdir, queue, lock, failed_files):
def run_tidy(executable, args, options, tmpdir, path_queue, lock, failed_files):
while True: while True:
path = queue.get() path = path_queue.get()
invocation = [executable] invocation = [executable]
if tmpdir is not None: if tmpdir is not None:
@ -139,17 +140,20 @@ def run_tidy(executable, args, options, tmpdir, queue, lock, failed_files):
invocation.append("--") invocation.append("--")
invocation.extend(options) invocation.extend(options)
proc = subprocess.run(invocation, capture_output=True, encoding="utf-8") proc = subprocess.run(
invocation, capture_output=True, encoding="utf-8", check=False
)
if proc.returncode != 0: if proc.returncode != 0:
with lock: with lock:
print_error_for_file(path, proc.stdout) print_error_for_file(path, proc.stdout)
failed_files.append(path) failed_files.append(path)
queue.task_done() path_queue.task_done()
def progress_bar_show(value): def progress_bar_show(value):
if value is None: if value is None:
return "" return ""
return None
def split_list(a, n): def split_list(a, n):
@ -237,7 +241,15 @@ def main():
for _ in range(args.jobs): for _ in range(args.jobs):
t = threading.Thread( t = threading.Thread(
target=run_tidy, target=run_tidy,
args=(executable, args, options, tmpdir, task_queue, lock, failed_files), args=(
executable,
args,
options,
tmpdir,
task_queue,
lock,
failed_files,
),
) )
t.daemon = True t.daemon = True
t.start() t.start()
@ -245,14 +257,14 @@ def main():
# Fill the queue with files. # Fill the queue with files.
with click.progressbar( with click.progressbar(
files, width=30, file=sys.stderr, item_show_func=progress_bar_show files, width=30, file=sys.stderr, item_show_func=progress_bar_show
) as bar: ) as progress_bar:
for name in bar: for name in progress_bar:
task_queue.put(name) task_queue.put(name)
# Wait for all threads to be done. # Wait for all threads to be done.
task_queue.join() task_queue.join()
except FileNotFoundError as ex: except FileNotFoundError:
return 1 return 1
except KeyboardInterrupt: except KeyboardInterrupt:
print() print()
@ -262,7 +274,7 @@ def main():
# Kill subprocesses (and ourselves!) # Kill subprocesses (and ourselves!)
# No simple, clean alternative appears to be available. # No simple, clean alternative appears to be available.
os.kill(0, 9) os.kill(0, 9)
return 2 # Will not execute. return 2 # Will not execute.
if args.fix and failed_files: if args.fix and failed_files:
print("Applying fixes ...") print("Applying fixes ...")
@ -272,7 +284,10 @@ def main():
except FileNotFoundError: except FileNotFoundError:
subprocess.call(["clang-apply-replacements", tmpdir]) subprocess.call(["clang-apply-replacements", tmpdir])
except FileNotFoundError: except FileNotFoundError:
print("Error please install clang-apply-replacements-14 or clang-apply-replacements.\n", file=sys.stderr) print(
"Error please install clang-apply-replacements-14 or clang-apply-replacements.\n",
file=sys.stderr,
)
except: except:
print("Error applying fixes.\n", file=sys.stderr) print("Error applying fixes.\n", file=sys.stderr)
raise raise

View file

@ -159,20 +159,19 @@ def get_binary(name: str, version: str) -> str:
binary_file = f"{name}-{version}" binary_file = f"{name}-{version}"
try: try:
result = subprocess.check_output([binary_file, "-version"]) result = subprocess.check_output([binary_file, "-version"])
if result.returncode == 0: return binary_file
return binary_file except FileNotFoundError:
except Exception:
pass pass
binary_file = name binary_file = name
try: try:
result = subprocess.run( result = subprocess.run(
[binary_file, "-version"], text=True, capture_output=True [binary_file, "-version"], text=True, capture_output=True, check=False
) )
if result.returncode == 0 and (f"version {version}") in result.stdout: if result.returncode == 0 and (f"version {version}") in result.stdout:
return binary_file return binary_file
raise FileNotFoundError(f"{name} not found") raise FileNotFoundError(f"{name} not found")
except FileNotFoundError as ex: except FileNotFoundError:
print( print(
f""" f"""
Oops. It looks like {name} is not installed. It should be available under venv/bin Oops. It looks like {name} is not installed. It should be available under venv/bin

View file

@ -0,0 +1,34 @@
i2c:
- id: i2c_bme68x
scl: ${scl_pin}
sda: ${sda_pin}
bme68x_bsec2_i2c:
address: 0x76
model: bme688
algorithm_output: classification
operating_age: 28d
sample_rate: LP
supply_voltage: 3.3V
sensor:
- platform: bme68x_bsec2
temperature:
name: BME68X Temperature
pressure:
name: BME68X Pressure
humidity:
name: BME68X Humidity
gas_resistance:
name: BME68X Gas Sensor
iaq:
name: BME68X IAQ
co2_equivalent:
name: BME68X eCO2
breath_voc_equivalent:
name: BME68X Breath eVOC
text_sensor:
- platform: bme68x_bsec2
iaq_accuracy:
name: BME68X Accuracy

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO6
sda_pin: GPIO7
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml