diff --git a/lib/bme280/README.md b/lib/bme280/README.md index 9432bb9e..40d45d20 100644 --- a/lib/bme280/README.md +++ b/lib/bme280/README.md @@ -196,6 +196,22 @@ Returns the dew point temperature in **degrees Celsius**, computed from the curr --- +### Measurement Time + +```python +ms = sensor.measurement_time_ms() +``` + +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: + +```python +print("Measurement takes up to", sensor.measurement_time_ms(), "ms") +``` + +Note: in practice, prefer `read_one_shot()` which handles triggering and waiting automatically. + +--- + ## Data-Ready Status ```python @@ -331,7 +347,7 @@ Performs a soft reset, re-reads calibration data, and re-applies default configu | read_one_shot | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | set_continuous | ✅ | ❌ | ⚠️ Via mode | ❌ | ❌ | ❌ | | Integer compensation | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | -| Measurement time estimate | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | +| Measurement time estimate | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | | Multi-unit temperature | ❌ | ❌ | ❌ | ✅ C/F/K | ❌ | ❌ | | BMP280 compatibility | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | | Dedicated exceptions | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | diff --git a/lib/bme280/bme280/device.py b/lib/bme280/bme280/device.py index c2ef8bab..02189060 100644 --- a/lib/bme280/bme280/device.py +++ b/lib/bme280/bme280/device.py @@ -212,6 +212,26 @@ def set_standby(self, standby): config = (config & ~(0x07 << STANDBY_SHIFT)) | (standby << STANDBY_SHIFT) self._write_reg(REG_CONFIG, config) + def measurement_time_ms(self): + """Return the maximum measurement time in milliseconds (int, rounded up). + + Computed from the current oversampling settings using the formula + from the BME280 datasheet (section 9.1). The result is always an + integer ceiling so it can be passed directly to ``sleep_ms()``. + """ + ctrl_meas = self._read_reg(REG_CTRL_MEAS) + osrs_t = (ctrl_meas >> OSRS_T_SHIFT) & 0x07 + osrs_p = (ctrl_meas >> OSRS_P_SHIFT) & 0x07 + osrs_h = self._read_reg(REG_CTRL_HUM) & 0x07 + t_ms = 1.25 + if osrs_t: + t_ms += 2.3 * (1 << (osrs_t - 1)) + if osrs_p: + t_ms += 2.3 * (1 << (osrs_p - 1)) + 0.575 + if osrs_h: + t_ms += 2.3 * (1 << (osrs_h - 1)) + 0.575 + return int(t_ms) + (1 if t_ms != int(t_ms) else 0) + # -------------------------------------------------- # Status # -------------------------------------------------- @@ -260,8 +280,14 @@ def trigger_one_shot(self): ctrl = self._read_reg(REG_CTRL_MEAS) self._write_reg(REG_CTRL_MEAS, (ctrl & ~MODE_MASK) | MODE_FORCED) - def _wait_measurement(self, timeout_ms=100): - """Wait for measurement to complete. Raises on timeout.""" + def _wait_measurement(self): + """Wait for measurement to complete. Raises on timeout. + + The timeout is derived from :meth:`measurement_time_ms` with a + 2x safety margin so that high-oversampling configurations (e.g. + x16/x16/x16 ≈ 113 ms) never hit a spurious timeout. + """ + timeout_ms = self.measurement_time_ms() * 2 + 10 for _ in range(timeout_ms // 5): if self.data_ready(): return diff --git a/tests/scenarios/bme280.yaml b/tests/scenarios/bme280.yaml index c849596a..5d542b77 100644 --- a/tests/scenarios/bme280.yaml +++ b/tests/scenarios/bme280.yaml @@ -654,3 +654,55 @@ tests: result = abs(dp - expected) < 0.01 expect_true: true mode: [mock] + + # ----- Measurement time ----- + + - name: "measurement_time_ms returns int ceiling with default x1" + action: script + script: | + # Default: OSRS_X1 for all three → ceil(9.3) = 10 ms + t = dev.measurement_time_ms() + result = t == 10 and isinstance(t, int) + expect_true: true + mode: [mock] + + - name: "measurement_time_ms with weather station config" + action: script + script: | + from bme280.const import OSRS_X2, OSRS_X16 + dev.set_oversampling(temperature=OSRS_X2, pressure=OSRS_X16, humidity=OSRS_X2) + t = dev.measurement_time_ms() + # ceil(1.25 + 2.3*2 + (2.3*16+0.575) + (2.3*2+0.575)) = ceil(48.4) = 49 ms + result = t == 49 + # Restore default + from bme280.const import OSRS_X1 + dev.set_oversampling(temperature=OSRS_X1, pressure=OSRS_X1, humidity=OSRS_X1) + expect_true: true + mode: [mock] + + - name: "measurement_time_ms with all channels skipped" + action: script + script: | + from bme280.const import OSRS_SKIP + dev.set_oversampling(temperature=OSRS_SKIP, pressure=OSRS_SKIP, humidity=OSRS_SKIP) + t = dev.measurement_time_ms() + # ceil(1.25) = 2 ms + result = t == 2 + # Restore default + from bme280.const import OSRS_X1 + dev.set_oversampling(temperature=OSRS_X1, pressure=OSRS_X1, humidity=OSRS_X1) + expect_true: true + mode: [mock] + + - name: "measurement_time_ms handles x16 on all channels" + action: script + script: | + from bme280.const import OSRS_X16 + dev.set_oversampling(temperature=OSRS_X16, pressure=OSRS_X16, humidity=OSRS_X16) + t = dev.measurement_time_ms() + # ceil(1.25 + 2.3*16 + (2.3*16+0.575) + (2.3*16+0.575)) = ceil(112.8) = 113 ms + result = t == 113 + from bme280.const import OSRS_X1 + dev.set_oversampling(temperature=OSRS_X1, pressure=OSRS_X1, humidity=OSRS_X1) + expect_true: true + mode: [mock]