1
0
mirror of https://github.com/esphome/esphome.git synced 2025-04-07 03:10:27 +01:00
This commit is contained in:
Anton Viktorov 2024-03-10 21:15:38 +01:00
parent 90f416bd0d
commit 8f9c42be9f
5 changed files with 549 additions and 0 deletions

View File

@ -47,6 +47,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,266 @@
#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;
this->set_timeout(BH1745_RESET_TIMEOUT_MS, [this]() {
this->configure_measurement_time_();
this->state_ = State::DELAYED_SETUP;
});
}
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);
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() {
switch (this->state_) {
case State::NOT_INITIALIZED:
break;
case 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::IDLE:
break;
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->publish_data_();
break;
default:
// wrong state
break;
}
}
float BH1745Component::get_setup_priority() const { return setup_priority::DATA; }
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_LOGD(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;
ESP_LOGD(TAG, "Lux calculation:%.0f", 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;
return roundf(ct);
/*
float cct;
float gain = get_adc_gain(data.gain);
float integration_time = get_measurement_time_ms(data.meas_time);
float r, g, b, x, y, n;
//copilot alg :)
r = (float)data.red / data.clear;
g = (float)data.green / data.clear;
b = (float)data.blue / data.clear;
x = (-0.14282) * r + (1.54924) * g + (-0.95641) * b;
y = (-0.32466) * r + (1.57837) * g + (-0.73191) * b;
n = (x - 0.3320) / (0.1858 - y);
cct = (449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33) / gain / integration_time;
ESP_LOGD(TAG, "Red:%d,Green:%d,Blue:%d,Clear:%d,CCT calculation:%.0f\n", data.red, data.green, data.blue,
data.clear, cct);
*/
}
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);
}
}
} // namespace bh1745
} // namespace esphome

View File

@ -0,0 +1,150 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.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_ = 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) { measurement_time_ = measurement_time; };
void set_adc_gain(AdcGain adc_gain) { adc_gain_ = adc_gain; };
void set_red_counts_sensor(sensor::Sensor *red_counts_sensor) { red_counts_sensor_ = red_counts_sensor; }
void set_green_counts_sensor(sensor::Sensor *green_counts_sensor) { green_counts_sensor_ = green_counts_sensor; }
void set_blue_counts_sensor(sensor::Sensor *blue_counts_sensor) { blue_counts_sensor_ = blue_counts_sensor; }
void set_clear_counts_sensor(sensor::Sensor *clear_counts_sensor) { clear_counts_sensor_ = clear_counts_sensor; }
void set_illuminance_sensor(sensor::Sensor *illuminance_sensor) { illuminance_sensor_ = illuminance_sensor; }
void set_color_temperature_sensor(sensor::Sensor *color_temperature_sensor) {
color_temperature_sensor_ = color_temperature_sensor;
}
protected:
MeasurementTime measurement_time_{MeasurementTime::TIME_160MS};
AdcGain adc_gain_{AdcGain::GAIN_1X};
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};
enum class State : uint8_t {
NOT_INITIALIZED,
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_();
};
} // namespace bh1745
} // namespace esphome

View File

@ -0,0 +1,131 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_COLOR_TEMPERATURE,
CONF_GAIN,
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"]
UNIT_COUNTS = "#"
CONF_RED_CHANNEL = "red_channel"
CONF_GREEN_CHANNEL = "green_channel"
CONF_BLUE_CHANNEL = "blue_channel"
CONF_CLEAR_CHANNEL = "clear_channel"
bh1745_ns = cg.esphome_ns.namespace("bh1745")
BH1745SComponent = bh1745_ns.class_(
"BH1745Component", cg.PollingComponent, i2c.I2CDevice
)
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_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,
),
}
)
.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]))
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))