use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::gpio::{Level, Output};
use embassy_rp::i2c::{Async, I2c, Config as I2cConfig, InterruptHandler as I2cIrq};
use embassy_rp::peripherals::{DMA_CH0, I2C0, PIO0};
use embassy_rp::pio::{InterruptHandler as PioIrq, Pio};
use embassy_net::tcp::TcpSocket;
use embassy_net::{Config as NetConfig, StackResources};
use embassy_time::{Duration, Timer};
use static_cell::StaticCell;
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => PioIrq<PIO0>;
I2C0_IRQ => I2cIrq<I2C0>;
// ── Configuration ──────────────────────────────────────────────────
const WIFI_SSID: &str = "YourNetworkName";
const WIFI_PASS: &str = "YourNetworkPassword";
const MQTT_BROKER_IP: [u8; 4] = [192, 168, 1, 100]; // Your broker IP
const MQTT_PORT: u16 = 1883;
const MQTT_CLIENT_ID: &str = "pico-w-bme280";
const MQTT_TOPIC: &str = "sensors/pico-w/bme280";
const PUBLISH_INTERVAL_SECS: u64 = 10;
// ── BME280 constants ───────────────────────────────────────────────
const BME280_ADDR: u8 = 0x76;
struct Bme280Calibration {
dig_t1: u16, dig_t2: i16, dig_t3: i16,
dig_p1: u16, dig_p2: i16, dig_p3: i16, dig_p4: i16,
dig_p5: i16, dig_p6: i16, dig_p7: i16, dig_p8: i16, dig_p9: i16,
dig_h1: u8, dig_h2: i16, dig_h3: u8, dig_h4: i16, dig_h5: i16, dig_h6: i8,
// ── Background Tasks ───────────────────────────────────────────────
#[embassy_executor::task]
runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>,
#[embassy_executor::task]
async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! {
// ── Main ───────────────────────────────────────────────────────────
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
// ── Status LED ─────────────────────────────────────────────────
let mut led = Output::new(p.PIN_16, Level::Low);
// ── CYW43 Initialization ───────────────────────────────────────
let fw = cyw43_firmware::firmware();
let clm = cyw43_firmware::clm();
let pwr = Output::new(p.PIN_23, Level::Low);
let cs = Output::new(p.PIN_25, Level::High);
let mut pio = Pio::new(p.PIO0, Irqs);
static STATE: StaticCell<cyw43::State> = StaticCell::new();
let state = STATE.init(cyw43::State::new());
let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await;
spawner.spawn(cyw43_task(runner)).unwrap();
control.set_power_management(cyw43::PowerManagement::PowerSave).await;
// ── Network Stack ──────────────────────────────────────────────
let net_config = NetConfig::dhcpv4(Default::default());
static RESOURCES: StaticCell<StackResources<5>> = StaticCell::new();
let resources = RESOURCES.init(StackResources::new());
let (stack, net_runner) = embassy_net::new(
net_device, net_config, resources, embassy_rp::clocks::RoscRng,
spawner.spawn(net_task(net_runner)).unwrap();
// ── Connect to Wi-Fi ───────────────────────────────────────────
info!("Connecting to Wi-Fi: {}", WIFI_SSID);
match control.join_wpa2(WIFI_SSID, WIFI_PASS).await {
info!("Wi-Fi connected");
warn!("Wi-Fi join failed: status={}", e.status);
Timer::after(Duration::from_secs(2)).await;
info!("Waiting for DHCP...");
if let Some(config) = stack.config_v4() {
info!("IP address: {}", config.address);
Timer::after(Duration::from_millis(500)).await;
// Blink LED 3 times to indicate Wi-Fi is connected
Timer::after(Duration::from_millis(200)).await;
Timer::after(Duration::from_millis(200)).await;
// ── Initialize BME280 ──────────────────────────────────────────
let mut i2c = I2c::new_async(p.I2C0, p.PIN_5, p.PIN_4, Irqs, I2cConfig::default());
let cal = match bme280_init(&mut i2c).await {
info!("BME280 initialized");
error!("BME280 initialization failed. Check wiring and I2C address.");
// Rapid blink to indicate sensor error
Timer::after(Duration::from_millis(100)).await;
Timer::after(Duration::from_millis(100)).await;
// ── MQTT Publish Loop with Reconnection ────────────────────────
let broker_addr = embassy_net::Ipv4Address::new(
MQTT_BROKER_IP[0], MQTT_BROKER_IP[1],
MQTT_BROKER_IP[2], MQTT_BROKER_IP[3],
let mut backoff_secs: u64 = 1;
let mut json_buf = [0u8; 256];
// Create TCP socket for this connection attempt
let mut rx_buf = [0u8; 1024];
let mut tx_buf = [0u8; 1024];
let socket = TcpSocket::new(stack, &mut rx_buf, &mut tx_buf);
let mut mqtt = MqttClient::new(socket);
info!("Connecting to MQTT broker...");
mqtt.socket.set_timeout(Some(Duration::from_secs(10)));
if let Err(e) = mqtt.socket.connect((broker_addr, MQTT_PORT)).await {
warn!("TCP connect failed: {:?}", e);
Timer::after(Duration::from_secs(backoff_secs)).await;
backoff_secs = (backoff_secs * 2).min(30);
if let Err(e) = mqtt.connect(MQTT_CLIENT_ID).await {
warn!("MQTT connect failed: {:?}", e);
Timer::after(Duration::from_secs(backoff_secs)).await;
backoff_secs = (backoff_secs * 2).min(30);
info!("MQTT connected to broker");
backoff_secs = 1; // Reset backoff on successful connection
led.set_high(); // Solid LED = connected
let mut ping_counter: u32 = 0;
match bme280_read(&mut i2c, &cal).await {
let json_len = format_json(
"Temp: {}.{} C, Hum: {}.{} %, Press: {} Pa",
reading.temperature_c / 100,
(reading.temperature_c % 100).unsigned_abs(),
reading.humidity_pct / 100,
reading.humidity_pct % 100,
if let Err(_) = mqtt.publish(MQTT_TOPIC, &json_buf[..json_len]).await {
warn!("MQTT publish failed, reconnecting...");
// Brief LED blink to indicate successful publish
Timer::after(Duration::from_millis(50)).await;
warn!("BME280 read failed");
// Send MQTT PINGREQ every 5 publishes (every 50 seconds)
if let Err(_) = mqtt.ping().await {
warn!("MQTT ping failed, reconnecting...");
Timer::after(Duration::from_secs(PUBLISH_INTERVAL_SECS)).await;
// Disconnected; turn off LED and retry
info!("MQTT disconnected, will retry in {} seconds", backoff_secs);
Timer::after(Duration::from_secs(backoff_secs)).await;
backoff_secs = (backoff_secs * 2).min(30);
// ── MQTT Client ────────────────────────────────────────────────────
impl<'a> MqttClient<'a> {
fn new(socket: TcpSocket<'a>) -> Self {
async fn connect(&mut self, client_id: &str) -> Result<(), MqttError> {
self.buf[pos] = 0x10; pos += 1; // CONNECT packet type
let id_bytes = client_id.as_bytes();
let remaining = 10 + 2 + id_bytes.len(); // Variable header (10) + client ID string
self.buf[pos] = remaining as u8; pos += 1;
self.buf[pos] = 0x00; pos += 1;
self.buf[pos] = 0x04; pos += 1;
self.buf[pos..pos + 4].copy_from_slice(b"MQTT"); pos += 4;
// Protocol Level (4 = MQTT 3.1.1)
self.buf[pos] = 0x04; pos += 1;
// Connect Flags: Clean Session
self.buf[pos] = 0x02; pos += 1;
// Keep Alive: 60 seconds
self.buf[pos] = 0x00; pos += 1;
self.buf[pos] = 0x3C; pos += 1;
self.buf[pos] = (id_bytes.len() >> 8) as u8; pos += 1;
self.buf[pos] = (id_bytes.len() & 0xFF) as u8; pos += 1;
self.buf[pos..pos + id_bytes.len()].copy_from_slice(id_bytes);
self.socket.write_all(&self.buf[..pos]).await.map_err(|_| MqttError::WriteError)?;
let mut connack = [0u8; 4];
self.socket.read_exact(&mut connack).await.map_err(|_| MqttError::ReadError)?;
if connack[0] != 0x20 || connack[3] != 0x00 {
return Err(MqttError::ConnectRejected);
async fn publish(&mut self, topic: &str, payload: &[u8]) -> Result<(), MqttError> {
let topic_bytes = topic.as_bytes();
let remaining_len = 2 + topic_bytes.len() + payload.len();
self.buf[pos] = 0x30; pos += 1; // PUBLISH, QoS 0
// Remaining length (variable-length encoding)
let mut rem = remaining_len;
let mut byte = (rem % 128) as u8;
if rem > 0 { byte |= 0x80; }
self.buf[pos] = byte; pos += 1;
self.buf[pos] = (topic_bytes.len() >> 8) as u8; pos += 1;
self.buf[pos] = (topic_bytes.len() & 0xFF) as u8; pos += 1;
self.buf[pos..pos + topic_bytes.len()].copy_from_slice(topic_bytes);
pos += topic_bytes.len();
self.buf[pos..pos + payload.len()].copy_from_slice(payload);
self.socket.write_all(&self.buf[..pos]).await.map_err(|_| MqttError::WriteError)
async fn ping(&mut self) -> Result<(), MqttError> {
self.socket.write_all(&[0xC0, 0x00]).await.map_err(|_| MqttError::WriteError)?;
self.socket.read_exact(&mut resp).await.map_err(|_| MqttError::ReadError)?;
if resp[0] != 0xD0 { return Err(MqttError::PingFailed); }
// ── BME280 Driver ──────────────────────────────────────────────────
async fn bme280_init(i2c: &mut I2c<'_, I2C0, Async>) -> Result<Bme280Calibration, ()> {
i2c.write_read(BME280_ADDR, &[0xD0], &mut id).await.map_err(|_| ())?;
i2c.write(BME280_ADDR, &[0xE0, 0xB6]).await.map_err(|_| ())?;
Timer::after(Duration::from_millis(10)).await;
let mut cal_buf = [0u8; 26];
i2c.write_read(BME280_ADDR, &[0x88], &mut cal_buf).await.map_err(|_| ())?;
let mut cal_h = [0u8; 7];
i2c.write_read(BME280_ADDR, &[0xE1], &mut cal_h).await.map_err(|_| ())?;
let mut dig_h1 = [0u8; 1];
i2c.write_read(BME280_ADDR, &[0xA1], &mut dig_h1).await.map_err(|_| ())?;
let cal = Bme280Calibration {
dig_t1: u16::from_le_bytes([cal_buf[0], cal_buf[1]]),
dig_t2: i16::from_le_bytes([cal_buf[2], cal_buf[3]]),
dig_t3: i16::from_le_bytes([cal_buf[4], cal_buf[5]]),
dig_p1: u16::from_le_bytes([cal_buf[6], cal_buf[7]]),
dig_p2: i16::from_le_bytes([cal_buf[8], cal_buf[9]]),
dig_p3: i16::from_le_bytes([cal_buf[10], cal_buf[11]]),
dig_p4: i16::from_le_bytes([cal_buf[12], cal_buf[13]]),
dig_p5: i16::from_le_bytes([cal_buf[14], cal_buf[15]]),
dig_p6: i16::from_le_bytes([cal_buf[16], cal_buf[17]]),
dig_p7: i16::from_le_bytes([cal_buf[18], cal_buf[19]]),
dig_p8: i16::from_le_bytes([cal_buf[20], cal_buf[21]]),
dig_p9: i16::from_le_bytes([cal_buf[22], cal_buf[23]]),
dig_h2: i16::from_le_bytes([cal_h[0], cal_h[1]]),
dig_h4: ((cal_h[3] as i16) << 4) | ((cal_h[4] as i16) & 0x0F),
dig_h5: ((cal_h[5] as i16) << 4) | (((cal_h[4] as i16) >> 4) & 0x0F),
i2c.write(BME280_ADDR, &[0xF2, 0x01]).await.map_err(|_| ())?;
i2c.write(BME280_ADDR, &[0xF4, 0x27]).await.map_err(|_| ())?;
i2c.write(BME280_ADDR, &[0xF5, 0xA0]).await.map_err(|_| ())?;
i2c: &mut I2c<'_, I2C0, Async>,
) -> Result<Bme280Reading, ()> {
i2c.write_read(BME280_ADDR, &[0xF7], &mut raw).await.map_err(|_| ())?;
let adc_p = ((raw[0] as i32) << 12) | ((raw[1] as i32) << 4) | ((raw[2] as i32) >> 4);
let adc_t = ((raw[3] as i32) << 12) | ((raw[4] as i32) << 4) | ((raw[5] as i32) >> 4);
let adc_h = ((raw[6] as i32) << 8) | (raw[7] as i32);
let var1 = ((((adc_t >> 3) - ((cal.dig_t1 as i32) << 1))) * (cal.dig_t2 as i32)) >> 11;
let var2 = (((((adc_t >> 4) - (cal.dig_t1 as i32))
* ((adc_t >> 4) - (cal.dig_t1 as i32))) >> 12)
* (cal.dig_t3 as i32)) >> 14;
let t_fine = var1 + var2;
let temperature = (t_fine * 5 + 128) >> 8;
let mut var1_p = (t_fine as i64) - 128000;
let mut var2_p = var1_p * var1_p * (cal.dig_p6 as i64);
var2_p = var2_p + ((var1_p * (cal.dig_p5 as i64)) << 17);
var2_p = var2_p + ((cal.dig_p4 as i64) << 35);
var1_p = ((var1_p * var1_p * (cal.dig_p3 as i64)) >> 8)
+ ((var1_p * (cal.dig_p2 as i64)) << 12);
var1_p = (((1i64 << 47) + var1_p) * (cal.dig_p1 as i64)) >> 33;
let pressure = if var1_p == 0 {
let mut p: i64 = 1048576 - adc_p as i64;
p = (((p << 31) - var2_p) * 3125) / var1_p;
let v1 = ((cal.dig_p9 as i64) * (p >> 13) * (p >> 13)) >> 25;
let v2 = ((cal.dig_p8 as i64) * p) >> 19;
((p + v1 + v2) >> 8) as u32 + (((cal.dig_p7 as i64) << 4) as u32)
let mut v_x1 = t_fine - 76800i32;
v_x1 = (((((adc_h << 14) - ((cal.dig_h4 as i32) << 20)
- ((cal.dig_h5 as i32) * v_x1)) + 16384) >> 15)
* (((((((v_x1 * (cal.dig_h6 as i32)) >> 10)
* (((v_x1 * (cal.dig_h3 as i32)) >> 11) + 32768)) >> 10)
+ 2097152) * (cal.dig_h2 as i32) + 8192) >> 14));
v_x1 = v_x1 - (((((v_x1 >> 15) * (v_x1 >> 15)) >> 7) * (cal.dig_h1 as i32)) >> 4);
let v_x1 = if v_x1 < 0 { 0 } else if v_x1 > 419430400 { 419430400 } else { v_x1 };
let humidity = (v_x1 >> 12) as u32;
temperature_c: temperature,
humidity_pct: (humidity * 100) / 1024,
pressure_pa: pressure / 256,
// ── JSON Formatter ─────────────────────────────────────────────────
fn format_json(buf: &mut [u8], temp_c: i32, hum_pct: u32, press_pa: u32) -> usize {
let h = b"{\"temperature\":";
buf[pos..pos + h.len()].copy_from_slice(h);
pos += write_fixed(&mut buf[pos..], temp_c);
let h2 = b",\"humidity\":";
buf[pos..pos + h2.len()].copy_from_slice(h2);
pos += write_fixed(&mut buf[pos..], hum_pct as i32);
let h3 = b",\"pressure\":";
buf[pos..pos + h3.len()].copy_from_slice(h3);
pos += write_u32_str(&mut buf[pos..], press_pa);
fn write_fixed(buf: &mut [u8], val: i32) -> usize {
if val < 0 { buf[pos] = b'-'; pos += 1; }
let abs = val.unsigned_abs();
pos += write_u32_str(&mut buf[pos..], abs / 100);
buf[pos] = b'.'; pos += 1;
if frac < 10 { buf[pos] = b'0'; pos += 1; }
pos += write_u32_str(&mut buf[pos..], frac);
fn write_u32_str(buf: &mut [u8], val: u32) -> usize {
if val == 0 { buf[0] = b'0'; return 1; }
while n > 0 { tmp[i] = b'0' + (n % 10) as u8; n /= 10; i += 1; }
for j in 0..i { buf[j] = tmp[i - 1 - j]; }
Comments