Skip to content

Commit 2af3fd9

Browse files
authored
feat(bme280): Add measurement modes and sensor configuration (#313)
* feat(bme280): Add measurement modes and sensor configuration. * fix(bme280): Rewrite ctrl_meas after ctrl_hum to latch humidity config.
1 parent f748804 commit 2af3fd9

2 files changed

Lines changed: 214 additions & 0 deletions

File tree

lib/bme280/bme280/device.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,26 @@
77
CALIB_H_SIZE,
88
CALIB_TP_SIZE,
99
DATA_BLOCK_SIZE,
10+
FILTER_SHIFT,
1011
MODE_FORCED,
1112
MODE_MASK,
13+
MODE_NORMAL,
1214
MODE_SLEEP,
1315
OSRS_P_SHIFT,
1416
OSRS_T_SHIFT,
1517
OSRS_X1,
1618
REG_CALIB_HUM,
1719
REG_CALIB_TEMP_PRESS,
1820
REG_CHIP_ID,
21+
REG_CONFIG,
1922
REG_CTRL_HUM,
2023
REG_CTRL_MEAS,
2124
REG_DATA_START,
2225
REG_SOFT_RESET,
2326
REG_STATUS,
2427
RESET_DELAY_MS,
2528
SOFT_RESET_CMD,
29+
STANDBY_SHIFT,
2630
STATUS_IM_UPDATE,
2731
STATUS_MEASURING,
2832
)
@@ -139,6 +143,74 @@ def reset(self):
139143
self._read_calibration()
140144
self._configure_default()
141145

146+
# --------------------------------------------------
147+
# Power and mode control
148+
# --------------------------------------------------
149+
150+
def power_off(self):
151+
"""Enter sleep mode. Stops all measurements."""
152+
ctrl = self._read_reg(REG_CTRL_MEAS)
153+
self._write_reg(REG_CTRL_MEAS, ctrl & ~MODE_MASK)
154+
155+
def power_on(self):
156+
"""Enter normal mode. Continuous measurements at configured standby rate."""
157+
ctrl = self._read_reg(REG_CTRL_MEAS)
158+
self._write_reg(REG_CTRL_MEAS, (ctrl & ~MODE_MASK) | MODE_NORMAL)
159+
160+
def set_continuous(self, standby=None):
161+
"""Start continuous measurements in normal mode.
162+
163+
Args:
164+
standby: standby time constant (STANDBY_0_5_MS .. STANDBY_1000_MS).
165+
If None, the current config register value is kept.
166+
"""
167+
if standby is not None:
168+
self.set_standby(standby)
169+
self.power_on()
170+
171+
# --------------------------------------------------
172+
# Sensor configuration
173+
# --------------------------------------------------
174+
175+
def set_oversampling(self, temperature=None, pressure=None, humidity=None):
176+
"""Configure oversampling for one or more channels.
177+
178+
Args:
179+
temperature: OSRS_SKIP .. OSRS_X16 (None = keep current).
180+
pressure: OSRS_SKIP .. OSRS_X16 (None = keep current).
181+
humidity: OSRS_SKIP .. OSRS_X16 (None = keep current).
182+
"""
183+
if humidity is not None:
184+
self._write_reg(REG_CTRL_HUM, humidity)
185+
ctrl = self._read_reg(REG_CTRL_MEAS)
186+
if temperature is not None:
187+
ctrl = (ctrl & ~(0x07 << OSRS_T_SHIFT)) | (temperature << OSRS_T_SHIFT)
188+
if pressure is not None:
189+
ctrl = (ctrl & ~(0x07 << OSRS_P_SHIFT)) | (pressure << OSRS_P_SHIFT)
190+
# ctrl_meas must always be rewritten: changes to ctrl_hum are only
191+
# latched when ctrl_meas is written (datasheet section 5.4.3).
192+
self._write_reg(REG_CTRL_MEAS, ctrl)
193+
194+
def set_iir_filter(self, coefficient):
195+
"""Configure the IIR filter coefficient.
196+
197+
Args:
198+
coefficient: FILTER_OFF .. FILTER_16.
199+
"""
200+
config = self._read_reg(REG_CONFIG)
201+
config = (config & ~(0x07 << FILTER_SHIFT)) | (coefficient << FILTER_SHIFT)
202+
self._write_reg(REG_CONFIG, config)
203+
204+
def set_standby(self, standby):
205+
"""Configure the standby time for normal mode.
206+
207+
Args:
208+
standby: STANDBY_0_5_MS .. STANDBY_1000_MS.
209+
"""
210+
config = self._read_reg(REG_CONFIG)
211+
config = (config & ~(0x07 << STANDBY_SHIFT)) | (standby << STANDBY_SHIFT)
212+
self._write_reg(REG_CONFIG, config)
213+
142214
# --------------------------------------------------
143215
# Status
144216
# --------------------------------------------------

tests/scenarios/bme280.yaml

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,145 @@ tests:
274274
result = triggered and abs(t - 25.08) < 0.1
275275
expect_true: true
276276
mode: [mock]
277+
278+
# ----- Power and mode control -----
279+
280+
- name: "power_off sets sleep mode"
281+
action: script
282+
script: |
283+
i2c.clear_write_log()
284+
dev.power_off()
285+
log = i2c.get_write_log()
286+
wrote_sleep = any(reg == 0xF4 and (data[0] & 0x03) == 0x00 for reg, data in log)
287+
result = wrote_sleep
288+
expect_true: true
289+
mode: [mock]
290+
291+
- name: "power_on sets normal mode"
292+
action: script
293+
script: |
294+
i2c.clear_write_log()
295+
dev.power_on()
296+
log = i2c.get_write_log()
297+
wrote_normal = any(reg == 0xF4 and (data[0] & 0x03) == 0x03 for reg, data in log)
298+
result = wrote_normal
299+
expect_true: true
300+
mode: [mock]
301+
302+
- name: "power_off preserves oversampling bits"
303+
action: script
304+
script: |
305+
from bme280.const import OSRS_X4, OSRS_T_SHIFT, OSRS_P_SHIFT
306+
dev.set_oversampling(temperature=OSRS_X4, pressure=OSRS_X4)
307+
i2c.clear_write_log()
308+
dev.power_off()
309+
log = i2c.get_write_log()
310+
ctrl = [data[0] for reg, data in log if reg == 0xF4][-1]
311+
osrs_t = (ctrl >> 5) & 0x07
312+
osrs_p = (ctrl >> 2) & 0x07
313+
result = osrs_t == OSRS_X4 and osrs_p == OSRS_X4 and (ctrl & 0x03) == 0x00
314+
expect_true: true
315+
mode: [mock]
316+
317+
- name: "set_continuous enters normal mode"
318+
action: script
319+
script: |
320+
dev.power_off()
321+
i2c.clear_write_log()
322+
dev.set_continuous()
323+
log = i2c.get_write_log()
324+
wrote_normal = any(reg == 0xF4 and (data[0] & 0x03) == 0x03 for reg, data in log)
325+
result = wrote_normal
326+
expect_true: true
327+
mode: [mock]
328+
329+
- name: "set_continuous with standby configures both"
330+
action: script
331+
script: |
332+
from bme280.const import STANDBY_500_MS, STANDBY_SHIFT
333+
i2c.clear_write_log()
334+
dev.set_continuous(standby=STANDBY_500_MS)
335+
log = i2c.get_write_log()
336+
wrote_config = any(reg == 0xF5 for reg, data in log)
337+
wrote_normal = any(reg == 0xF4 and (data[0] & 0x03) == 0x03 for reg, data in log)
338+
result = wrote_config and wrote_normal
339+
expect_true: true
340+
mode: [mock]
341+
342+
# ----- Configuration -----
343+
344+
- name: "set_oversampling configures temperature and pressure"
345+
action: script
346+
script: |
347+
from bme280.const import OSRS_X2, OSRS_X4, OSRS_T_SHIFT, OSRS_P_SHIFT
348+
i2c.clear_write_log()
349+
dev.set_oversampling(temperature=OSRS_X2, pressure=OSRS_X4)
350+
log = i2c.get_write_log()
351+
ctrl_writes = [data[0] for reg, data in log if reg == 0xF4]
352+
ctrl = ctrl_writes[-1]
353+
osrs_t = (ctrl >> OSRS_T_SHIFT) & 0x07
354+
osrs_p = (ctrl >> OSRS_P_SHIFT) & 0x07
355+
result = osrs_t == OSRS_X2 and osrs_p == OSRS_X4
356+
expect_true: true
357+
mode: [mock]
358+
359+
- name: "set_oversampling configures humidity and latches via ctrl_meas"
360+
action: script
361+
script: |
362+
from bme280.const import OSRS_X8
363+
i2c.clear_write_log()
364+
dev.set_oversampling(humidity=OSRS_X8)
365+
log = i2c.get_write_log()
366+
hum_writes = [data[0] for reg, data in log if reg == 0xF2]
367+
# ctrl_meas must be rewritten after ctrl_hum for the change to take effect
368+
ctrl_writes = [data[0] for reg, data in log if reg == 0xF4]
369+
# Verify write order: ctrl_hum (0xF2) must come before ctrl_meas (0xF4)
370+
hum_idx = next(i for i, (reg, _) in enumerate(log) if reg == 0xF2)
371+
meas_idx = next(i for i, (reg, _) in enumerate(log) if reg == 0xF4)
372+
result = (
373+
len(hum_writes) == 1
374+
and hum_writes[0] == OSRS_X8
375+
and len(ctrl_writes) == 1
376+
and hum_idx < meas_idx
377+
)
378+
expect_true: true
379+
mode: [mock]
380+
381+
- name: "set_iir_filter writes config register"
382+
action: script
383+
script: |
384+
from bme280.const import FILTER_16, FILTER_SHIFT
385+
i2c.clear_write_log()
386+
dev.set_iir_filter(FILTER_16)
387+
log = i2c.get_write_log()
388+
config_writes = [data[0] for reg, data in log if reg == 0xF5]
389+
result = len(config_writes) == 1 and ((config_writes[0] >> FILTER_SHIFT) & 0x07) == FILTER_16
390+
expect_true: true
391+
mode: [mock]
392+
393+
- name: "set_standby writes config register"
394+
action: script
395+
script: |
396+
from bme280.const import STANDBY_250_MS, STANDBY_SHIFT
397+
i2c.clear_write_log()
398+
dev.set_standby(STANDBY_250_MS)
399+
log = i2c.get_write_log()
400+
config_writes = [data[0] for reg, data in log if reg == 0xF5]
401+
result = len(config_writes) == 1 and ((config_writes[0] >> STANDBY_SHIFT) & 0x07) == STANDBY_250_MS
402+
expect_true: true
403+
mode: [mock]
404+
405+
- name: "set_iir_filter preserves standby bits"
406+
action: script
407+
script: |
408+
from bme280.const import STANDBY_500_MS, STANDBY_SHIFT, FILTER_4, FILTER_SHIFT
409+
dev.set_standby(STANDBY_500_MS)
410+
i2c.clear_write_log()
411+
dev.set_iir_filter(FILTER_4)
412+
log = i2c.get_write_log()
413+
config = [data[0] for reg, data in log if reg == 0xF5][-1]
414+
standby = (config >> STANDBY_SHIFT) & 0x07
415+
filt = (config >> FILTER_SHIFT) & 0x07
416+
result = standby == STANDBY_500_MS and filt == FILTER_4
417+
expect_true: true
418+
mode: [mock]

0 commit comments

Comments
 (0)