From 5811389891e814b138eb8dd5236d730c9c0c581b Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 15:49:20 +0100 Subject: [PATCH] BH1750 dynamically calculate options (#3214) * BH1750 dynamically calculate options * Fix tests * Fix NAN * Convert setup to new-style * Add myself as codeowner --- CODEOWNERS | 1 + esphome/components/bh1750/bh1750.cpp | 184 +++++++++++++++++++-------- esphome/components/bh1750/bh1750.h | 27 +--- esphome/components/bh1750/sensor.py | 24 +--- tests/test1.yaml | 2 - 5 files changed, 142 insertions(+), 96 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index d2971e7c73..61469c53fa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -28,6 +28,7 @@ esphome/components/atc_mithermometer/* @ahpohl esphome/components/b_parasite/* @rbaron esphome/components/ballu/* @bazuchan esphome/components/bang_bang/* @OttoWinter +esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/bl0940/* @tobias- esphome/components/ble_client/* @buxtronix diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 4e6bb3c563..de6d811ed2 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -9,18 +9,109 @@ static const char *const TAG = "bh1750.sensor"; static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001; static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000; // last 3 bits static const uint8_t BH1750_COMMAND_MT_REG_LO = 0b01100000; // last 5 bits +static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011; +static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000; +static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001; + +/* +bh1750 properties: + +L-resolution mode: +- resolution 4lx (@ mtreg=69) +- measurement time: typ=16ms, max=24ms, scaled by MTreg value divided by 69 +- formula: counts / 1.2 * (69 / MTreg) lx +H-resolution mode: +- resolution 1lx (@ mtreg=69) +- measurement time: typ=120ms, max=180ms, scaled by MTreg value divided by 69 +- formula: counts / 1.2 * (69 / MTreg) lx +H-resolution mode2: +- resolution 0.5lx (@ mtreg=69) +- measurement time: typ=120ms, max=180ms, scaled by MTreg value divided by 69 +- formula: counts / 1.2 * (69 / MTreg) / 2 lx + +MTreg: +- min=31, default=69, max=254 + +-> only reason to use l-resolution is faster, but offers no higher range +-> below ~7000lx, makes sense to use H-resolution2 @ MTreg=254 +-> try to maximize MTreg to get lowest noise level +*/ void BH1750Sensor::setup() { ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str()); - if (!this->write_bytes(BH1750_COMMAND_POWER_ON, nullptr, 0)) { + uint8_t turn_on = BH1750_COMMAND_POWER_ON; + if (this->write(&turn_on, 1) != i2c::ERROR_OK) { this->mark_failed(); return; } +} - uint8_t mtreg_hi = (this->measurement_duration_ >> 5) & 0b111; - uint8_t mtreg_lo = (this->measurement_duration_ >> 0) & 0b11111; - this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0); - this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0); +void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function &f) { + // turn on (after one-shot sensor automatically powers down) + uint8_t turn_on = BH1750_COMMAND_POWER_ON; + if (this->write(&turn_on, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Turning on BH1750 failed"); + f(NAN); + return; + } + + if (active_mtreg_ != mtreg) { + // set mtreg + uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111); + uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111); + if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Setting measurement time for BH1750 failed"); + active_mtreg_ = 0; + f(NAN); + return; + } + active_mtreg_ = mtreg; + } + + uint8_t cmd; + uint16_t meas_time; + switch (mode) { + case BH1750_MODE_L: + cmd = BH1750_COMMAND_ONE_TIME_L; + meas_time = 24 * mtreg / 69; + break; + case BH1750_MODE_H: + cmd = BH1750_COMMAND_ONE_TIME_H; + meas_time = 180 * mtreg / 69; + break; + case BH1750_MODE_H2: + cmd = BH1750_COMMAND_ONE_TIME_H2; + meas_time = 180 * mtreg / 69; + break; + default: + f(NAN); + return; + } + if (this->write(&cmd, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Starting measurement for BH1750 failed"); + f(NAN); + return; + } + + // probably not needed, but adjust for rounding + meas_time++; + + this->set_timeout("read", meas_time, [this, mode, mtreg, f]() { + uint16_t raw_value; + if (this->read(reinterpret_cast(&raw_value), 2) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Reading BH1750 data failed"); + f(NAN); + return; + } + raw_value = i2c::i2ctohs(raw_value); + + float lx = float(raw_value) / 1.2f; + lx *= 69.0f / mtreg; + if (mode == BH1750_MODE_H2) + lx /= 2.0f; + + f(lx); + }); } void BH1750Sensor::dump_config() { @@ -30,64 +121,49 @@ void BH1750Sensor::dump_config() { ESP_LOGE(TAG, "Communication with BH1750 failed!"); } - const char *resolution_s; - switch (this->resolution_) { - case BH1750_RESOLUTION_0P5_LX: - resolution_s = "0.5"; - break; - case BH1750_RESOLUTION_1P0_LX: - resolution_s = "1"; - break; - case BH1750_RESOLUTION_4P0_LX: - resolution_s = "4"; - break; - default: - resolution_s = "Unknown"; - break; - } - ESP_LOGCONFIG(TAG, " Resolution: %s", resolution_s); LOG_UPDATE_INTERVAL(this); } void BH1750Sensor::update() { - if (!this->write_bytes(this->resolution_, nullptr, 0)) - return; + // first do a quick measurement in L-mode with full range + // to find right range + this->read_lx_(BH1750_MODE_L, 31, [this](float val) { + if (std::isnan(val)) { + this->status_set_warning(); + this->publish_state(NAN); + return; + } - uint32_t wait = 0; - // use max conversion times - switch (this->resolution_) { - case BH1750_RESOLUTION_0P5_LX: - case BH1750_RESOLUTION_1P0_LX: - wait = 180; - break; - case BH1750_RESOLUTION_4P0_LX: - wait = 24; - break; - } + BH1750Mode use_mode; + uint8_t use_mtreg; + if (val <= 7000) { + use_mode = BH1750_MODE_H2; + use_mtreg = 254; + } else { + use_mode = BH1750_MODE_H; + // lx = counts / 1.2 * (69 / mtreg) + // -> mtreg = counts / 1.2 * (69 / lx) + // calculate for counts=50000 (allow some range to not saturate, but maximize mtreg) + // -> mtreg = 50000*(10/12)*(69/lx) + int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val); + use_mtreg = std::min(254, std::max(31, ideal_mtreg)); + } + ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg); - this->set_timeout("illuminance", wait, [this]() { this->read_data_(); }); + this->read_lx_(use_mode, use_mtreg, [this](float val) { + if (std::isnan(val)) { + this->status_set_warning(); + this->publish_state(NAN); + return; + } + ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), val); + this->status_clear_warning(); + this->publish_state(val); + }); + }); } float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } -void BH1750Sensor::read_data_() { - uint16_t raw_value; - if (this->read(reinterpret_cast(&raw_value), 2) != i2c::ERROR_OK) { - this->status_set_warning(); - return; - } - raw_value = i2c::i2ctohs(raw_value); - - float lx = float(raw_value) / 1.2f; - lx *= 69.0f / this->measurement_duration_; - if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) { - lx /= 2.0f; - } - ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); - this->publish_state(lx); - this->status_clear_warning(); -} - -void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; } } // namespace bh1750 } // namespace esphome diff --git a/esphome/components/bh1750/bh1750.h b/esphome/components/bh1750/bh1750.h index c88fa10832..a31eb33609 100644 --- a/esphome/components/bh1750/bh1750.h +++ b/esphome/components/bh1750/bh1750.h @@ -7,29 +7,15 @@ namespace esphome { namespace bh1750 { -/// Enum listing all resolutions that can be used with the BH1750 -enum BH1750Resolution { - BH1750_RESOLUTION_4P0_LX = 0b00100011, // one-time low resolution mode - BH1750_RESOLUTION_1P0_LX = 0b00100000, // one-time high resolution mode 1 - BH1750_RESOLUTION_0P5_LX = 0b00100001, // one-time high resolution mode 2 +enum BH1750Mode { + BH1750_MODE_L, + BH1750_MODE_H, + BH1750_MODE_H2, }; /// This class implements support for the i2c-based BH1750 ambient light sensor. class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { public: - /** Set the resolution of this sensor. - * - * Possible values are: - * - * - `BH1750_RESOLUTION_4P0_LX` - * - `BH1750_RESOLUTION_1P0_LX` - * - `BH1750_RESOLUTION_0P5_LX` (default) - * - * @param resolution The new resolution of the sensor. - */ - void set_resolution(BH1750Resolution resolution); - void set_measurement_duration(uint8_t measurement_duration) { measurement_duration_ = measurement_duration; } - // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) void setup() override; @@ -38,10 +24,9 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c: float get_setup_priority() const override; protected: - void read_data_(); + void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function &f); - BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX}; - uint8_t measurement_duration_; + uint8_t active_mtreg_{0}; }; } // namespace bh1750 diff --git a/esphome/components/bh1750/sensor.py b/esphome/components/bh1750/sensor.py index 904e716eb8..69778f49ce 100644 --- a/esphome/components/bh1750/sensor.py +++ b/esphome/components/bh1750/sensor.py @@ -2,28 +2,20 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_RESOLUTION, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, UNIT_LUX, - CONF_MEASUREMENT_DURATION, ) DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@OttoWinter"] bh1750_ns = cg.esphome_ns.namespace("bh1750") -BH1750Resolution = bh1750_ns.enum("BH1750Resolution") -BH1750_RESOLUTIONS = { - 4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX, - 1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX, - 0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX, -} BH1750Sensor = bh1750_ns.class_( "BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice ) -CONF_MEASUREMENT_TIME = "measurement_time" CONFIG_SCHEMA = ( sensor.sensor_schema( BH1750Sensor, @@ -34,14 +26,11 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum( - BH1750_RESOLUTIONS, float=True + cv.Optional("resolution"): cv.invalid( + "The 'resolution' option has been removed. The optimal value is now dynamically calculated." ), - cv.Optional(CONF_MEASUREMENT_DURATION, default=69): cv.int_range( - min=31, max=254 - ), - cv.Optional(CONF_MEASUREMENT_TIME): cv.invalid( - "The 'measurement_time' option has been replaced with 'measurement_duration' in 1.18.0" + cv.Optional("measurement_duration"): cv.invalid( + "The 'measurement_duration' option has been removed. The optimal value is now dynamically calculated." ), } ) @@ -54,6 +43,3 @@ async def to_code(config): var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - - cg.add(var.set_resolution(config[CONF_RESOLUTION])) - cg.add(var.set_measurement_duration(config[CONF_MEASUREMENT_DURATION])) diff --git a/tests/test1.yaml b/tests/test1.yaml index 4e82d06485..2acb420fb5 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -456,12 +456,10 @@ sensor: name: "Living Room Brightness 3" internal: true address: 0x23 - resolution: 1.0 update_interval: 30s retain: False availability: state_topic: livingroom/custom_state_topic - measurement_duration: 31 i2c_id: i2c_bus - platform: max44009 name: "Outside Brightness 1"