mirror of
https://github.com/esphome/esphome.git
synced 2024-12-23 16:47:57 +01: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:
parent
debcaf6fb7
commit
5811389891
@ -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
|
||||||
return;
|
// 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;
|
BH1750Mode use_mode;
|
||||||
// use max conversion times
|
uint8_t use_mtreg;
|
||||||
switch (this->resolution_) {
|
if (val <= 7000) {
|
||||||
case BH1750_RESOLUTION_0P5_LX:
|
use_mode = BH1750_MODE_H2;
|
||||||
case BH1750_RESOLUTION_1P0_LX:
|
use_mtreg = 254;
|
||||||
wait = 180;
|
} else {
|
||||||
break;
|
use_mode = BH1750_MODE_H;
|
||||||
case BH1750_RESOLUTION_4P0_LX:
|
// lx = counts / 1.2 * (69 / mtreg)
|
||||||
wait = 24;
|
// -> mtreg = counts / 1.2 * (69 / lx)
|
||||||
break;
|
// 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; }
|
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"
|
||||||
|
Loading…
Reference in New Issue
Block a user