mirror of
https://github.com/esphome/esphome.git
synced 2024-12-24 14:34:54 +01:00
Merge remote-tracking branch 'upstream/dev' into gsm
This commit is contained in:
commit
e1af5bc767
30 changed files with 1384 additions and 85 deletions
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
|
@ -46,7 +46,7 @@ runs:
|
|||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v6.5.0
|
||||
uses: docker/build-push-action@v6.6.1
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
@ -69,7 +69,7 @@ runs:
|
|||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@v6.5.0
|
||||
uses: docker/build-push-action@v6.6.1
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
|
|
@ -65,6 +65,8 @@ esphome/components/bluetooth_proxy/* @jesserockz
|
|||
esphome/components/bme280_base/* @esphome/core
|
||||
esphome/components/bme280_spi/* @apbodrov
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/bme68x_bsec2/* @kbx81 @neffs
|
||||
esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs
|
||||
esphome/components/bmi160/* @flaviut
|
||||
esphome/components/bmp3xx/* @latonita
|
||||
esphome/components/bmp3xx_base/* @latonita @martgras
|
||||
|
|
|
@ -1336,7 +1336,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
|
|||
update->check();
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown update command: %d", msg.command);
|
||||
ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
196
esphome/components/bme68x_bsec2/__init__.py
Normal file
196
esphome/components/bme68x_bsec2/__init__.py
Normal 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
|
523
esphome/components/bme68x_bsec2/bme68x_bsec2.cpp
Normal file
523
esphome/components/bme68x_bsec2/bme68x_bsec2.cpp
Normal 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(¤t_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
|
163
esphome/components/bme68x_bsec2/bme68x_bsec2.h
Normal file
163
esphome/components/bme68x_bsec2/bme68x_bsec2.h
Normal 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
|
130
esphome/components/bme68x_bsec2/sensor.py
Normal file
130
esphome/components/bme68x_bsec2/sensor.py
Normal 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)
|
33
esphome/components/bme68x_bsec2/text_sensor.py
Normal file
33
esphome/components/bme68x_bsec2/text_sensor.py
Normal 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)
|
28
esphome/components/bme68x_bsec2_i2c/__init__.py
Normal file
28
esphome/components/bme68x_bsec2_i2c/__init__.py
Normal 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)
|
53
esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp
Normal file
53
esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp
Normal 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
|
28
esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.h
Normal file
28
esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.h
Normal 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
|
|
@ -675,5 +675,36 @@ void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
|
|||
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
|
||||
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 esphome
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/time.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "display_color_utils.h"
|
||||
|
||||
#ifdef USE_GRAPH
|
||||
|
@ -737,5 +738,7 @@ class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *>
|
|||
DisplayPage *to_{nullptr};
|
||||
};
|
||||
|
||||
const LogString *text_align_to_string(TextAlign textalign);
|
||||
|
||||
} // namespace display
|
||||
} // namespace esphome
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components import improv_base
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.components.esp32.const import VARIANT_ESP32S3
|
||||
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
|
||||
from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_LOGGER
|
||||
from esphome.core import CORE
|
||||
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)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
|
||||
}
|
||||
)
|
||||
cv.Schema({cv.GenerateID(): cv.declare_id(ImprovSerialComponent)})
|
||||
.extend(improv_base.IMPROV_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
|
|
@ -170,7 +170,11 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
|
|||
}
|
||||
|
||||
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()};
|
||||
#endif
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
|
||||
return data;
|
||||
};
|
||||
|
|
|
@ -47,6 +47,7 @@ from .types import (
|
|||
IdleTrigger,
|
||||
ObjUpdateAction,
|
||||
lv_font_t,
|
||||
lv_group_t,
|
||||
lv_style_t,
|
||||
lvgl_ns,
|
||||
)
|
||||
|
@ -335,8 +336,9 @@ CONFIG_SCHEMA = (
|
|||
cv.Optional(df.CONF_THEME): cv.Schema(
|
||||
{cv.Optional(name): obj_schema(w) for name, w in WIDGET_TYPES.items()}
|
||||
),
|
||||
cv.GenerateID(df.CONF_TOUCHSCREENS): touchscreen_schema,
|
||||
cv.GenerateID(df.CONF_ENCODERS): ENCODERS_CONFIG,
|
||||
cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema,
|
||||
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)
|
||||
|
|
|
@ -386,6 +386,7 @@ CONF_COLOR_DEPTH = "color_depth"
|
|||
CONF_CONTROL = "control"
|
||||
CONF_DEFAULT = "default"
|
||||
CONF_DEFAULT_FONT = "default_font"
|
||||
CONF_DEFAULT_GROUP = "default_group"
|
||||
CONF_DIR = "dir"
|
||||
CONF_DISPLAYS = "displays"
|
||||
CONF_ENCODERS = "encoders"
|
||||
|
|
|
@ -5,6 +5,7 @@ import esphome.config_validation as cv
|
|||
from esphome.const import CONF_GROUP, CONF_ID, CONF_SENSOR
|
||||
|
||||
from .defines import (
|
||||
CONF_DEFAULT_GROUP,
|
||||
CONF_ENCODERS,
|
||||
CONF_ENTER_BUTTON,
|
||||
CONF_LEFT_BUTTON,
|
||||
|
@ -38,7 +39,10 @@ ENCODERS_CONFIG = cv.ensure_list(
|
|||
|
||||
|
||||
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")
|
||||
lpt = enc_conf[CONF_LONG_PRESS_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):
|
||||
group = lv_Pvariable(lv_group_t, group)
|
||||
lv_assign(group, lv_expr.group_create())
|
||||
lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group)
|
||||
else:
|
||||
lv.indev_drv_register(listener.get_drv())
|
||||
group = default_group
|
||||
lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group)
|
||||
|
|
|
@ -34,7 +34,7 @@ def touchscreen_schema(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)
|
||||
touchscreen = await cg.get_variable(tconf[CONF_TOUCHSCREEN_ID])
|
||||
lpt = tconf[CONF_LONG_PRESS_TIME].total_milliseconds
|
||||
|
|
|
@ -7,30 +7,24 @@ namespace esphome {
|
|||
|
||||
namespace media_player {
|
||||
|
||||
#define MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(ACTION_CLASS, ACTION_COMMAND) \
|
||||
template<typename... Ts> class ACTION_CLASS : public Action<Ts...>, public Parented<MediaPlayer> { \
|
||||
void play(Ts... x) override { \
|
||||
this->parent_->make_call().set_command(MediaPlayerCommand::MEDIA_PLAYER_COMMAND_##ACTION_COMMAND).perform(); \
|
||||
} \
|
||||
};
|
||||
template<MediaPlayerCommand Command, typename... Ts>
|
||||
class MediaPlayerCommandAction : public Action<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->make_call().set_command(Command).perform(); }
|
||||
};
|
||||
|
||||
#define MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(TRIGGER_CLASS, TRIGGER_STATE) \
|
||||
class TRIGGER_CLASS : public Trigger<> { \
|
||||
public: \
|
||||
explicit TRIGGER_CLASS(MediaPlayer *player) { \
|
||||
player->add_on_state_callback([this, player]() { \
|
||||
if (player->state == MediaPlayerState::MEDIA_PLAYER_STATE_##TRIGGER_STATE) \
|
||||
this->trigger(); \
|
||||
}); \
|
||||
} \
|
||||
};
|
||||
|
||||
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(PlayAction, PLAY)
|
||||
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>
|
||||
using PlayAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_PLAY, Ts...>;
|
||||
template<typename... Ts>
|
||||
using PauseAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_PAUSE, Ts...>;
|
||||
template<typename... Ts>
|
||||
using StopAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_STOP, Ts...>;
|
||||
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>
|
||||
using VolumeDownAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_VOLUME_DOWN, Ts...>;
|
||||
|
||||
template<typename... Ts> class PlayMediaAction : public Action<Ts...>, public Parented<MediaPlayer> {
|
||||
TEMPLATABLE_VALUE(std::string, media_url)
|
||||
|
@ -49,10 +43,20 @@ class StateTrigger : public Trigger<> {
|
|||
}
|
||||
};
|
||||
|
||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE)
|
||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING)
|
||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED)
|
||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(AnnouncementTrigger, ANNOUNCING)
|
||||
template<MediaPlayerState State> class MediaPlayerStateTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit MediaPlayerStateTrigger(MediaPlayer *player) {
|
||||
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> {
|
||||
public:
|
||||
|
|
|
@ -10,7 +10,7 @@ SmlFile::SmlFile(bytes buffer) : buffer_(std::move(buffer)) {
|
|||
this->pos_ = 0;
|
||||
while (this->pos_ < this->buffer_.size()) {
|
||||
if (this->buffer_[this->pos_] == 0x00)
|
||||
break; // fill byte detected -> no more messages
|
||||
break; // EndOfSmlMsg
|
||||
|
||||
SmlNode message = SmlNode();
|
||||
if (!this->setup_node(&message))
|
||||
|
@ -20,40 +20,66 @@ SmlFile::SmlFile(bytes buffer) : buffer_(std::move(buffer)) {
|
|||
}
|
||||
|
||||
bool SmlFile::setup_node(SmlNode *node) {
|
||||
uint8_t type = this->buffer_[this->pos_] >> 4; // type including overlength info
|
||||
uint8_t length = this->buffer_[this->pos_] & 0x0f; // length including TL bytes
|
||||
bool is_list = (type & 0x07) == SML_LIST;
|
||||
bool has_extended_length = type & 0x08; // we have a long list/value (>15 entries)
|
||||
uint8_t parse_length = length;
|
||||
if (has_extended_length) {
|
||||
length = (length << 4) + (this->buffer_[this->pos_ + 1] & 0x0f);
|
||||
parse_length = length;
|
||||
// If the TL field is 0x00, this is the end of the message
|
||||
// (see 6.3.1 of SML protocol definition)
|
||||
if (this->buffer_[this->pos_] == 0x00) {
|
||||
// Increment past this byte and signal that the message is done
|
||||
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;
|
||||
|
||||
node->type = type & 0x07;
|
||||
node->type = type;
|
||||
node->nodes.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 (!has_extended_length && this->buffer_[this->pos_] == 0x00) { // end of message
|
||||
this->pos_ += 1;
|
||||
} else if (is_list) { // list
|
||||
this->pos_ += 1;
|
||||
node->nodes.reserve(parse_length);
|
||||
for (size_t i = 0; i != parse_length; i++) {
|
||||
if (type == SML_LIST) {
|
||||
node->nodes.reserve(length);
|
||||
for (size_t i = 0; i != length; i++) {
|
||||
SmlNode child_node = SmlNode();
|
||||
if (!this->setup_node(&child_node))
|
||||
return false;
|
||||
node->nodes.emplace_back(child_node);
|
||||
}
|
||||
} else { // value
|
||||
node->value_bytes =
|
||||
bytes(this->buffer_.begin() + this->pos_ + 1, this->buffer_.begin() + this->pos_ + parse_length);
|
||||
this->pos_ += parse_length;
|
||||
} else {
|
||||
// Value starts at the current position
|
||||
// Value ends "length" bytes later,
|
||||
// (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;
|
||||
}
|
||||
|
@ -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
|
||||
if (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;
|
||||
}
|
||||
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
#endif
|
||||
|
||||
// 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
|
||||
|
|
|
@ -115,9 +115,10 @@ def clang_options(idedata):
|
|||
|
||||
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:
|
||||
path = queue.get()
|
||||
path = path_queue.get()
|
||||
invocation = [executable]
|
||||
|
||||
if tmpdir is not None:
|
||||
|
@ -139,17 +140,20 @@ def run_tidy(executable, args, options, tmpdir, queue, lock, failed_files):
|
|||
invocation.append("--")
|
||||
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:
|
||||
with lock:
|
||||
print_error_for_file(path, proc.stdout)
|
||||
failed_files.append(path)
|
||||
queue.task_done()
|
||||
path_queue.task_done()
|
||||
|
||||
|
||||
def progress_bar_show(value):
|
||||
if value is None:
|
||||
return ""
|
||||
return None
|
||||
|
||||
|
||||
def split_list(a, n):
|
||||
|
@ -237,7 +241,15 @@ def main():
|
|||
for _ in range(args.jobs):
|
||||
t = threading.Thread(
|
||||
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.start()
|
||||
|
@ -245,14 +257,14 @@ def main():
|
|||
# Fill the queue with files.
|
||||
with click.progressbar(
|
||||
files, width=30, file=sys.stderr, item_show_func=progress_bar_show
|
||||
) as bar:
|
||||
for name in bar:
|
||||
) as progress_bar:
|
||||
for name in progress_bar:
|
||||
task_queue.put(name)
|
||||
|
||||
# Wait for all threads to be done.
|
||||
task_queue.join()
|
||||
|
||||
except FileNotFoundError as ex:
|
||||
except FileNotFoundError:
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
|
@ -262,7 +274,7 @@ def main():
|
|||
# Kill subprocesses (and ourselves!)
|
||||
# No simple, clean alternative appears to be available.
|
||||
os.kill(0, 9)
|
||||
return 2 # Will not execute.
|
||||
return 2 # Will not execute.
|
||||
|
||||
if args.fix and failed_files:
|
||||
print("Applying fixes ...")
|
||||
|
@ -272,7 +284,10 @@ def main():
|
|||
except FileNotFoundError:
|
||||
subprocess.call(["clang-apply-replacements", tmpdir])
|
||||
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:
|
||||
print("Error applying fixes.\n", file=sys.stderr)
|
||||
raise
|
||||
|
|
|
@ -159,20 +159,19 @@ def get_binary(name: str, version: str) -> str:
|
|||
binary_file = f"{name}-{version}"
|
||||
try:
|
||||
result = subprocess.check_output([binary_file, "-version"])
|
||||
if result.returncode == 0:
|
||||
return binary_file
|
||||
except Exception:
|
||||
return binary_file
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
binary_file = name
|
||||
try:
|
||||
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:
|
||||
return binary_file
|
||||
raise FileNotFoundError(f"{name} not found")
|
||||
|
||||
except FileNotFoundError as ex:
|
||||
except FileNotFoundError:
|
||||
print(
|
||||
f"""
|
||||
Oops. It looks like {name} is not installed. It should be available under venv/bin
|
||||
|
|
34
tests/components/bme68x_bsec2_i2c/common.yaml
Normal file
34
tests/components/bme68x_bsec2_i2c/common.yaml
Normal 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
|
5
tests/components/bme68x_bsec2_i2c/test.esp32-ard.yaml
Normal file
5
tests/components/bme68x_bsec2_i2c/test.esp32-ard.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/bme68x_bsec2_i2c/test.esp32-c3-ard.yaml
Normal file
5
tests/components/bme68x_bsec2_i2c/test.esp32-c3-ard.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
substitutions:
|
||||
scl_pin: GPIO6
|
||||
sda_pin: GPIO7
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/bme68x_bsec2_i2c/test.esp32-s2-ard.yaml
Normal file
5
tests/components/bme68x_bsec2_i2c/test.esp32-s2-ard.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/bme68x_bsec2_i2c/test.esp32-s3-ard.yaml
Normal file
5
tests/components/bme68x_bsec2_i2c/test.esp32-s3-ard.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/bme68x_bsec2_i2c/test.esp8266-ard.yaml
Normal file
5
tests/components/bme68x_bsec2_i2c/test.esp8266-ard.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
Loading…
Reference in a new issue