Skip to content

Commit 5d36b59

Browse files
authored
feat(bme280): Add measurement time estimation from oversampling config (#324)
* feat(bme280): Add measurement time estimation from oversampling config. * fix(bme280): Fix README measurement time example to avoid double-trigger. * fix(bme280): Return int ceiling from measurement_time_ms and derive timeout.
1 parent 348219b commit 5d36b59

3 files changed

Lines changed: 97 additions & 3 deletions

File tree

lib/bme280/README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,22 @@ Returns the dew point temperature in **degrees Celsius**, computed from the curr
196196

197197
---
198198

199+
### Measurement Time
200+
201+
```python
202+
ms = sensor.measurement_time_ms()
203+
```
204+
205+
Returns the maximum measurement time in **milliseconds** (integer, rounded up) based on the current oversampling settings (datasheet section 9.1). The result can be passed directly to `sleep_ms()`. Useful for estimating how long a forced measurement takes:
206+
207+
```python
208+
print("Measurement takes up to", sensor.measurement_time_ms(), "ms")
209+
```
210+
211+
Note: in practice, prefer `read_one_shot()` which handles triggering and waiting automatically.
212+
213+
---
214+
199215
## Data-Ready Status
200216

201217
```python
@@ -331,7 +347,7 @@ Performs a soft reset, re-reads calibration data, and re-applies default configu
331347
| read_one_shot |||||||
332348
| set_continuous ||| ⚠️ Via mode ||||
333349
| Integer compensation |||||||
334-
| Measurement time estimate | ||||||
350+
| Measurement time estimate | ||||||
335351
| Multi-unit temperature |||| ✅ C/F/K |||
336352
| BMP280 compatibility |||||||
337353
| Dedicated exceptions |||||||

lib/bme280/bme280/device.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,26 @@ def set_standby(self, standby):
212212
config = (config & ~(0x07 << STANDBY_SHIFT)) | (standby << STANDBY_SHIFT)
213213
self._write_reg(REG_CONFIG, config)
214214

215+
def measurement_time_ms(self):
216+
"""Return the maximum measurement time in milliseconds (int, rounded up).
217+
218+
Computed from the current oversampling settings using the formula
219+
from the BME280 datasheet (section 9.1). The result is always an
220+
integer ceiling so it can be passed directly to ``sleep_ms()``.
221+
"""
222+
ctrl_meas = self._read_reg(REG_CTRL_MEAS)
223+
osrs_t = (ctrl_meas >> OSRS_T_SHIFT) & 0x07
224+
osrs_p = (ctrl_meas >> OSRS_P_SHIFT) & 0x07
225+
osrs_h = self._read_reg(REG_CTRL_HUM) & 0x07
226+
t_ms = 1.25
227+
if osrs_t:
228+
t_ms += 2.3 * (1 << (osrs_t - 1))
229+
if osrs_p:
230+
t_ms += 2.3 * (1 << (osrs_p - 1)) + 0.575
231+
if osrs_h:
232+
t_ms += 2.3 * (1 << (osrs_h - 1)) + 0.575
233+
return int(t_ms) + (1 if t_ms != int(t_ms) else 0)
234+
215235
# --------------------------------------------------
216236
# Status
217237
# --------------------------------------------------
@@ -260,8 +280,14 @@ def trigger_one_shot(self):
260280
ctrl = self._read_reg(REG_CTRL_MEAS)
261281
self._write_reg(REG_CTRL_MEAS, (ctrl & ~MODE_MASK) | MODE_FORCED)
262282

263-
def _wait_measurement(self, timeout_ms=100):
264-
"""Wait for measurement to complete. Raises on timeout."""
283+
def _wait_measurement(self):
284+
"""Wait for measurement to complete. Raises on timeout.
285+
286+
The timeout is derived from :meth:`measurement_time_ms` with a
287+
2x safety margin so that high-oversampling configurations (e.g.
288+
x16/x16/x16 ≈ 113 ms) never hit a spurious timeout.
289+
"""
290+
timeout_ms = self.measurement_time_ms() * 2 + 10
265291
for _ in range(timeout_ms // 5):
266292
if self.data_ready():
267293
return

tests/scenarios/bme280.yaml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,3 +654,55 @@ tests:
654654
result = abs(dp - expected) < 0.01
655655
expect_true: true
656656
mode: [mock]
657+
658+
# ----- Measurement time -----
659+
660+
- name: "measurement_time_ms returns int ceiling with default x1"
661+
action: script
662+
script: |
663+
# Default: OSRS_X1 for all three → ceil(9.3) = 10 ms
664+
t = dev.measurement_time_ms()
665+
result = t == 10 and isinstance(t, int)
666+
expect_true: true
667+
mode: [mock]
668+
669+
- name: "measurement_time_ms with weather station config"
670+
action: script
671+
script: |
672+
from bme280.const import OSRS_X2, OSRS_X16
673+
dev.set_oversampling(temperature=OSRS_X2, pressure=OSRS_X16, humidity=OSRS_X2)
674+
t = dev.measurement_time_ms()
675+
# ceil(1.25 + 2.3*2 + (2.3*16+0.575) + (2.3*2+0.575)) = ceil(48.4) = 49 ms
676+
result = t == 49
677+
# Restore default
678+
from bme280.const import OSRS_X1
679+
dev.set_oversampling(temperature=OSRS_X1, pressure=OSRS_X1, humidity=OSRS_X1)
680+
expect_true: true
681+
mode: [mock]
682+
683+
- name: "measurement_time_ms with all channels skipped"
684+
action: script
685+
script: |
686+
from bme280.const import OSRS_SKIP
687+
dev.set_oversampling(temperature=OSRS_SKIP, pressure=OSRS_SKIP, humidity=OSRS_SKIP)
688+
t = dev.measurement_time_ms()
689+
# ceil(1.25) = 2 ms
690+
result = t == 2
691+
# Restore default
692+
from bme280.const import OSRS_X1
693+
dev.set_oversampling(temperature=OSRS_X1, pressure=OSRS_X1, humidity=OSRS_X1)
694+
expect_true: true
695+
mode: [mock]
696+
697+
- name: "measurement_time_ms handles x16 on all channels"
698+
action: script
699+
script: |
700+
from bme280.const import OSRS_X16
701+
dev.set_oversampling(temperature=OSRS_X16, pressure=OSRS_X16, humidity=OSRS_X16)
702+
t = dev.measurement_time_ms()
703+
# ceil(1.25 + 2.3*16 + (2.3*16+0.575) + (2.3*16+0.575)) = ceil(112.8) = 113 ms
704+
result = t == 113
705+
from bme280.const import OSRS_X1
706+
dev.set_oversampling(temperature=OSRS_X1, pressure=OSRS_X1, humidity=OSRS_X1)
707+
expect_true: true
708+
mode: [mock]

0 commit comments

Comments
 (0)