ESPHome 2026.1.4
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
7static const char *const 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();
69 return;
70 }
71 this->state_ = IDLE;
72}
73
75 LOG_SENSOR("", "BH1750", this);
76 LOG_I2C_DEVICE(this);
77 if (this->is_failed()) {
78 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL_FOR, this->get_name().c_str());
79 }
80
81 LOG_UPDATE_INTERVAL(this);
82}
83
85 const uint32_t now = millis();
86
87 // Start coarse measurement to determine optimal mode/mtreg
88 if (this->state_ != IDLE) {
89 // Safety timeout: reset if stuck
90 if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
91 ESP_LOGW(TAG, "Measurement timeout, resetting state");
92 this->state_ = IDLE;
93 } else {
94 ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
95 return;
96 }
97 }
98
99 if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
100 this->status_set_warning();
101 this->publish_state(NAN);
102 return;
103 }
104
106 this->enable_loop(); // Enable loop while measurement in progress
107}
108
110 const uint32_t now = App.get_loop_component_start_time();
111
112 switch (this->state_) {
113 case IDLE:
114 // Disable loop when idle to save cycles
115 this->disable_loop();
116 break;
117
119 if (now - this->measurement_start_time_ >= this->measurement_duration_) {
121 }
122 break;
123
125 float lx;
126 if (!this->read_measurement_(lx)) {
127 this->fail_and_reset_();
128 break;
129 }
130
131 this->process_coarse_result_(lx);
132
133 // Start fine measurement with optimal settings
134 // fetch millis() again since the read can take a bit
135 if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
136 this->fail_and_reset_();
137 break;
138 }
139
141 break;
142 }
143
145 if (now - this->measurement_start_time_ >= this->measurement_duration_) {
147 }
148 break;
149
150 case READING_FINE_RESULT: {
151 float lx;
152 if (!this->read_measurement_(lx)) {
153 this->fail_and_reset_();
154 break;
155 }
156
157 ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
158 this->status_clear_warning();
159 this->publish_state(lx);
160 this->state_ = IDLE;
161 break;
162 }
163 }
164}
165
166bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
167 // Power on
168 uint8_t turn_on = BH1750_COMMAND_POWER_ON;
169 if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
170 ESP_LOGW(TAG, "Power on failed");
171 return false;
172 }
173
174 // Set MTreg if changed
175 if (this->active_mtreg_ != mtreg) {
176 uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
177 uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
178 if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
179 ESP_LOGW(TAG, "Set measurement time failed");
180 this->active_mtreg_ = 0;
181 return false;
182 }
183 this->active_mtreg_ = mtreg;
184 }
185
186 // Start measurement
187 uint8_t cmd;
188 uint16_t meas_time;
189 switch (mode) {
190 case BH1750_MODE_L:
191 cmd = BH1750_COMMAND_ONE_TIME_L;
192 meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
193 break;
194 case BH1750_MODE_H:
195 cmd = BH1750_COMMAND_ONE_TIME_H;
196 meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
197 break;
198 case BH1750_MODE_H2:
199 cmd = BH1750_COMMAND_ONE_TIME_H2;
200 meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
201 break;
202 default:
203 return false;
204 }
205
206 if (this->write(&cmd, 1) != i2c::ERROR_OK) {
207 ESP_LOGW(TAG, "Start measurement failed");
208 return false;
209 }
210
211 // Store current measurement parameters
212 this->current_mode_ = mode;
213 this->current_mtreg_ = mtreg;
214 this->measurement_start_time_ = now;
215 this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
216
217 return true;
218}
219
221 uint16_t raw_value;
222 if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
223 ESP_LOGW(TAG, "Read data failed");
224 return false;
225 }
226 raw_value = i2c::i2ctohs(raw_value);
227
228 float lx = float(raw_value) / RESOLUTION_DIVISOR;
229 lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
230 if (this->current_mode_ == BH1750_MODE_H2) {
231 lx /= MODE_H2_DIVISOR;
232 }
233
234 lx_out = lx;
235 return true;
236}
237
239 if (std::isnan(lx)) {
240 // Use defaults if coarse measurement failed
242 this->fine_mtreg_ = MTREG_MAX;
243 return;
244 }
245
246 if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
248 this->fine_mtreg_ = MTREG_MAX;
249 } else {
251 // lx = counts / 1.2 * (69 / mtreg)
252 // -> mtreg = counts / 1.2 * (69 / lx)
253 // calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
254 // -> mtreg = 50000*(10/12)*(69/lx)
255 int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
256 this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
257 }
258
259 ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
260}
261
263 this->status_set_warning();
264 this->publish_state(NAN);
265 this->state_ = IDLE;
266}
267
269
270} // 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.
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_set_warning(const char *message=nullptr)
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
void status_clear_warning()
const StringRef & get_name() const
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now)
Definition bh1750.cpp:166
void process_coarse_result_(float lx)
Definition bh1750.cpp:238
void dump_config() override
Definition bh1750.cpp:74
float get_setup_priority() const override
Definition bh1750.cpp:268
bool read_measurement_(float &lx_out)
Definition bh1750.cpp:220
ErrorCode write(const uint8_t *data, size_t len) const
writes an array of bytes to a device using an I2CBus
Definition i2c.h:184
ErrorCode read(uint8_t *data, size_t len) const
reads an array of bytes from the device using an I2CBus
Definition i2c.h:164
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:76
uint16_t i2ctohs(uint16_t i2cshort)
Definition i2c.h:128
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:17
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:81
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.