ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
bh1745.cpp
Go to the documentation of this file.
1#include "bh1745.h"
2#include <cinttypes>
3#include <cmath>
5#include "esphome/core/log.h"
6
7namespace esphome::bh1745 {
8
9ESPHOME_LOG_TAG(TAG, "bh1745");
10
11static constexpr uint8_t REG_SYSTEM_CONTROL = 0x40;
12static constexpr uint8_t REG_MODE_CONTROL1 = 0x41;
13static constexpr uint8_t REG_MODE_CONTROL2 = 0x42;
14static constexpr uint8_t REG_MODE_CONTROL3 = 0x44;
15static constexpr uint8_t REG_RED_DATA_LSB = 0x50;
16static constexpr uint8_t REG_INTERRUPT = 0x60;
17static constexpr uint8_t REG_TH_LSB = 0x62;
18static constexpr uint8_t REG_MANUFACTURER_ID = 0x92;
19
20static constexpr uint8_t MANUFACTURER_ID = 0xE0;
21static constexpr uint8_t PART_ID = 0b001011;
22static constexpr uint8_t SYSTEM_CONTROL_PART_ID_MASK = 0x3F;
23static constexpr uint8_t SYSTEM_CONTROL_SW_RESET = 0x80;
24static constexpr uint8_t MODE_CONTROL2_RGBC_EN = 0x10;
25static constexpr uint8_t MODE_CONTROL2_VALID = 0x80;
26static constexpr uint8_t MODE_CONTROL3_VALUE = 0x02; // the datasheet says always write 0x02
27static constexpr uint8_t INTERRUPT_ENABLE = 0x01;
28
29static constexpr uint8_t BH1745_CHANNELS = 4;
30static constexpr uint32_t RESET_DELAY_MS = 100;
31static constexpr uint32_t BASE_MEAS_TIME_MS = 160;
32
33static uint32_t get_measurement_time_ms(MeasurementTime time) {
34 return BASE_MEAS_TIME_MS << static_cast<uint8_t>(time);
35}
36
37static uint8_t get_adc_gain(AdcGain gain) {
38 return gain == AdcGain::ADC_GAIN_16X ? 16 : 1 + static_cast<uint8_t>(gain);
39}
40
41static float calculate_lux(uint16_t red, uint16_t green, bool low_clear_ratio, float scale) {
42 if (green == 0)
43 return 0.0f;
44 float lx = low_clear_ratio ? 0.202f * red + 0.766f * green : 0.159f * red + 0.646f * green;
45 return lx * scale;
46}
47
48static float calculate_cct(uint16_t red, uint16_t green, uint16_t blue, bool low_clear_ratio) {
49 if (green == 0)
50 return 0.0f;
51 float all = static_cast<float>(static_cast<uint32_t>(red) + green + blue);
52 float r_ratio = red / all;
53 float b_ratio = blue / all;
54 float ct;
55 if (low_clear_ratio) {
56 float b_eff = std::fmin(b_ratio * 3.13f, 1.0f);
57 ct = ((1.0f - b_eff) * 12746.0f * std::exp(-2.911f * r_ratio)) + (b_eff * 1637.0f * std::exp(4.865f * b_ratio));
58 } else {
59 float b_eff = std::fmin(b_ratio * 10.67f, 1.0f);
60 ct = ((1.0f - b_eff) * 16234.0f * std::exp(-2.781f * r_ratio)) + (b_eff * 1882.0f * std::exp(4.448f * b_ratio));
61 }
62 return clamp_at_most(ct, 10000.0f);
63}
64
66 uint8_t manufacturer_id;
67 if (!this->read_byte(REG_MANUFACTURER_ID, &manufacturer_id)) {
68 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
69 return;
70 }
71 if (manufacturer_id != MANUFACTURER_ID) {
72 this->mark_failed(LOG_STR("Wrong manufacturer ID"));
73 return;
74 }
75 uint8_t system_control;
76 if (!this->read_byte(REG_SYSTEM_CONTROL, &system_control) ||
77 (system_control & SYSTEM_CONTROL_PART_ID_MASK) != PART_ID) {
78 this->mark_failed(LOG_STR("Wrong part ID"));
79 return;
80 }
81 if (!this->write_byte(REG_SYSTEM_CONTROL, SYSTEM_CONTROL_SW_RESET)) {
82 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
83 return;
84 }
85
86 // Light lost behind glass is scaled back up, as the other light sensors do
87 this->lux_scale_ = 160.0f * this->glass_attenuation_factor_ /
88 (get_adc_gain(this->adc_gain_) * get_measurement_time_ms(this->measurement_time_));
89
90 this->set_timeout(RESET_DELAY_MS, [this]() {
91 if (this->interrupt_output_) {
92 // High threshold 0 and low threshold 0xFFFF (low byte first) make the interrupt condition always
93 // true, so the INT pin simply follows the enable bit, which drives the breakout's LEDs.
94 static constexpr uint8_t THRESHOLDS[4] = {0x00, 0x00, 0xFF, 0xFF};
95 if (!this->write_bytes(REG_TH_LSB, THRESHOLDS, sizeof(THRESHOLDS))) {
96 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
97 return;
98 }
99 }
100 // RGBC_EN makes the chip measure continuously; update() reads the latest result.
101 if (!this->write_byte(REG_MODE_CONTROL1, static_cast<uint8_t>(this->measurement_time_)) ||
102 !this->write_byte(REG_MODE_CONTROL2, static_cast<uint8_t>(this->adc_gain_) | MODE_CONTROL2_RGBC_EN) ||
103 !this->write_byte(REG_MODE_CONTROL3, MODE_CONTROL3_VALUE)) {
104 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
105 return;
106 }
107 this->busy_ = false;
108 });
109}
110
111void BH1745Component::dump_config() {
112 ESP_LOGCONFIG(TAG,
113 "BH1745:\n"
114 " Gain: %ux\n"
115 " Integration time: %" PRIu32 " ms\n"
116 " Glass attenuation factor: %.2f",
117 get_adc_gain(this->adc_gain_), get_measurement_time_ms(this->measurement_time_),
119 LOG_I2C_DEVICE(this);
120 LOG_UPDATE_INTERVAL(this);
121
122 LOG_SENSOR(" ", "Red Counts", this->red_counts_sensor_);
123 LOG_SENSOR(" ", "Green Counts", this->green_counts_sensor_);
124 LOG_SENSOR(" ", "Blue Counts", this->blue_counts_sensor_);
125 LOG_SENSOR(" ", "Clear Counts", this->clear_counts_sensor_);
126 LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
127 LOG_SENSOR(" ", "Color Temperature", this->color_temperature_sensor_);
128}
129
130void BH1745Component::update() {
131 if (this->busy_)
132 return;
133
134 // Reading MODE_CONTROL2 clears VALID, so read it only once per poll.
135 uint8_t mode_control2;
136 if (!this->read_byte(REG_MODE_CONTROL2, &mode_control2)) {
137 this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
138 return;
139 }
140 if ((mode_control2 & MODE_CONTROL2_VALID) == 0)
141 return;
142
143 uint8_t buffer[BH1745_CHANNELS * 2];
144 if (!this->read_bytes(REG_RED_DATA_LSB, buffer, sizeof(buffer))) {
145 this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
146 return;
147 }
148 this->status_clear_warning();
149
150 // Each channel is sent low byte first.
151 const uint16_t red = encode_uint16(buffer[1], buffer[0]);
152 const uint16_t green = encode_uint16(buffer[3], buffer[2]);
153 const uint16_t blue = encode_uint16(buffer[5], buffer[4]);
154 const uint16_t clear = encode_uint16(buffer[7], buffer[6]);
155 ESP_LOGV(TAG, "Red: %u, Green: %u, Blue: %u, Clear: %u", red, green, blue, clear);
156
157 if (this->red_counts_sensor_ != nullptr)
158 this->red_counts_sensor_->publish_state(red);
159 if (this->green_counts_sensor_ != nullptr)
160 this->green_counts_sensor_->publish_state(green);
161 if (this->blue_counts_sensor_ != nullptr)
162 this->blue_counts_sensor_->publish_state(blue);
163 if (this->clear_counts_sensor_ != nullptr)
164 this->clear_counts_sensor_->publish_state(clear);
165
166 // The datasheet picks its coefficients by whether clear / green < 0.160 (= 4 / 25).
167 const bool low_clear_ratio = static_cast<uint32_t>(clear) * 25 < static_cast<uint32_t>(green) * 4;
168 if (this->illuminance_sensor_ != nullptr)
169 this->illuminance_sensor_->publish_state(calculate_lux(red, green, low_clear_ratio, this->lux_scale_));
170 if (this->color_temperature_sensor_ != nullptr)
171 this->color_temperature_sensor_->publish_state(calculate_cct(red, green, blue, low_clear_ratio));
172}
173
174void BH1745Component::set_interrupt_state(bool on) {
175 if (this->is_failed())
176 return;
177 if (!this->write_byte(REG_INTERRUPT, on ? INTERRUPT_ENABLE : 0x00)) {
178 this->status_set_warning();
179 }
180}
181
182} // namespace esphome::bh1745
void mark_failed()
Mark this component as failed.
virtual void setup()
Where the component's initialization should happen.
Definition component.cpp:84
bool is_failed() const
Definition component.h:274
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void status_clear_warning()
Definition component.h:291
MeasurementTime measurement_time_
Definition bh1745.h:48
bool write_byte(uint8_t a_register, uint8_t data) const
Definition i2c.h:265
bool read_byte(uint8_t a_register, uint8_t *data)
Definition i2c.h:240
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) const
Definition i2c.h:251
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len)
Compat APIs All methods below have been added for compatibility reasons.
Definition i2c.h:217
uint32_t buffer
Definition gif.cpp:50
AlsGain501 gain
ESPHOME_LOG_TAG(TAG, "bh1745")
T clamp_at_most(T value, U max)
Definition helpers.h:2402
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:980
static void uint32_t