This commit is contained in:
Anton Viktorov 2024-05-16 11:26:28 +12:00 committed by GitHub
commit f5a7836941
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 669 additions and 0 deletions

View File

@ -51,6 +51,7 @@ esphome/components/bang_bang/* @OttoWinter
esphome/components/bedjet/* @jhansche
esphome/components/bedjet/climate/* @jhansche
esphome/components/bedjet/fan/* @jhansche
esphome/components/bh1745/* @latonita
esphome/components/bh1750/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core
esphome/components/bk72xx/* @kuba2k2

View File

@ -0,0 +1 @@
CODEOWNERS = ["@latonita"]

View File

@ -0,0 +1,289 @@
#include "bh1745.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <cmath>
namespace esphome {
namespace bh1745 {
static const char *const TAG = "bh1745";
static constexpr uint8_t BH1745_MANUFACTURER_ID = 0xE0;
static constexpr uint8_t BH1745_DEVICE_ID = 0b001011;
static constexpr uint8_t BH1745_RESET_TIMEOUT_MS = 100;
static constexpr uint8_t BH1745_BASE_MEAS_TIME_MS = 160;
static constexpr uint8_t BH1745_MAX_TRIES = 15;
static constexpr float CHANNEL_COMPENSATION[BH1745_CHANNELS] = {2.2f, 1.0f, 1.8f, 10.0f};
uint32_t get_measurement_time_ms(MeasurementTime time) {
return ((uint32_t) BH1745_BASE_MEAS_TIME_MS) << static_cast<uint8_t>(time);
}
uint8_t get_adc_gain(AdcGain gain) {
switch (gain) {
case AdcGain::GAIN_1X:
return 1;
case AdcGain::GAIN_2X:
return 2;
case AdcGain::GAIN_16X:
return 16;
default:
return 1;
}
}
void BH1745Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up BH1745");
uint8_t manuf_id = this->reg((uint8_t) Bh1745Registers::MANUFACTURER_ID).get();
if (manuf_id != BH1745_MANUFACTURER_ID) {
ESP_LOGW(TAG, "Manufacturer ID of BH1745 is not correct! Got 0x%02X, expected 0x%02X", manuf_id,
BH1745_MANUFACTURER_ID);
this->mark_failed();
return;
}
SystemControlRegister sys_ctrl;
sys_ctrl.raw = this->reg((uint8_t) Bh1745Registers::SYSTEM_CONTROL).get();
if (sys_ctrl.part_id != BH1745_DEVICE_ID) {
ESP_LOGW(TAG, "Device ID of BH1745 is not correct! Got 0x%02X, expected 0x%02X", sys_ctrl.part_id,
BH1745_DEVICE_ID);
this->mark_failed();
return;
}
sys_ctrl.sw_reset = true;
sys_ctrl.int_reset = false;
this->reg((uint8_t) Bh1745Registers::SYSTEM_CONTROL) = sys_ctrl.raw;
// only for pimoroni boards for now - there are LEDs connected to interrupt pin
uint16_t th_high[1] = {0xFFFF};
uint16_t th_low[1] = {0x0000};
this->write_bytes_16((uint8_t) Bh1745Registers::TH_LSB, th_low, 1);
this->write_bytes_16((uint8_t) Bh1745Registers::TL_LSB, th_high, 1);
this->reg((uint8_t) Bh1745Registers::INTERRUPT_REG) = 0x00;
this->set_timeout(BH1745_RESET_TIMEOUT_MS, [this]() {
this->configure_measurement_time_();
this->state_ = State::INITIAL_SETUP_COMPLETED;
});
}
void BH1745Component::dump_config() {
ESP_LOGCONFIG(TAG, "BH1745:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with BH1745 failed!");
}
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Gain: %dx", get_adc_gain(this->adc_gain_));
ESP_LOGCONFIG(TAG, " Integration time: %d ms", get_measurement_time_ms(this->measurement_time_));
ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_);
LOG_SENSOR(" ", "Red Counts", this->red_counts_sensor_);
LOG_SENSOR(" ", "Green Counts", this->green_counts_sensor_);
LOG_SENSOR(" ", "Blue Counts", this->blue_counts_sensor_);
LOG_SENSOR(" ", "Clear Counts", this->clear_counts_sensor_);
}
void BH1745Component::update() {
if (this->is_ready() && this->state_ == State::IDLE) {
ESP_LOGV(TAG, "Initiating new data collection");
this->state_ = State::MEASUREMENT_IN_PROGRESS;
this->readings_.red = 0;
this->readings_.green = 0;
this->readings_.blue = 0;
this->readings_.clear = 0;
this->readings_.tries = 0;
ModeControl2Register mode_ctrl2{0};
mode_ctrl2.adc_gain = this->adc_gain_;
mode_ctrl2.rgbc_measurement_enable = true;
this->reg((uint8_t) Bh1745Registers::MODE_CONTROL2) = mode_ctrl2.raw;
this->set_timeout(get_measurement_time_ms(this->measurement_time_),
[this]() { this->state_ = State::WAITING_FOR_DATA; });
}
}
void BH1745Component::loop() {
if (this->is_ready()) {
switch (this->state_) {
case State::NOT_INITIALIZED:
break;
case State::INITIAL_SETUP_COMPLETED:
this->state_ = State::DELAYED_SETUP;
this->configure_gain_();
this->reg((uint8_t) Bh1745Registers::MODE_CONTROL3) = 0x02;
this->set_timeout(BH1745_RESET_TIMEOUT_MS, [this]() { this->state_ = State::IDLE; });
break;
case State::DELAYED_SETUP:
case State::IDLE:
case State::MEASUREMENT_IN_PROGRESS:
break;
case State::WAITING_FOR_DATA:
if (this->is_data_ready_(this->readings_)) {
this->read_data_(this->readings_);
this->state_ = State::DATA_COLLECTED;
return;
} else if (this->readings_.tries > BH1745_MAX_TRIES) {
ESP_LOGW(TAG, "Can't get data after several tries. Aborting.");
this->status_set_warning();
this->state_ = State::IDLE;
return;
} else {
this->readings_.tries++;
}
break;
case State::DATA_COLLECTED:
this->status_clear_warning();
this->publish_data_();
this->state_ = State::IDLE;
break;
default:
// wrong state
break;
}
}
}
float BH1745Component::get_setup_priority() const { return setup_priority::DATA; }
void BH1745Component::switch_led(bool on_off) {
// shall we require somehow that its a pimoroni board?
uint8_t raw = this->reg((uint8_t) Bh1745Registers::INTERRUPT_REG).get();
if (on_off) {
raw |= (1);
} else {
raw &= ~(1);
}
this->reg((uint8_t) Bh1745Registers::INTERRUPT_REG) = raw;
}
void BH1745Component::configure_measurement_time_() {
ModeControl1Register mode_ctrl1;
mode_ctrl1.reserved_3_7 = 0;
mode_ctrl1.measurement_time = this->measurement_time_;
this->reg((uint8_t) Bh1745Registers::MODE_CONTROL1) = mode_ctrl1.raw;
}
void BH1745Component::configure_gain_() {
ModeControl2Register mode_ctrl2;
mode_ctrl2.raw = this->reg((uint8_t) Bh1745Registers::MODE_CONTROL2).get();
mode_ctrl2.adc_gain = this->adc_gain_;
this->reg((uint8_t) Bh1745Registers::MODE_CONTROL2) = mode_ctrl2.raw;
}
bool BH1745Component::is_data_ready_(Readings &data) {
ModeControl2Register mode_ctrl2;
mode_ctrl2.raw = this->reg((uint8_t) Bh1745Registers::MODE_CONTROL2).get();
if (mode_ctrl2.valid) {
ModeControl1Register mode_ctrl1;
mode_ctrl1.raw = this->reg((uint8_t) Bh1745Registers::MODE_CONTROL1).get();
data.meas_time = mode_ctrl1.measurement_time;
data.gain = mode_ctrl2.adc_gain;
}
return mode_ctrl2.valid;
}
void BH1745Component::read_data_(BH1745Component::Readings &data) {
static uint8_t buffer[BH1745_CHANNELS * 2];
this->read_bytes((uint8_t) Bh1745Registers::RED_DATA_LSB, buffer, BH1745_CHANNELS * 2);
data.red = ((buffer[1] << 8) + buffer[0]) & 0xffff;
data.green = ((buffer[3] << 8) + buffer[2]) & 0xffff;
data.blue = ((buffer[5] << 8) + buffer[4]) & 0xffff;
data.clear = ((buffer[7] << 8) + buffer[6]) & 0xffff;
data.red *= CHANNEL_COMPENSATION[0];
data.green *= CHANNEL_COMPENSATION[1];
data.blue *= CHANNEL_COMPENSATION[2];
data.clear *= CHANNEL_COMPENSATION[3];
ESP_LOGV(TAG, "Red: %d, Green: %d, Blue: %d, Clear: %d", data.red, data.green, data.blue, data.clear);
}
float BH1745Component::calculate_lux_(Readings &data) {
float lx, lx_tmp;
float gain = get_adc_gain(data.gain);
float integration_time = get_measurement_time_ms(data.meas_time);
if (data.green < 1) {
lx_tmp = 0;
} else if (((float) data.clear / (float) data.green) < 0.160) {
lx_tmp = (0.202 * data.red + 0.766 * data.green);
} else {
lx_tmp = (0.159 * data.red + 0.646 * data.green);
}
if (lx_tmp < 0) {
lx_tmp = 0;
}
lx = lx_tmp / gain / integration_time * 160 / this->glass_attenuation_factor_;
ESP_LOGV(TAG, "Lux calculation: %.1f", lx);
return lx;
}
float BH1745Component::calculate_cct_(Readings &data) {
uint32_t all = data.red + data.green + data.blue;
if (data.green < 1 || all < 1)
return 0;
float r_ratio = (float) data.red / all;
float b_ratio = (float) data.blue / all;
float ct = 0;
if (((float) data.clear / (float) data.green) < 0.160) {
float b_eff = fmin(b_ratio * 3.13, 1);
ct = ((1 - b_eff) * 12746 * (exp(-2.911 * r_ratio))) + (b_eff * 1637 * (exp(4.865 * b_ratio)));
} else {
float b_eff = fmin(b_ratio * 10.67, 1);
ct = ((1 - b_eff) * 16234 * (exp(-2.781 * r_ratio))) + (b_eff * 1882 * (exp(4.448 * b_ratio)));
}
if (ct > 10000) {
ct = 10000;
}
ESP_LOGV(TAG, "CCT calculation: %.1f", ct);
return ct;
}
void BH1745Component::publish_data_() {
if (this->red_counts_sensor_ != nullptr) {
this->red_counts_sensor_->publish_state(this->readings_.red);
}
if (this->green_counts_sensor_ != nullptr) {
this->green_counts_sensor_->publish_state(this->readings_.green);
}
if (this->blue_counts_sensor_ != nullptr) {
this->blue_counts_sensor_->publish_state(this->readings_.blue);
}
if (this->clear_counts_sensor_ != nullptr) {
this->clear_counts_sensor_->publish_state(this->readings_.clear);
}
if (this->illuminance_sensor_ != nullptr) {
this->illuminance_sensor_->publish_state(this->calculate_lux_(this->readings_));
}
if (this->color_temperature_sensor_ != nullptr) {
this->color_temperature_sensor_->publish_state(this->calculate_cct_(this->readings_));
}
}
void BH1745Component::set_pimoroni_led_switch(switch_::Switch *led_switch) {
this->led_switch_ = led_switch;
if (this->led_switch_ != nullptr) {
static_cast<BH1745SwitchLed *>(this->led_switch_)->set_bh1745(this);
}
}
} // namespace bh1745
} // namespace esphome

View File

@ -0,0 +1,172 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/switch/switch.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace bh1745 {
enum class Bh1745Registers : uint8_t {
SYSTEM_CONTROL = 0x40,
MODE_CONTROL1 = 0x41,
MODE_CONTROL2 = 0x42,
MODE_CONTROL3 = 0x44,
RED_DATA_LSB = 0x50,
RED_DATA_MSB = 0x51,
GREEN_DATA_LSB = 0x52,
GREEN_DATA_MSB = 0x53,
BLUE_DATA_LSB = 0x54,
BLUE_DATA_MSB = 0x55,
CLEAR_DATA_LSB = 0x56,
CLEAR_DATA_MSB = 0x57,
DINT_DATA_LSB = 0x58,
DINT_DATA_MSB = 0x59,
INTERRUPT_REG = 0x60,
PERSISTENCE = 0x61,
TH_LSB = 0x62,
TH_MSB = 0x63,
TL_LSB = 0x64,
TL_MSB = 0x65,
MANUFACTURER_ID = 0x92,
};
enum MeasurementTime : uint8_t {
TIME_160MS = 0b000,
TIME_320MS = 0b001,
TIME_640MS = 0b010,
TIME_1280MS = 0b011,
TIME_2560MS = 0b100,
TIME_5120MS = 0b101,
};
enum AdcGain : uint8_t {
GAIN_1X = 0,
GAIN_2X,
GAIN_16X,
};
// 0x40
union SystemControlRegister {
uint8_t raw;
struct {
uint8_t part_id : 6;
uint8_t int_reset : 1;
uint8_t sw_reset : 1;
};
};
// 0x41
union ModeControl1Register {
u_int8_t raw;
struct {
MeasurementTime measurement_time : 3;
uint8_t reserved_3_7 : 5;
};
};
// 0x42
union ModeControl2Register {
u_int8_t raw;
struct {
AdcGain adc_gain : 2;
uint8_t reserved_2_3 : 2;
bool rgbc_measurement_enable : 1;
uint8_t reserved_5_6 : 2;
bool valid : 1;
};
};
constexpr uint8_t BH1745_CHANNELS = 4;
// 0x44
// always write 0x02
class BH1745Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void update() override;
void loop() override;
float get_setup_priority() const override;
void set_measurement_time(MeasurementTime measurement_time) { this->measurement_time_ = measurement_time; };
void set_adc_gain(AdcGain adc_gain) { this->adc_gain_ = adc_gain; };
void set_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; }
void set_red_counts_sensor(sensor::Sensor *red) { this->red_counts_sensor_ = red; }
void set_green_counts_sensor(sensor::Sensor *green) { this->green_counts_sensor_ = green; }
void set_blue_counts_sensor(sensor::Sensor *blue) { this->blue_counts_sensor_ = blue; }
void set_clear_counts_sensor(sensor::Sensor *clear) { this->clear_counts_sensor_ = clear; }
void set_illuminance_sensor(sensor::Sensor *illuminance) { this->illuminance_sensor_ = illuminance; }
void set_color_temperature_sensor(sensor::Sensor *cct) { this->color_temperature_sensor_ = cct; }
void set_pimoroni_led_switch(switch_::Switch *led_switch);
// only for Pimoroni board
void switch_led(bool on_off);
protected:
MeasurementTime measurement_time_{MeasurementTime::TIME_160MS};
AdcGain adc_gain_{AdcGain::GAIN_1X};
float glass_attenuation_factor_{1.0};
sensor::Sensor *red_counts_sensor_{nullptr};
sensor::Sensor *green_counts_sensor_{nullptr};
sensor::Sensor *blue_counts_sensor_{nullptr};
sensor::Sensor *clear_counts_sensor_{nullptr};
sensor::Sensor *illuminance_sensor_{nullptr};
sensor::Sensor *color_temperature_sensor_{nullptr};
switch_::Switch *led_switch_{nullptr};
enum class State : uint8_t {
NOT_INITIALIZED,
INITIAL_SETUP_COMPLETED,
DELAYED_SETUP,
IDLE,
MEASUREMENT_IN_PROGRESS,
WAITING_FOR_DATA,
DATA_COLLECTED,
} state_{State::NOT_INITIALIZED};
struct Readings {
uint16_t red;
uint16_t green;
uint16_t blue;
uint16_t clear;
AdcGain gain;
MeasurementTime meas_time;
uint8_t tries;
} readings_;
void configure_measurement_time_();
void configure_gain_();
bool is_data_ready_(Readings &data);
void read_data_(Readings &data);
float calculate_lux_(Readings &data);
float calculate_cct_(Readings &data);
void publish_data_();
};
class BH1745SwitchLed : public switch_::Switch, public Component {
public:
void set_bh1745(BH1745Component *bh1745) { this->bh1745_ = bh1745; }
protected:
void write_state(bool state) override {
if (this->bh1745_ != nullptr) {
this->bh1745_->switch_led(state);
publish_state(state);
}
};
BH1745Component *bh1745_;
};
} // namespace bh1745
} // namespace esphome

View File

@ -0,0 +1,157 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor, switch
from esphome.const import (
CONF_ID,
CONF_COLOR_TEMPERATURE,
CONF_GAIN,
CONF_GLASS_ATTENUATION_FACTOR,
CONF_ILLUMINANCE,
CONF_INTEGRATION_TIME,
CONF_NAME,
ICON_LIGHTBULB,
ICON_THERMOMETER,
STATE_CLASS_MEASUREMENT,
DEVICE_CLASS_ILLUMINANCE,
UNIT_KELVIN,
UNIT_LUX,
)
CODEOWNERS = ["@latonita"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensor", "switch"]
CONF_BH1745_ID = "bh1745_id"
UNIT_COUNTS = "#"
CONF_RED_CHANNEL = "red_channel"
CONF_GREEN_CHANNEL = "green_channel"
CONF_BLUE_CHANNEL = "blue_channel"
CONF_CLEAR_CHANNEL = "clear_channel"
CONF_PIMORONI_LED_SWITCH = "pimoroni_led_switch"
bh1745_ns = cg.esphome_ns.namespace("bh1745")
BH1745SComponent = bh1745_ns.class_(
"BH1745Component", cg.PollingComponent, i2c.I2CDevice
)
BH1745SSwitchLed = bh1745_ns.class_("BH1745SwitchLed", switch.Switch, cg.Component)
AdcGain = bh1745_ns.enum("AdcGain")
ADC_GAINS = {
"1X": AdcGain.GAIN_1X,
"2X": AdcGain.GAIN_2X,
"16X": AdcGain.GAIN_16X,
}
MeasurementTime = bh1745_ns.enum("MeasurementTime")
MEASUREMENT_TIMES = {
160: MeasurementTime.TIME_160MS,
320: MeasurementTime.TIME_320MS,
640: MeasurementTime.TIME_640MS,
1280: MeasurementTime.TIME_1280MS,
2560: MeasurementTime.TIME_2560MS,
5120: MeasurementTime.TIME_5120MS,
}
color_channel_schema = cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_LIGHTBULB,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
)
def validate_measurement_time(value):
value = cv.positive_time_period_milliseconds(value).total_milliseconds
return cv.enum(MEASUREMENT_TIMES, int=True)(value)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BH1745SComponent),
cv.Optional(CONF_GAIN, default="1X"): cv.enum(ADC_GAINS, upper=True),
cv.Optional(
CONF_INTEGRATION_TIME, default="160ms"
): validate_measurement_time,
cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range(
min=1.0
),
cv.Optional(CONF_RED_CHANNEL): color_channel_schema,
cv.Optional(CONF_GREEN_CHANNEL): color_channel_schema,
cv.Optional(CONF_BLUE_CHANNEL): color_channel_schema,
cv.Optional(CONF_CLEAR_CHANNEL): color_channel_schema,
cv.Optional(CONF_ILLUMINANCE): cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
icon=ICON_LIGHTBULB,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_COLOR_TEMPERATURE): cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_KELVIN,
icon=ICON_THERMOMETER,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_PIMORONI_LED_SWITCH): cv.maybe_simple_value(
switch.SWITCH_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BH1745SSwitchLed),
}
)
),
key=CONF_NAME,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x38))
)
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_adc_gain(config[CONF_GAIN]))
cg.add(var.set_measurement_time(config[CONF_INTEGRATION_TIME]))
cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR]))
if CONF_RED_CHANNEL in config:
sens = await sensor.new_sensor(config[CONF_RED_CHANNEL])
cg.add(var.set_red_counts_sensor(sens))
if CONF_GREEN_CHANNEL in config:
sens = await sensor.new_sensor(config[CONF_GREEN_CHANNEL])
cg.add(var.set_green_counts_sensor(sens))
if CONF_BLUE_CHANNEL in config:
sens = await sensor.new_sensor(config[CONF_BLUE_CHANNEL])
cg.add(var.set_blue_counts_sensor(sens))
if CONF_CLEAR_CHANNEL in config:
sens = await sensor.new_sensor(config[CONF_CLEAR_CHANNEL])
cg.add(var.set_clear_counts_sensor(sens))
if CONF_ILLUMINANCE in config:
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE])
cg.add(var.set_illuminance_sensor(sens))
if CONF_COLOR_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_COLOR_TEMPERATURE])
cg.add(var.set_color_temperature_sensor(sens))
if CONF_PIMORONI_LED_SWITCH in config:
sw = await switch.new_switch(config[CONF_PIMORONI_LED_SWITCH])
cg.add(var.set_pimoroni_led_switch(sw))

View File

@ -0,0 +1,13 @@
sensor:
- platform: bh1745
address: 0x38
i2c_id: i2c_bh1745
gain: 1X
integration_time: 160ms
red_channel: Red channel
green_channel: Green channel
blue_channel: Blue channel
clear_channel: Clear channel
illuminance: Illuminance
color_temperature: Color temperature
pimoroni_led_switch: LED Switch

View File

@ -0,0 +1,6 @@
i2c:
- id: i2c_bh1745
scl: 5
sda: 4
<<: !include common.yaml

View File

@ -0,0 +1,6 @@
i2c:
- id: i2c_bh1745
scl: 5
sda: 4
<<: !include common.yaml

View File

@ -0,0 +1,6 @@
i2c:
- id: i2c_bh1745
scl: 16
sda: 17
<<: !include common.yaml

View File

@ -0,0 +1,6 @@
i2c:
- id: i2c_bh1745
scl: 16
sda: 17
<<: !include common.yaml

View File

@ -0,0 +1,6 @@
i2c:
- id: i2c_bh1745
scl: 5
sda: 4
<<: !include common.yaml

View File

@ -0,0 +1,6 @@
i2c:
- id: i2c_bh1745
scl: 5
sda: 4
<<: !include common.yaml