ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
bh1750.cpp
Go to the documentation of this file.
1#include "bh1750.h"
2#include "esphome/core/log.h"
4
5namespace esphome::bh1750 {
6
7ESPHOME_LOG_TAG(TAG, "bh1750.sensor");
8
9static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001;
10static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000; // last 3 bits
11static const uint8_t BH1750_COMMAND_MT_REG_LO = 0b01100000; // last 5 bits
12static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
13static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
14static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
15
16static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
17static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
18
19// Measurement time constants (datasheet values)
20static constexpr uint16_t MTREG_DEFAULT = 69;
21static constexpr uint16_t MTREG_MIN = 31;
22static constexpr uint16_t MTREG_MAX = 254;
23static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
24static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
25
26// Conversion constants (datasheet formulas)
27static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
28static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
29
30// MTreg calculation constants
31static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
32static constexpr int COUNTS_NUMERATOR = 10;
33static constexpr int COUNTS_DENOMINATOR = 12;
34
35// MTreg register bit manipulation constants
36static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
37static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
38static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
39static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
40
41/*
42bh1750 properties:
43
44L-resolution mode:
45- resolution 4lx (@ mtreg=69)
46- measurement time: typ=16ms, max=24ms, scaled by MTreg value divided by 69
47- formula: counts / 1.2 * (69 / MTreg) lx
48H-resolution mode:
49- resolution 1lx (@ mtreg=69)
50- measurement time: typ=120ms, max=180ms, scaled by MTreg value divided by 69
51- formula: counts / 1.2 * (69 / MTreg) lx
52H-resolution mode2:
53- resolution 0.5lx (@ mtreg=69)
54- measurement time: typ=120ms, max=180ms, scaled by MTreg value divided by 69
55- formula: counts / 1.2 * (69 / MTreg) / 2 lx
56
57MTreg:
58- min=31, default=69, max=254
59
60-> only reason to use l-resolution is faster, but offers no higher range
61-> below ~7000lx, makes sense to use H-resolution2 @ MTreg=254
62-> try to maximize MTreg to get lowest noise level
63*/
64
66 uint8_t turn_on = BH1750_COMMAND_POWER_ON;
67 if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
68 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
69 return;
70 }
71 this->state_ = IDLE;
72}
73
75 LOG_SENSOR("", "BH1750", this);
76 LOG_I2C_DEVICE(this);
77
78 LOG_UPDATE_INTERVAL(this);
79}
80
82 const uint32_t now = millis();
83
84 // Start coarse measurement to determine optimal mode/mtreg
85 if (this->state_ != IDLE) {
86 // Safety timeout: reset if stuck
87 if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
88 ESP_LOGW(TAG, "Measurement timeout, resetting state");
89 this->state_ = IDLE;
90 } else {
91 ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
92 return;
93 }
94 }
95
96 if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
97 this->status_set_warning();
98 this->publish_state(NAN);
99 return;
100 }
101
103 this->enable_loop(); // Enable loop while measurement in progress
104}
105
108
109 switch (this->state_) {
110 case IDLE:
111 // Disable loop when idle to save cycles
112 this->disable_loop();
113 break;
114
116 if (now - this->measurement_start_time_ >= this->measurement_duration_) {
118 }
119 break;
120
122 float lx;
123 if (!this->read_measurement_(lx)) {
124 this->fail_and_reset_();
125 break;
126 }
127
128 this->process_coarse_result_(lx);
129
130 // Start fine measurement with optimal settings
131 // fetch millis() again since the read can take a bit
132 if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
133 this->fail_and_reset_();
134 break;
135 }
136
138 break;
139 }
140
142 if (now - this->measurement_start_time_ >= this->measurement_duration_) {
144 }
145 break;
146
147 case READING_FINE_RESULT: {
148 float lx;
149 if (!this->read_measurement_(lx)) {
150 this->fail_and_reset_();
151 break;
152 }
153
154 ESP_LOGV(TAG, "'%s': Illuminance=%.1flx", LOG_STR_ARG(this->get_log_name()), lx);
155 this->status_clear_warning();
156 this->publish_state(lx);
157 this->state_ = IDLE;
158 break;
159 }
160 }
161}
162
164 // Power on
165 uint8_t turn_on = BH1750_COMMAND_POWER_ON;
166 if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
167 ESP_LOGW(TAG, "Power on failed");
168 return false;
169 }
170
171 // Set MTreg if changed
172 if (this->active_mtreg_ != mtreg) {
173 uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
174 uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
175 if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
176 ESP_LOGW(TAG, "Set measurement time failed");
177 this->active_mtreg_ = 0;
178 return false;
179 }
180 this->active_mtreg_ = mtreg;
181 }
182
183 // Start measurement
184 uint8_t cmd;
185 uint16_t meas_time;
186 switch (mode) {
187 case BH1750_MODE_L:
188 cmd = BH1750_COMMAND_ONE_TIME_L;
189 meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
190 break;
191 case BH1750_MODE_H:
192 cmd = BH1750_COMMAND_ONE_TIME_H;
193 meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
194 break;
195 case BH1750_MODE_H2:
196 cmd = BH1750_COMMAND_ONE_TIME_H2;
197 meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
198 break;
199 default:
200 return false;
201 }
202
203 if (this->write(&cmd, 1) != i2c::ERROR_OK) {
204 ESP_LOGW(TAG, "Start measurement failed");
205 return false;
206 }
207
208 // Store current measurement parameters
209 this->current_mode_ = mode;
210 this->current_mtreg_ = mtreg;
211 this->measurement_start_time_ = now;
212 this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
213
214 return true;
215}
216
218 uint16_t raw_value;
219 if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
220 ESP_LOGW(TAG, "Read data failed");
221 return false;
222 }
223 raw_value = i2c::i2ctohs(raw_value);
224
225 float lx = float(raw_value) / RESOLUTION_DIVISOR;
226 lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
227 if (this->current_mode_ == BH1750_MODE_H2) {
228 lx /= MODE_H2_DIVISOR;
229 }
230
231 lx_out = lx;
232 return true;
233}
234
236 if (std::isnan(lx)) {
237 // Use defaults if coarse measurement failed
239 this->fine_mtreg_ = MTREG_MAX;
240 return;
241 }
242
243 if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
245 this->fine_mtreg_ = MTREG_MAX;
246 } else {
248 // lx = counts / 1.2 * (69 / mtreg)
249 // -> mtreg = counts / 1.2 * (69 / lx)
250 // calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
251 // -> mtreg = 50000*(10/12)*(69/lx)
252 int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
253 this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
254 }
255
256 ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
257}
258
260 this->status_set_warning();
261 this->publish_state(NAN);
262 this->state_ = IDLE;
263}
264
265} // namespace esphome::bh1750
BedjetMode mode
BedJet operating mode.
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void mark_failed()
Mark this component as failed.
void enable_loop()
Enable this component's loop.
Definition component.h:248
void disable_loop()
Disable this component's loop.
void status_clear_warning()
Definition component.h:296
const LogString * get_log_name() const
Get the name for a "%s" log argument (LOG_STR_ARG).
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now)
Definition bh1750.cpp:163
void process_coarse_result_(float lx)
Definition bh1750.cpp:235
void dump_config() override
Definition bh1750.cpp:74
bool read_measurement_(float &lx_out)
Definition bh1750.cpp:217
ErrorCode write(const uint8_t *data, size_t len) const
writes an array of bytes to a device using an I2CBus
Definition i2c.h:195
ErrorCode read(uint8_t *data, size_t len) const
reads an array of bytes from the device using an I2CBus
Definition i2c.h:175
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
ESPHOME_LOG_TAG(TAG, "bh1750.sensor")
uint16_t i2ctohs(uint16_t i2cshort)
Definition i2c.h:139
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:15
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t