mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	BH1750 dynamically calculate options (#3214)
* BH1750 dynamically calculate options * Fix tests * Fix NAN * Convert setup to new-style * Add myself as codeowner
This commit is contained in:
		| @@ -28,6 +28,7 @@ esphome/components/atc_mithermometer/* @ahpohl | |||||||
| esphome/components/b_parasite/* @rbaron | esphome/components/b_parasite/* @rbaron | ||||||
| esphome/components/ballu/* @bazuchan | esphome/components/ballu/* @bazuchan | ||||||
| esphome/components/bang_bang/* @OttoWinter | esphome/components/bang_bang/* @OttoWinter | ||||||
|  | esphome/components/bh1750/* @OttoWinter | ||||||
| esphome/components/binary_sensor/* @esphome/core | esphome/components/binary_sensor/* @esphome/core | ||||||
| esphome/components/bl0940/* @tobias- | esphome/components/bl0940/* @tobias- | ||||||
| esphome/components/ble_client/* @buxtronix | esphome/components/ble_client/* @buxtronix | ||||||
|   | |||||||
| @@ -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_POWER_ON = 0b00000001; | ||||||
| static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000;  // last 3 bits | 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_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() { | void BH1750Sensor::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str()); |   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(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|   uint8_t mtreg_hi = (this->measurement_duration_ >> 5) & 0b111; | void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) { | ||||||
|   uint8_t mtreg_lo = (this->measurement_duration_ >> 0) & 0b11111; |   // turn on (after one-shot sensor automatically powers down) | ||||||
|   this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0); |   uint8_t turn_on = BH1750_COMMAND_POWER_ON; | ||||||
|   this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0); |   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<uint8_t *>(&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() { | void BH1750Sensor::dump_config() { | ||||||
| @@ -30,64 +121,49 @@ void BH1750Sensor::dump_config() { | |||||||
|     ESP_LOGE(TAG, "Communication with BH1750 failed!"); |     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); |   LOG_UPDATE_INTERVAL(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BH1750Sensor::update() { | void BH1750Sensor::update() { | ||||||
|   if (!this->write_bytes(this->resolution_, nullptr, 0)) |   // 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; |       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; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   this->set_timeout("illuminance", wait, [this]() { this->read_data_(); }); |     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->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; } | float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } | ||||||
| void BH1750Sensor::read_data_() { |  | ||||||
|   uint16_t raw_value; |  | ||||||
|   if (this->read(reinterpret_cast<uint8_t *>(&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 bh1750 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -7,29 +7,15 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace bh1750 { | namespace bh1750 { | ||||||
|  |  | ||||||
| /// Enum listing all resolutions that can be used with the BH1750 | enum BH1750Mode { | ||||||
| enum BH1750Resolution { |   BH1750_MODE_L, | ||||||
|   BH1750_RESOLUTION_4P0_LX = 0b00100011,  // one-time low resolution mode |   BH1750_MODE_H, | ||||||
|   BH1750_RESOLUTION_1P0_LX = 0b00100000,  // one-time high resolution mode 1 |   BH1750_MODE_H2, | ||||||
|   BH1750_RESOLUTION_0P5_LX = 0b00100001,  // one-time high resolution mode 2 |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// This class implements support for the i2c-based BH1750 ambient light sensor. | /// This class implements support for the i2c-based BH1750 ambient light sensor. | ||||||
| class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { | class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { | ||||||
|  public: |  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 ========== |   // ========== INTERNAL METHODS ========== | ||||||
|   // (In most use cases you won't need these) |   // (In most use cases you won't need these) | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -38,10 +24,9 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c: | |||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void read_data_(); |   void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f); | ||||||
|  |  | ||||||
|   BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX}; |   uint8_t active_mtreg_{0}; | ||||||
|   uint8_t measurement_duration_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace bh1750 | }  // namespace bh1750 | ||||||
|   | |||||||
| @@ -2,28 +2,20 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_RESOLUTION, |  | ||||||
|     DEVICE_CLASS_ILLUMINANCE, |     DEVICE_CLASS_ILLUMINANCE, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|     UNIT_LUX, |     UNIT_LUX, | ||||||
|     CONF_MEASUREMENT_DURATION, |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
|  | CODEOWNERS = ["@OttoWinter"] | ||||||
|  |  | ||||||
| bh1750_ns = cg.esphome_ns.namespace("bh1750") | 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 = bh1750_ns.class_( | ||||||
|     "BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice |     "BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONF_MEASUREMENT_TIME = "measurement_time" |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = ( | ||||||
|     sensor.sensor_schema( |     sensor.sensor_schema( | ||||||
|         BH1750Sensor, |         BH1750Sensor, | ||||||
| @@ -34,14 +26,11 @@ CONFIG_SCHEMA = ( | |||||||
|     ) |     ) | ||||||
|     .extend( |     .extend( | ||||||
|         { |         { | ||||||
|             cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum( |             cv.Optional("resolution"): cv.invalid( | ||||||
|                 BH1750_RESOLUTIONS, float=True |                 "The 'resolution' option has been removed. The optimal value is now dynamically calculated." | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_MEASUREMENT_DURATION, default=69): cv.int_range( |             cv.Optional("measurement_duration"): cv.invalid( | ||||||
|                 min=31, max=254 |                 "The 'measurement_duration' option has been removed. The optimal value is now dynamically calculated." | ||||||
|             ), |  | ||||||
|             cv.Optional(CONF_MEASUREMENT_TIME): cv.invalid( |  | ||||||
|                 "The 'measurement_time' option has been replaced with 'measurement_duration' in 1.18.0" |  | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
| @@ -54,6 +43,3 @@ async def to_code(config): | |||||||
|     var = await sensor.new_sensor(config) |     var = await sensor.new_sensor(config) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await i2c.register_i2c_device(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])) |  | ||||||
|   | |||||||
| @@ -456,12 +456,10 @@ sensor: | |||||||
|     name: "Living Room Brightness 3" |     name: "Living Room Brightness 3" | ||||||
|     internal: true |     internal: true | ||||||
|     address: 0x23 |     address: 0x23 | ||||||
|     resolution: 1.0 |  | ||||||
|     update_interval: 30s |     update_interval: 30s | ||||||
|     retain: False |     retain: False | ||||||
|     availability: |     availability: | ||||||
|     state_topic: livingroom/custom_state_topic |     state_topic: livingroom/custom_state_topic | ||||||
|     measurement_duration: 31 |  | ||||||
|     i2c_id: i2c_bus |     i2c_id: i2c_bus | ||||||
|   - platform: max44009 |   - platform: max44009 | ||||||
|     name: "Outside Brightness 1" |     name: "Outside Brightness 1" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user