Pico with BME280 breakout (via Explorer Base and MicroPython)

from machine import Pin, I2C
from micropython import const
from time import sleep
import ustruct

_ADDR = const(0x76)

_CALIB2 = const(0xE1)
_CALIB1 = const(0x88)
_DATA = const(0xF7)
_CONFIG = const(0xF5)
_CTRL_MEAS = const(0xF4)
_CTRL_HUM = const(0xF2)
_CHIP_ID = const(0xD0)
_RESET = const(0xE0)


class TooManyAttempts(Exception):
    """Too many attempts"""


class BME280:
    def __init__(self, i2c):
        self._i2c = i2c
        self._ready = bytearray(1)
        self._chip_id = bytearray(1)
        self._data = bytearray(8)
        self._calib1 = bytearray(26)
        self._calib2 = bytearray(7)

    def ready(self):
        attempts = 100
        while True:
            self._i2c.readfrom_into(_ADDR, self._ready)
            if self._ready == b"\x00":
                break
            attempts -= 1
            if not attempts:
                raise TooManyAttempts()

    def chip_id(self):
        self.ready()
        self._i2c.readfrom_mem_into(_ADDR, _CHIP_ID, self._chip_id)
        return self._chip_id

    def reset(self):
        self.ready()
        self._i2c.writeto_mem(_ADDR, _RESET, b"\xb6")

    def ctrl_hum(self, osrs_h=16):
        self.ready()
        self._i2c.writeto_mem(_ADDR, _CTRL_HUM, chr(0b101))

    def ctrl_meas(self, osrs_t=16, osrs_p=16, mode="normal"):
        self.ready()
        self._i2c.writeto_mem(_ADDR, _CTRL_MEAS, chr(0b10110111))

    def config(self, t_sb=500, filter=2):
        self.ready()
        self._i2c.writeto_mem(_ADDR, _CONFIG, chr(0b10001000))

    def data(self):
        self.ready()
        self._i2c.readfrom_mem_into(_ADDR, _DATA, self._data)

    @property
    def raw_pressure(self):
        data = self._data
        return (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)

    @property
    def raw_temperature(self):
        data = self._data
        return (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)

    @property
    def raw_humidity(self):
        data = self._data
        return (data[6] << 8) | data[7]

    @property
    def temperature(self):
        temp_raw = self.raw_temperature
        dig_T1, dig_T2, dig_T3 = self._calib[:3]
        var1 = ((((temp_raw >> 3) - (dig_T1 << 1))) * (dig_T2)) >> 11
        var2 = (
            ((((temp_raw >> 4) - (dig_T1)) * ((temp_raw >> 4) - (dig_T1))) >> 12)
            * (dig_T3)
        ) >> 14
        t_fine = var1 + var2
        return float(((t_fine * 5) + 128) >> 8) / 100.0

    def calibration(self):
        self.ready()
        self._i2c.readfrom_mem_into(_ADDR, _CALIB1, self._calib1)
        self.ready()
        self._i2c.readfrom_mem_into(_ADDR, _CALIB2, self._calib2)
        self._calib = ustruct.unpack("HhhHhhhhhhhhBb", self._calib1) + ustruct.unpack(
            "hBbbbB", self._calib2
        )

    def init(self):
        self.reset()
        sleep(0.1)
        self.ctrl_hum()
        self.ctrl_meas()
        self.config()
        self.calibration()


bme280 = BME280(I2C(0, scl=Pin(21), sda=Pin(20)))
print(bme280.chip_id())
bme280.init()

then

bme280.data() refreshes the data and bme280.temperature is a float containing the current temperature.

My temperature calibration code is from MattHawkinsUK/rpispy-misc which is using bit shifting whereas the pimoroni code at pimoroni/bme280-python seems to be doing lots of floating point maths.