diff --git a/CODEOWNERS b/CODEOWNERS index 7f2aef7be8..f47b36ff73 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -31,6 +31,7 @@ esphome/components/binary_sensor/* @esphome/core esphome/components/bl0940/* @tobias- esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth +esphome/components/bmp3xx/* @martgras esphome/components/button/* @esphome/core esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @MrEditor97 diff --git a/esphome/components/bmp3xx/__init__.py b/esphome/components/bmp3xx/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/bmp3xx/bmp3xx.cpp b/esphome/components/bmp3xx/bmp3xx.cpp new file mode 100644 index 0000000000..410b7a3173 --- /dev/null +++ b/esphome/components/bmp3xx/bmp3xx.cpp @@ -0,0 +1,388 @@ +/* + based on BMP388_DEV by Martin Lindupp + under MIT License (MIT) + Copyright (C) Martin Lindupp 2020 + http://github.com/MartinL1/BMP388_DEV +*/ + +#include "bmp3xx.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace bmp3xx { + +static const char *const TAG = "bmp3xx.sensor"; + +static const LogString *chip_type_to_str(uint8_t chip_type) { + switch (chip_type) { + case BMP388_ID: + return LOG_STR("BMP 388"); + case BMP390_ID: + return LOG_STR("BMP 390"); + default: + return LOG_STR("Unknown Chip Type"); + } +} + +static const LogString *oversampling_to_str(Oversampling oversampling) { + switch (oversampling) { + case Oversampling::OVERSAMPLING_NONE: + return LOG_STR("None"); + case Oversampling::OVERSAMPLING_X2: + return LOG_STR("2x"); + case Oversampling::OVERSAMPLING_X4: + return LOG_STR("4x"); + case Oversampling::OVERSAMPLING_X8: + return LOG_STR("8x"); + case Oversampling::OVERSAMPLING_X16: + return LOG_STR("16x"); + case Oversampling::OVERSAMPLING_X32: + return LOG_STR("32x"); + default: + return LOG_STR(""); + } +} + +static const LogString *iir_filter_to_str(IIRFilter filter) { + switch (filter) { + case IIRFilter::IIR_FILTER_OFF: + return LOG_STR("OFF"); + case IIRFilter::IIR_FILTER_2: + return LOG_STR("2x"); + case IIRFilter::IIR_FILTER_4: + return LOG_STR("4x"); + case IIRFilter::IIR_FILTER_8: + return LOG_STR("8x"); + case IIRFilter::IIR_FILTER_16: + return LOG_STR("16x"); + case IIRFilter::IIR_FILTER_32: + return LOG_STR("32x"); + case IIRFilter::IIR_FILTER_64: + return LOG_STR("64x"); + case IIRFilter::IIR_FILTER_128: + return LOG_STR("128x"); + default: + return LOG_STR(""); + } +} + +void BMP3XXComponent::setup() { + this->error_code_ = NONE; + ESP_LOGCONFIG(TAG, "Setting up BMP3XX..."); + // Call the Device base class "initialise" function + if (!reset()) { + ESP_LOGE(TAG, "Failed to reset BMP3XX..."); + this->error_code_ = ERROR_SENSOR_RESET; + this->mark_failed(); + } + + if (!read_byte(BMP388_CHIP_ID, &this->chip_id_.reg)) { + ESP_LOGE(TAG, "Can't read chip id"); + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + ESP_LOGCONFIG(TAG, "Chip %s Id 0x%X", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg); + + if (chip_id_.reg != BMP388_ID && chip_id_.reg != BMP390_ID) { + ESP_LOGE(TAG, "Unknown chip id - is this really a BMP388 or BMP390?"); + this->error_code_ = ERROR_WRONG_CHIP_ID; + this->mark_failed(); + return; + } + // set sensor in sleep mode + stop_conversion(); + // Read the calibration parameters into the params structure + if (!read_bytes(BMP388_TRIM_PARAMS, (uint8_t *) &compensation_params_, sizeof(compensation_params_))) { + ESP_LOGE(TAG, "Can't read calibration data"); + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + compensation_float_params_.param_T1 = + (float) compensation_params_.param_T1 / powf(2.0f, -8.0f); // Calculate the floating point trim parameters + compensation_float_params_.param_T2 = (float) compensation_params_.param_T2 / powf(2.0f, 30.0f); + compensation_float_params_.param_T3 = (float) compensation_params_.param_T3 / powf(2.0f, 48.0f); + compensation_float_params_.param_P1 = ((float) compensation_params_.param_P1 - powf(2.0f, 14.0f)) / powf(2.0f, 20.0f); + compensation_float_params_.param_P2 = ((float) compensation_params_.param_P2 - powf(2.0f, 14.0f)) / powf(2.0f, 29.0f); + compensation_float_params_.param_P3 = (float) compensation_params_.param_P3 / powf(2.0f, 32.0f); + compensation_float_params_.param_P4 = (float) compensation_params_.param_P4 / powf(2.0f, 37.0f); + compensation_float_params_.param_P5 = (float) compensation_params_.param_P5 / powf(2.0f, -3.0f); + compensation_float_params_.param_P6 = (float) compensation_params_.param_P6 / powf(2.0f, 6.0f); + compensation_float_params_.param_P7 = (float) compensation_params_.param_P7 / powf(2.0f, 8.0f); + compensation_float_params_.param_P8 = (float) compensation_params_.param_P8 / powf(2.0f, 15.0f); + compensation_float_params_.param_P9 = (float) compensation_params_.param_P9 / powf(2.0f, 48.0f); + compensation_float_params_.param_P10 = (float) compensation_params_.param_P10 / powf(2.0f, 48.0f); + compensation_float_params_.param_P11 = (float) compensation_params_.param_P11 / powf(2.0f, 65.0f); + + // Initialise the BMP388 IIR filter register + if (!set_iir_filter(this->iir_filter_)) { + ESP_LOGE(TAG, "Failed to set IIR filter"); + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + // Set power control registers + pwr_ctrl_.bit.press_en = 1; + pwr_ctrl_.bit.temp_en = 1; + // Disable pressure if no sensor defined + // keep temperature enabled since it's needed for compensation + if (this->pressure_sensor_ == nullptr) { + pwr_ctrl_.bit.press_en = 0; + this->pressure_oversampling_ = OVERSAMPLING_NONE; + } + // just disable oeversampling for temp if not used + if (this->temperature_sensor_ == nullptr) { + this->temperature_oversampling_ = OVERSAMPLING_NONE; + } + // Initialise the BMP388 oversampling register + if (!set_oversampling_register(this->pressure_oversampling_, this->temperature_oversampling_)) { + ESP_LOGE(TAG, "Failed to set oversampling register"); + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + return; + } +} + +void BMP3XXComponent::dump_config() { + ESP_LOGCONFIG(TAG, "BMP3XX:"); + ESP_LOGCONFIG(TAG, " Type: %s (0x%X)", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg); + LOG_I2C_DEVICE(this); + switch (this->error_code_) { + case NONE: + break; + case ERROR_COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Communication with BMP3XX failed!"); + break; + case ERROR_WRONG_CHIP_ID: + ESP_LOGE( + TAG, + "BMP3XX has wrong chip ID (reported id: 0x%X) - please check if you are really using a BMP 388 or BMP 390", + this->chip_id_.reg); + break; + case ERROR_SENSOR_RESET: + ESP_LOGE(TAG, "BMP3XX failed to reset"); + break; + default: + ESP_LOGE(TAG, "BMP3XX error code %d", (int) this->error_code_); + break; + } + ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_filter_))); + LOG_UPDATE_INTERVAL(this); + if (this->temperature_sensor_) { + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_))); + } + if (this->pressure_sensor_) { + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_))); + } +} +float BMP3XXComponent::get_setup_priority() const { return setup_priority::DATA; } + +inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << uint8_t(over_sampling)); } + +void BMP3XXComponent::update() { + // Enable sensor + ESP_LOGV(TAG, "Sending conversion request..."); + float meas_time = 1.0f; + // Ref: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf 3.9.2 + meas_time += 2.02f * oversampling_to_time(this->temperature_oversampling_) + 0.163f; + meas_time += 2.02f * oversampling_to_time(this->pressure_oversampling_) + 0.392f; + meas_time += 0.234f; + if (!set_mode(FORCED_MODE)) { + ESP_LOGE(TAG, "Failed start forced mode"); + this->mark_failed(); + return; + } + + ESP_LOGVV(TAG, "measurement time %d", uint32_t(ceilf(meas_time))); + this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { + float temperature = 0.0f; + float pressure = 0.0f; + if (this->pressure_sensor_ != nullptr) { + if (!get_measurements(temperature, pressure)) { + ESP_LOGW(TAG, "Failed to read pressure and temperature - skipping update"); + this->status_set_warning(); + return; + } + ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure); + } else { + if (!get_temperature(temperature)) { + ESP_LOGW(TAG, "Failed to read temperature - skipping update"); + this->status_set_warning(); + return; + } + ESP_LOGD(TAG, "Got temperature=%.1f°C", temperature); + } + + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + if (this->pressure_sensor_ != nullptr) + this->pressure_sensor_->publish_state(pressure); + this->status_clear_warning(); + set_mode(SLEEP_MODE); + }); +} + +// Reset the BMP3XX +uint8_t BMP3XXComponent::reset() { + write_byte(BMP388_CMD, RESET_CODE); // Write the reset code to the command register + // Wait for 10ms + delay(10); + this->read_byte(BMP388_EVENT, &event_.reg); // Read the BMP388's event register + return event_.bit.por_detected; // Return if device reset is complete +} + +// Start a one shot measurement in FORCED_MODE +bool BMP3XXComponent::start_forced_conversion() { + // Only set FORCED_MODE if we're already in SLEEP_MODE + if (pwr_ctrl_.bit.mode == SLEEP_MODE) { + return set_mode(FORCED_MODE); + } + return true; +} + +// Stop the conversion and return to SLEEP_MODE +bool BMP3XXComponent::stop_conversion() { return set_mode(SLEEP_MODE); } + +// Set the pressure oversampling rate +bool BMP3XXComponent::set_pressure_oversampling(Oversampling oversampling) { + osr_.bit.osr_p = oversampling; + return this->write_byte(BMP388_OSR, osr_.reg); +} + +// Set the temperature oversampling rate +bool BMP3XXComponent::set_temperature_oversampling(Oversampling oversampling) { + osr_.bit.osr_t = oversampling; + return this->write_byte(BMP388_OSR, osr_.reg); +} + +// Set the IIR filter setting +bool BMP3XXComponent::set_iir_filter(IIRFilter iir_filter) { + config_.bit.iir_filter = iir_filter; + return this->write_byte(BMP388_CONFIG, config_.reg); +} + +// Get temperature +bool BMP3XXComponent::get_temperature(float &temperature) { + // Check if a measurement is ready + if (!data_ready()) { + return false; + } + uint8_t data[3]; + // Read the temperature + if (!this->read_bytes(BMP388_DATA_3, &data[0], 3)) { + ESP_LOGE(TAG, "Failed to read temperature"); + return false; + } + // Copy the temperature data into the adc variables + int32_t adc_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0]; + // Temperature compensation (function from BMP388 datasheet) + temperature = bmp388_compensate_temperature_((float) adc_temp); + return true; +} + +// Get the pressure +bool BMP3XXComponent::get_pressure(float &pressure) { + float temperature; + return get_measurements(temperature, pressure); +} + +// Get temperature and pressure +bool BMP3XXComponent::get_measurements(float &temperature, float &pressure) { + // Check if a measurement is ready + if (!data_ready()) { + ESP_LOGD(TAG, "BMP3XX Get measurement - data not ready skipping update"); + return false; + } + + uint8_t data[6]; + // Read the temperature and pressure data + if (!this->read_bytes(BMP388_DATA_0, &data[0], 6)) { + ESP_LOGE(TAG, "Failed to read measurements"); + return false; + } + // Copy the temperature and pressure data into the adc variables + int32_t adc_pres = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0]; + int32_t adc_temp = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3]; + + // Temperature compensation (function from BMP388 datasheet) + temperature = bmp388_compensate_temperature_((float) adc_temp); + // Pressure compensation (function from BMP388 datasheet) + pressure = bmp388_compensate_pressure_((float) adc_pres, temperature); + // Calculate the pressure in millibar/hPa + pressure /= 100.0f; + return true; +} + +// Set the BMP388's mode in the power control register +bool BMP3XXComponent::set_mode(OperationMode mode) { + pwr_ctrl_.bit.mode = mode; + return this->write_byte(BMP388_PWR_CTRL, pwr_ctrl_.reg); +} + +// Set the BMP388 oversampling register +bool BMP3XXComponent::set_oversampling_register(Oversampling pressure_oversampling, + Oversampling temperature_oversampling) { + osr_.reg = temperature_oversampling << 3 | pressure_oversampling; + return this->write_byte(BMP388_OSR, osr_.reg); +} + +// Check if measurement data is ready +bool BMP3XXComponent::data_ready() { + // If we're in SLEEP_MODE return immediately + if (pwr_ctrl_.bit.mode == SLEEP_MODE) { + ESP_LOGD(TAG, "Not ready - sensor is in sleep mode"); + return false; + } + // Read the interrupt status register + uint8_t status; + if (!this->read_byte(BMP388_INT_STATUS, &status)) { + ESP_LOGE(TAG, "Failed to read status register"); + return false; + } + int_status_.reg = status; + ESP_LOGVV(TAG, "data ready status %d", status); + // If we're in FORCED_MODE switch back to SLEEP_MODE + if (int_status_.bit.drdy) { + if (pwr_ctrl_.bit.mode == FORCED_MODE) { + pwr_ctrl_.bit.mode = SLEEP_MODE; + } + return true; // The measurement is ready + } + return false; // The measurement is still pending +} + +//////////////////////////////////////////////////////////////////////////////// +// Bosch BMP3XXComponent (Private) Member Functions +//////////////////////////////////////////////////////////////////////////////// + +float BMP3XXComponent::bmp388_compensate_temperature_(float uncomp_temp) { + float partial_data1 = uncomp_temp - compensation_float_params_.param_T1; + float partial_data2 = partial_data1 * compensation_float_params_.param_T2; + return partial_data2 + partial_data1 * partial_data1 * compensation_float_params_.param_T3; +} + +float BMP3XXComponent::bmp388_compensate_pressure_(float uncomp_press, float t_lin) { + float partial_data1 = compensation_float_params_.param_P6 * t_lin; + float partial_data2 = compensation_float_params_.param_P7 * t_lin * t_lin; + float partial_data3 = compensation_float_params_.param_P8 * t_lin * t_lin * t_lin; + float partial_out1 = compensation_float_params_.param_P5 + partial_data1 + partial_data2 + partial_data3; + partial_data1 = compensation_float_params_.param_P2 * t_lin; + partial_data2 = compensation_float_params_.param_P3 * t_lin * t_lin; + partial_data3 = compensation_float_params_.param_P4 * t_lin * t_lin * t_lin; + float partial_out2 = + uncomp_press * (compensation_float_params_.param_P1 + partial_data1 + partial_data2 + partial_data3); + partial_data1 = uncomp_press * uncomp_press; + partial_data2 = compensation_float_params_.param_P9 + compensation_float_params_.param_P10 * t_lin; + partial_data3 = partial_data1 * partial_data2; + float partial_data4 = + partial_data3 + uncomp_press * uncomp_press * uncomp_press * compensation_float_params_.param_P11; + return partial_out1 + partial_out2 + partial_data4; +} + +} // namespace bmp3xx +} // namespace esphome diff --git a/esphome/components/bmp3xx/bmp3xx.h b/esphome/components/bmp3xx/bmp3xx.h new file mode 100644 index 0000000000..ab20abfe9b --- /dev/null +++ b/esphome/components/bmp3xx/bmp3xx.h @@ -0,0 +1,237 @@ +/* + based on BMP388_DEV by Martin Lindupp + under MIT License (MIT) + Copyright (C) Martin Lindupp 2020 + http://github.com/MartinL1/BMP388_DEV +*/ + +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace bmp3xx { + +static const uint8_t BMP388_ID = 0x50; // The BMP388 device ID +static const uint8_t BMP390_ID = 0x60; // The BMP390 device ID +static const uint8_t RESET_CODE = 0xB6; // The BMP388 reset code + +/// BMP388_DEV Registers +enum { + BMP388_CHIP_ID = 0x00, // Chip ID register sub-address + BMP388_ERR_REG = 0x02, // Error register sub-address + BMP388_STATUS = 0x03, // Status register sub-address + BMP388_DATA_0 = 0x04, // Pressure eXtended Least Significant Byte (XLSB) register sub-address + BMP388_DATA_1 = 0x05, // Pressure Least Significant Byte (LSB) register sub-address + BMP388_DATA_2 = 0x06, // Pressure Most Significant Byte (MSB) register sub-address + BMP388_DATA_3 = 0x07, // Temperature eXtended Least Significant Byte (XLSB) register sub-address + BMP388_DATA_4 = 0x08, // Temperature Least Significant Byte (LSB) register sub-address + BMP388_DATA_5 = 0x09, // Temperature Most Significant Byte (MSB) register sub-address + BMP388_SENSORTIME_0 = 0x0C, // Sensor time register 0 sub-address + BMP388_SENSORTIME_1 = 0x0D, // Sensor time register 1 sub-address + BMP388_SENSORTIME_2 = 0x0E, // Sensor time register 2 sub-address + BMP388_EVENT = 0x10, // Event register sub-address + BMP388_INT_STATUS = 0x11, // Interrupt Status register sub-address + BMP388_INT_CTRL = 0x19, // Interrupt Control register sub-address + BMP388_IF_CONFIG = 0x1A, // Interface Configuration register sub-address + BMP388_PWR_CTRL = 0x1B, // Power Control register sub-address + BMP388_OSR = 0x1C, // Oversampling register sub-address + BMP388_ODR = 0x1D, // Output Data Rate register sub-address + BMP388_CONFIG = 0x1F, // Configuration register sub-address + BMP388_TRIM_PARAMS = 0x31, // Trim parameter registers' base sub-address + BMP388_CMD = 0x7E // Command register sub-address +}; + +/// Device mode bitfield in the control and measurement register +enum OperationMode { SLEEP_MODE = 0x00, FORCED_MODE = 0x01, NORMAL_MODE = 0x03 }; + +/// Oversampling bit fields in the control and measurement register +enum Oversampling { + OVERSAMPLING_NONE = 0x00, + OVERSAMPLING_X2 = 0x01, + OVERSAMPLING_X4 = 0x02, + OVERSAMPLING_X8 = 0x03, + OVERSAMPLING_X16 = 0x04, + OVERSAMPLING_X32 = 0x05 +}; + +/// Infinite Impulse Response (IIR) filter bit field in the configuration register +enum IIRFilter { + IIR_FILTER_OFF = 0x00, + IIR_FILTER_2 = 0x01, + IIR_FILTER_4 = 0x02, + IIR_FILTER_8 = 0x03, + IIR_FILTER_16 = 0x04, + IIR_FILTER_32 = 0x05, + IIR_FILTER_64 = 0x06, + IIR_FILTER_128 = 0x07 +}; + +/// This class implements support for the BMP3XX Temperature+Pressure i2c sensor. +class BMP3XXComponent : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + + /// Set the oversampling value for the temperature sensor. Default is 16x. + void set_temperature_oversampling_config(Oversampling temperature_oversampling) { + this->temperature_oversampling_ = temperature_oversampling; + } + /// Set the oversampling value for the pressure sensor. Default is 16x. + void set_pressure_oversampling_config(Oversampling pressure_oversampling) { + this->pressure_oversampling_ = pressure_oversampling; + } + /// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter. + void set_iir_filter_config(IIRFilter iir_filter) { this->iir_filter_ = iir_filter; } + + /// Soft reset the sensor + uint8_t reset(); + /// Start continuous measurement in NORMAL_MODE + bool start_normal_conversion(); + /// Start a one shot measurement in FORCED_MODE + bool start_forced_conversion(); + /// Stop the conversion and return to SLEEP_MODE + bool stop_conversion(); + /// Set the pressure oversampling: OFF, X1, X2, X4, X8, X16, X32 + bool set_pressure_oversampling(Oversampling pressure_oversampling); + /// Set the temperature oversampling: OFF, X1, X2, X4, X8, X16, X32 + bool set_temperature_oversampling(Oversampling temperature_oversampling); + /// Set the IIR filter setting: OFF, 2, 3, 8, 16, 32 + bool set_iir_filter(IIRFilter iir_filter); + /// Get a temperature measurement + bool get_temperature(float &temperature); + /// Get a pressure measurement + bool get_pressure(float &pressure); + /// Get a temperature and pressure measurement + bool get_measurements(float &temperature, float &pressure); + /// Get a temperature and pressure measurement + bool get_measurement(); + /// Set the barometer mode + bool set_mode(OperationMode mode); + /// Set the BMP388 oversampling register + bool set_oversampling_register(Oversampling pressure_oversampling, Oversampling temperature_oversampling); + /// Checks if a measurement is ready + bool data_ready(); + + protected: + Oversampling temperature_oversampling_{OVERSAMPLING_X16}; + Oversampling pressure_oversampling_{OVERSAMPLING_X16}; + IIRFilter iir_filter_{IIR_FILTER_OFF}; + OperationMode operation_mode_{FORCED_MODE}; + sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_; + enum ErrorCode { + NONE = 0, + ERROR_COMMUNICATION_FAILED, + ERROR_WRONG_CHIP_ID, + ERROR_SENSOR_STATUS, + ERROR_SENSOR_RESET, + } error_code_{NONE}; + + struct { // The BMP388 compensation trim parameters (coefficients) + uint16_t param_T1; + uint16_t param_T2; + int8_t param_T3; + int16_t param_P1; + int16_t param_P2; + int8_t param_P3; + int8_t param_P4; + uint16_t param_P5; + uint16_t param_P6; + int8_t param_P7; + int8_t param_P8; + int16_t param_P9; + int8_t param_P10; + int8_t param_P11; + } __attribute__((packed)) compensation_params_; + + struct FloatParams { // The BMP388 float point compensation trim parameters + float param_T1; + float param_T2; + float param_T3; + float param_P1; + float param_P2; + float param_P3; + float param_P4; + float param_P5; + float param_P6; + float param_P7; + float param_P8; + float param_P9; + float param_P10; + float param_P11; + } compensation_float_params_; + + union { // Copy of the BMP388's chip id register + struct { + uint8_t chip_id_nvm : 4; + uint8_t chip_id_fixed : 4; + } bit; + uint8_t reg; + } chip_id_ = {.reg = 0}; + + union { // Copy of the BMP388's event register + struct { + uint8_t por_detected : 1; + } bit; + uint8_t reg; + } event_ = {.reg = 0}; + + union { // Copy of the BMP388's interrupt status register + struct { + uint8_t fwm_int : 1; + uint8_t ffull_int : 1; + uint8_t : 1; + uint8_t drdy : 1; + } bit; + uint8_t reg; + } int_status_ = {.reg = 0}; + + union { // Copy of the BMP388's power control register + struct { + uint8_t press_en : 1; + uint8_t temp_en : 1; + uint8_t : 2; + uint8_t mode : 2; + } bit; + uint8_t reg; + } pwr_ctrl_ = {.reg = 0}; + + union { // Copy of the BMP388's oversampling register + struct { + uint8_t osr_p : 3; + uint8_t osr_t : 3; + } bit; + uint8_t reg; + } osr_ = {.reg = 0}; + + union { // Copy of the BMP388's output data rate register + struct { + uint8_t odr_sel : 5; + } bit; + uint8_t reg; + } odr_ = {.reg = 0}; + + union { // Copy of the BMP388's configuration register + struct { + uint8_t : 1; + uint8_t iir_filter : 3; + } bit; + uint8_t reg; + } config_ = {.reg = 0}; + + // Bosch temperature compensation function + float bmp388_compensate_temperature_(float uncomp_temp); + // Bosch pressure compensation function + float bmp388_compensate_pressure_(float uncomp_press, float t_lin); +}; + +} // namespace bmp3xx +} // namespace esphome diff --git a/esphome/components/bmp3xx/sensor.py b/esphome/components/bmp3xx/sensor.py new file mode 100644 index 0000000000..736e6df3d8 --- /dev/null +++ b/esphome/components/bmp3xx/sensor.py @@ -0,0 +1,100 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_IIR_FILTER, + CONF_OVERSAMPLING, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, +) + +CODEOWNERS = ["@martgras"] +DEPENDENCIES = ["i2c"] + +bmp3xx_ns = cg.esphome_ns.namespace("bmp3xx") +Oversampling = bmp3xx_ns.enum("Oversampling") +OVERSAMPLING_OPTIONS = { + "NONE": Oversampling.OVERSAMPLING_NONE, + "2X": Oversampling.OVERSAMPLING_X2, + "4X": Oversampling.OVERSAMPLING_X4, + "8X": Oversampling.OVERSAMPLING_X8, + "16X": Oversampling.OVERSAMPLING_X16, + "32x": Oversampling.OVERSAMPLING_X32, +} + +IIRFilter = bmp3xx_ns.enum("IIRFilter") +IIR_FILTER_OPTIONS = { + "OFF": IIRFilter.IIR_FILTER_OFF, + "2X": IIRFilter.IIR_FILTER_2, + "4X": IIRFilter.IIR_FILTER_4, + "8X": IIRFilter.IIR_FILTER_8, + "16X": IIRFilter.IIR_FILTER_16, + "32X": IIRFilter.IIR_FILTER_32, + "64X": IIRFilter.IIR_FILTER_64, + "128X": IIRFilter.IIR_FILTER_128, +} + +BMP3XXComponent = bmp3xx_ns.class_( + "BMP3XXComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BMP3XXComponent), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="2X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( + IIR_FILTER_OPTIONS, upper=True + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x77)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER])) + if CONF_TEMPERATURE in config: + conf = config[CONF_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_temperature_sensor(sens)) + cg.add(var.set_temperature_oversampling_config(conf[CONF_OVERSAMPLING])) + + if CONF_PRESSURE in config: + conf = config[CONF_PRESSURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_pressure_sensor(sens)) + cg.add(var.set_pressure_oversampling_config(conf[CONF_OVERSAMPLING])) diff --git a/tests/test5.yaml b/tests/test5.yaml index 37e65e7da2..40db645097 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -180,6 +180,15 @@ sensor: co2: name: CO2 Sensor + - platform: bmp3xx + temperature: + name: "BMP Temperature" + oversampling: 16x + pressure: + name: "BMP Pressure" + address: 0x77 + iir_filter: 2X + script: - id: automation_test then: