|
6 | 6 | BME280_I2C_DEFAULT_ADDR, |
7 | 7 | CALIB_H_SIZE, |
8 | 8 | CALIB_TP_SIZE, |
| 9 | + DATA_BLOCK_SIZE, |
| 10 | + MODE_FORCED, |
| 11 | + MODE_MASK, |
9 | 12 | MODE_SLEEP, |
10 | 13 | OSRS_P_SHIFT, |
11 | 14 | OSRS_T_SHIFT, |
|
15 | 18 | REG_CHIP_ID, |
16 | 19 | REG_CTRL_HUM, |
17 | 20 | REG_CTRL_MEAS, |
| 21 | + REG_DATA_START, |
18 | 22 | REG_SOFT_RESET, |
19 | 23 | REG_STATUS, |
20 | 24 | RESET_DELAY_MS, |
21 | 25 | SOFT_RESET_CMD, |
22 | 26 | STATUS_IM_UPDATE, |
| 27 | + STATUS_MEASURING, |
23 | 28 | ) |
24 | 29 | from bme280.exceptions import BME280Error, BME280InvalidDevice, BME280NotFound |
25 | 30 |
|
@@ -51,10 +56,6 @@ def _write_reg(self, reg, value): |
51 | 56 | """Write a single byte to register.""" |
52 | 57 | self.i2c.writeto_mem(self.address, reg, bytes([value])) |
53 | 58 |
|
54 | | - # -------------------------------------------------- |
55 | | - # Device identification and initialization |
56 | | - # -------------------------------------------------- |
57 | | - |
58 | 59 | # -------------------------------------------------- |
59 | 60 | # Calibration data |
60 | 61 | # -------------------------------------------------- |
@@ -137,3 +138,145 @@ def reset(self): |
137 | 138 | self.soft_reset() |
138 | 139 | self._read_calibration() |
139 | 140 | self._configure_default() |
| 141 | + |
| 142 | + # -------------------------------------------------- |
| 143 | + # Status |
| 144 | + # -------------------------------------------------- |
| 145 | + |
| 146 | + def _status(self): |
| 147 | + """Return the raw STATUS register value.""" |
| 148 | + return self._read_reg(REG_STATUS) |
| 149 | + |
| 150 | + def data_ready(self): |
| 151 | + """Return True when all measurements are available.""" |
| 152 | + return not (self._status() & STATUS_MEASURING) |
| 153 | + |
| 154 | + def temperature_ready(self): |
| 155 | + """Return True when temperature data is available.""" |
| 156 | + return self.data_ready() |
| 157 | + |
| 158 | + def pressure_ready(self): |
| 159 | + """Return True when pressure data is available.""" |
| 160 | + return self.data_ready() |
| 161 | + |
| 162 | + def humidity_ready(self): |
| 163 | + """Return True when humidity data is available.""" |
| 164 | + return self.data_ready() |
| 165 | + |
| 166 | + # -------------------------------------------------- |
| 167 | + # Forced measurement trigger |
| 168 | + # -------------------------------------------------- |
| 169 | + |
| 170 | + def trigger_one_shot(self): |
| 171 | + """Trigger a single forced measurement. Poll data_ready() for completion.""" |
| 172 | + ctrl = self._read_reg(REG_CTRL_MEAS) |
| 173 | + self._write_reg(REG_CTRL_MEAS, (ctrl & ~MODE_MASK) | MODE_FORCED) |
| 174 | + |
| 175 | + def _wait_measurement(self, timeout_ms=100): |
| 176 | + """Wait for measurement to complete. Raises on timeout.""" |
| 177 | + for _ in range(timeout_ms // 5): |
| 178 | + if self.data_ready(): |
| 179 | + return |
| 180 | + sleep_ms(5) |
| 181 | + raise BME280Error("BME280 measurement timeout") |
| 182 | + |
| 183 | + # -------------------------------------------------- |
| 184 | + # Raw data burst read |
| 185 | + # -------------------------------------------------- |
| 186 | + |
| 187 | + def _read_raw(self): |
| 188 | + """Read raw ADC values via burst read (0xF7-0xFE, 8 bytes). |
| 189 | +
|
| 190 | + Returns (raw_temp, raw_press, raw_hum) as 20-bit/20-bit/16-bit integers. |
| 191 | + """ |
| 192 | + data = self._read_block(REG_DATA_START, DATA_BLOCK_SIZE) |
| 193 | + raw_press = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4) |
| 194 | + raw_temp = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4) |
| 195 | + raw_hum = (data[6] << 8) | data[7] |
| 196 | + return raw_temp, raw_press, raw_hum |
| 197 | + |
| 198 | + # -------------------------------------------------- |
| 199 | + # Compensation formulas (BME280 datasheet section 4.2.3) |
| 200 | + # -------------------------------------------------- |
| 201 | + |
| 202 | + def _compensate_temperature(self, raw_temp): |
| 203 | + """Compute compensated temperature in hundredths of °C. Updates t_fine.""" |
| 204 | + var1 = (((raw_temp >> 3) - (self.dig_T1 << 1)) * self.dig_T2) >> 11 |
| 205 | + var2 = ( |
| 206 | + (((raw_temp >> 4) - self.dig_T1) * ((raw_temp >> 4) - self.dig_T1)) >> 12 |
| 207 | + ) * self.dig_T3 >> 14 |
| 208 | + self.t_fine = var1 + var2 |
| 209 | + return (self.t_fine * 5 + 128) >> 8 |
| 210 | + |
| 211 | + def _compensate_pressure(self, raw_press): |
| 212 | + """Compute compensated pressure in Pa (Q24.8 fixed point).""" |
| 213 | + var1 = self.t_fine - 128000 |
| 214 | + var2 = var1 * var1 * self.dig_P6 |
| 215 | + var2 = var2 + ((var1 * self.dig_P5) << 17) |
| 216 | + var2 = var2 + (self.dig_P4 << 35) |
| 217 | + var1 = ((var1 * var1 * self.dig_P3) >> 8) + ((var1 * self.dig_P2) << 12) |
| 218 | + var1 = ((1 << 47) + var1) * self.dig_P1 >> 33 |
| 219 | + if var1 == 0: |
| 220 | + return 0 |
| 221 | + p = 1048576 - raw_press |
| 222 | + p = (((p << 31) - var2) * 3125) // var1 |
| 223 | + var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25 |
| 224 | + var2 = (self.dig_P8 * p) >> 19 |
| 225 | + return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4) |
| 226 | + |
| 227 | + def _compensate_humidity(self, raw_hum): |
| 228 | + """Compute compensated humidity in Q22.10 fixed point.""" |
| 229 | + h = self.t_fine - 76800 |
| 230 | + h = ( |
| 231 | + (((raw_hum << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) + 16384) |
| 232 | + >> 15 |
| 233 | + ) * ( |
| 234 | + ( |
| 235 | + ( |
| 236 | + (((h * self.dig_H6) >> 10) * (((h * self.dig_H3) >> 11) + 32768)) |
| 237 | + >> 10 |
| 238 | + ) |
| 239 | + + 2097152 |
| 240 | + ) |
| 241 | + * self.dig_H2 |
| 242 | + + 8192 |
| 243 | + ) >> 14 |
| 244 | + h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4) |
| 245 | + h = max(h, 0) |
| 246 | + h = min(h, 419430400) |
| 247 | + return h >> 12 |
| 248 | + |
| 249 | + # -------------------------------------------------- |
| 250 | + # Public measurement methods |
| 251 | + # -------------------------------------------------- |
| 252 | + |
| 253 | + def temperature(self): |
| 254 | + """Return compensated temperature in °C (float).""" |
| 255 | + raw_temp, _, _ = self._read_raw() |
| 256 | + return self._compensate_temperature(raw_temp) / 100.0 |
| 257 | + |
| 258 | + def pressure_hpa(self): |
| 259 | + """Return compensated pressure in hPa (float).""" |
| 260 | + raw_temp, raw_press, _ = self._read_raw() |
| 261 | + self._compensate_temperature(raw_temp) |
| 262 | + return self._compensate_pressure(raw_press) / 25600.0 |
| 263 | + |
| 264 | + def humidity(self): |
| 265 | + """Return compensated relative humidity in %RH (float).""" |
| 266 | + raw_temp, _, raw_hum = self._read_raw() |
| 267 | + self._compensate_temperature(raw_temp) |
| 268 | + return self._compensate_humidity(raw_hum) / 1024.0 |
| 269 | + |
| 270 | + def read(self): |
| 271 | + """Return (temperature_c, pressure_hpa, humidity_rh) tuple.""" |
| 272 | + raw_temp, raw_press, raw_hum = self._read_raw() |
| 273 | + temp_c = self._compensate_temperature(raw_temp) / 100.0 |
| 274 | + press_hpa = self._compensate_pressure(raw_press) / 25600.0 |
| 275 | + hum_rh = self._compensate_humidity(raw_hum) / 1024.0 |
| 276 | + return temp_c, press_hpa, hum_rh |
| 277 | + |
| 278 | + def read_one_shot(self): |
| 279 | + """Trigger a forced measurement, wait, and return (temp_c, press_hpa, hum_rh).""" |
| 280 | + self.trigger_one_shot() |
| 281 | + self._wait_measurement() |
| 282 | + return self.read() |
0 commit comments