A personal rust firmware for the Badger 2040 W

Real time and wifi counting

Changed files
+169 -28
src
+19
Cargo.lock
··· 246 246 ] 247 247 248 248 [[package]] 249 + name = "cobs" 250 + version = "0.2.3" 251 + source = "registry+https://github.com/rust-lang/crates.io-index" 252 + checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" 253 + 254 + [[package]] 249 255 name = "codespan-reporting" 250 256 version = "0.11.1" 251 257 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 902 908 "pio", 903 909 "pio-proc", 904 910 "portable-atomic", 911 + "postcard", 905 912 "rand", 906 913 "reqwless", 907 914 "serde", ··· 1412 1419 "atomic-polyfill", 1413 1420 "hash32 0.2.1", 1414 1421 "rustc_version 0.4.0", 1422 + "serde", 1415 1423 "spin", 1416 1424 "stable_deref_trait", 1417 1425 ] ··· 1818 1826 checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" 1819 1827 dependencies = [ 1820 1828 "critical-section", 1829 + ] 1830 + 1831 + [[package]] 1832 + name = "postcard" 1833 + version = "1.0.10" 1834 + source = "registry+https://github.com/rust-lang/crates.io-index" 1835 + checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e" 1836 + dependencies = [ 1837 + "cobs", 1838 + "heapless 0.7.17", 1839 + "serde", 1821 1840 ] 1822 1841 1823 1842 [[package]]
+1
Cargo.toml
··· 105 105 embedded-text = "0.7.0" 106 106 tinybmp = "0.5.0" 107 107 shtcx = "1.0.0" 108 + postcard = "1.0.8" 108 109 109 110 [profile.release] 110 111 debug = 2
+9 -15
src/badge_display/mod.rs
··· 9 9 use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; 10 10 use embassy_rp::gpio; 11 11 use embassy_rp::gpio::Input; 12 - use embassy_sync::{ 13 - blocking_mutex::{self, raw::CriticalSectionRawMutex}, 14 - mutex, 15 - }; 12 + use embassy_sync::blocking_mutex::{self, raw::CriticalSectionRawMutex}; 16 13 use embassy_time::{Delay, Duration, Timer}; 17 14 use embedded_graphics::{ 18 15 image::Image, 19 16 mono_font::{ascii::*, MonoTextStyle}, 20 - pixelcolor::{BinaryColor, Rgb565}, 17 + pixelcolor::BinaryColor, 21 18 prelude::*, 22 19 primitives::{PrimitiveStyle, PrimitiveStyleBuilder, Rectangle}, 23 20 text::Text, ··· 30 27 use gpio::Output; 31 28 use heapless::String; 32 29 use tinybmp::Bmp; 30 + use uc8151::asynch::Uc8151; 33 31 use uc8151::LUT; 34 32 use uc8151::WIDTH; 35 - use uc8151::{asynch::Uc8151, UpdateRegion}; 36 33 use {defmt_rtt as _, panic_probe as _}; 37 34 38 35 use crate::{env::env_value, helpers::easy_format, Spi0Bus}; ··· 45 42 blocking_mutex::Mutex::new(RefCell::new(String::<8>::new())); 46 43 pub static TEMP: AtomicU8 = AtomicU8::new(0); 47 44 pub static HUMIDITY: AtomicU8 = AtomicU8::new(0); 48 - pub static HOUR: AtomicU8 = AtomicU8::new(10); 49 - pub static MINUTE: AtomicU8 = AtomicU8::new(57); 50 - pub static SECOND: AtomicU8 = AtomicU8::new(0); 51 45 52 46 #[embassy_executor::task] 53 47 pub async fn run_the_display( ··· 118 112 } 119 113 120 114 //Updates the top bar 121 - //Runs every 30 cycles/15 seconds and first run 122 - if cycles_since_last_clear % 30 == 0 || first_run { 115 + //Runs every 60 cycles/30 seconds and first run 116 + if cycles_since_last_clear % 60 == 0 || first_run { 123 117 let count = WIFI_COUNT.load(core::sync::atomic::Ordering::Relaxed); 124 118 let temp = TEMP.load(core::sync::atomic::Ordering::Relaxed); 125 119 let humidity = HUMIDITY.load(core::sync::atomic::Ordering::Relaxed); 126 - let top_text: String<64> = 127 - easy_format::<64>(format_args!("{}F {}% Count: {}", temp, humidity, count)); 120 + let top_text: String<64> = easy_format::<64>(format_args!( 121 + "{}F {}% Wifi found: {}", 122 + temp, humidity, count 123 + )); 128 124 let top_bounds = Rectangle::new(Point::new(0, 0), Size::new(WIDTH, 24)); 129 125 top_bounds 130 126 .into_styled( ··· 149 145 info!("Error updating display"); 150 146 } 151 147 } 152 - 153 - WIFI_COUNT.store(count + 1, core::sync::atomic::Ordering::Relaxed); 154 148 } 155 149 156 150 //Runs every 120 cycles/60 seconds and first run
+77 -12
src/main.rs
··· 4 4 5 5 #![no_std] 6 6 #![no_main] 7 + use badge_display::display_image::DisplayImage; 8 + use badge_display::{run_the_display, CHANGE_IMAGE, CURRENT_IMAGE, RTC_TIME_STRING, WIFI_COUNT}; 9 + use core::fmt::Write; 7 10 use core::str::from_utf8; 8 - use core::time; 9 - 10 - use badge_display::display_image::DisplayImage; 11 - use badge_display::{run_the_display, CHANGE_IMAGE, CURRENT_IMAGE, RTC_TIME_STRING}; 12 11 use cyw43_driver::setup_cyw43; 13 - use cyw43_pio::PioSpi; 14 12 use defmt::info; 15 13 use defmt::*; 16 14 use embassy_executor::Spawner; ··· 18 16 use embassy_net::tcp::client::{TcpClient, TcpClientState}; 19 17 use embassy_net::{Stack, StackResources}; 20 18 use embassy_rp::clocks::RoscRng; 19 + use embassy_rp::flash::Async; 21 20 use embassy_rp::gpio; 22 21 use embassy_rp::gpio::Input; 23 22 use embassy_rp::peripherals::{DMA_CH0, PIO0, SPI0}; ··· 27 26 use embassy_sync::blocking_mutex::raw::NoopRawMutex; 28 27 use embassy_sync::mutex::Mutex; 29 28 use embassy_time::{Duration, Timer}; 29 + use embedded_hal_1::digital::OutputPin; 30 30 use env::env_value; 31 31 use gpio::{Level, Output, Pull}; 32 - use heapless::Vec; 32 + use heapless::{String, Vec}; 33 33 use helpers::easy_format; 34 34 use rand::RngCore; 35 35 use reqwless::client::{HttpClient, TlsConfig, TlsVerify}; 36 36 use reqwless::request::Method; 37 + use save::{read_postcard_from_flash, save_postcard_to_flash, Save}; 37 38 use serde::Deserialize; 38 39 use static_cell::StaticCell; 39 40 use temp_sensor::run_the_temp_sensor; ··· 43 44 mod cyw43_driver; 44 45 mod env; 45 46 mod helpers; 47 + mod save; 46 48 mod temp_sensor; 47 49 48 50 type Spi0Bus = Mutex<NoopRawMutex, Spi<'static, SPI0, spi::Async>>; 49 51 52 + const BSSID_LEN: usize = 1_000; 53 + const ADDR_OFFSET: u32 = 0x100000; 54 + const SAVE_OFFSET: u32 = 0x00; 55 + 56 + const FLASH_SIZE: usize = 2 * 1024 * 1024; 57 + 50 58 #[embassy_executor::main] 51 59 async fn main(spawner: Spawner) { 52 60 let p = embassy_rp::init(Default::default()); 61 + let mut user_led = Output::new(p.PIN_22, Level::High); 62 + user_led.set_high(); 63 + 53 64 let (net_device, mut control) = setup_cyw43( 54 65 p.PIO0, p.PIN_23, p.PIN_24, p.PIN_25, p.PIN_29, p.DMA_CH0, spawner, 55 66 ) ··· 74 85 let _btn_up = Input::new(p.PIN_15, Pull::Down); 75 86 let _btn_down = Input::new(p.PIN_11, Pull::Down); 76 87 let _btn_a = Input::new(p.PIN_12, Pull::Down); 77 - let _btn_b = Input::new(p.PIN_13, Pull::Down); 88 + let btn_b = Input::new(p.PIN_13, Pull::Down); 78 89 let btn_c = Input::new(p.PIN_14, Pull::Down); 79 90 80 91 let spi = Spi::new( ··· 242 253 } 243 254 } 244 255 245 - // 246 - 256 + //Set up saving 257 + let mut flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(p.FLASH, p.DMA_CH3); 258 + let mut save: Save = read_postcard_from_flash(ADDR_OFFSET, &mut flash, SAVE_OFFSET).unwrap(); 259 + WIFI_COUNT.store(save.wifi_counted, core::sync::atomic::Ordering::Relaxed); 247 260 //Task spawning 248 261 spawner.must_spawn(run_the_temp_sensor(p.I2C0, p.PIN_5, p.PIN_4)); 249 262 spawner.must_spawn(run_the_display(spi_bus, cs, dc, busy, reset)); 250 263 251 264 //Input loop 265 + let cycle = Duration::from_millis(100); 266 + let mut current_cycle = 0; 267 + //5 minutes 268 + let reset_cycle = 300_000; 269 + //Turn off led to signify that the badge is ready 270 + user_led.set_low(); 271 + 252 272 loop { 253 273 //Change Image Button 254 274 if btn_c.is_high() { ··· 258 278 CURRENT_IMAGE.store(new_image.as_u8(), core::sync::atomic::Ordering::Relaxed); 259 279 CHANGE_IMAGE.store(true, core::sync::atomic::Ordering::Relaxed); 260 280 Timer::after(Duration::from_millis(500)).await; 281 + current_cycle += 500; 282 + continue; 283 + } 284 + 285 + if btn_b.is_high() { 286 + user_led.toggle(); 287 + Timer::after(Duration::from_millis(500)).await; 288 + current_cycle += 500; 261 289 continue; 262 290 } 263 291 ··· 275 303 rtc_time_string.borrow_mut().push_str("No Wifi").unwrap(); 276 304 }); 277 305 } 278 - 279 - Timer::after(Duration::from_millis(100)).await; 306 + if current_cycle == 0 { 307 + let mut scanner = control.scan(Default::default()).await; 308 + while let Some(bss) = scanner.next().await { 309 + process_bssid(bss.bssid, &mut save.wifi_counted, &mut save.bssid); 310 + let ssid = core::str::from_utf8(&bss.ssid).unwrap(); 311 + info!("ssid: {}", ssid); 312 + } 313 + save_postcard_to_flash(ADDR_OFFSET, &mut flash, SAVE_OFFSET, &save).unwrap(); 314 + WIFI_COUNT.store(save.wifi_counted, core::sync::atomic::Ordering::Relaxed); 315 + info!("wifi_counted: {}", save.wifi_counted); 316 + } 317 + if current_cycle >= reset_cycle { 318 + current_cycle = 0; 319 + } 320 + current_cycle += 1; 321 + Timer::after(cycle).await; 280 322 } 281 323 } 282 324 ··· 316 358 struct TimeApiResponse<'a> { 317 359 datetime: &'a str, 318 360 day_of_week: u8, 319 - // other fields as needed 361 + } 362 + 363 + fn process_bssid(bssid: [u8; 6], wifi_counted: &mut u32, bssids: &mut Vec<String<17>, BSSID_LEN>) { 364 + let bssid_str = format_bssid(bssid); 365 + if !bssids.contains(&bssid_str) { 366 + *wifi_counted += 1; 367 + info!("bssid: {:x}", bssid_str); 368 + let result = bssids.push(bssid_str); 369 + if result.is_err() { 370 + info!("bssid list full"); 371 + bssids.clear(); 372 + } 373 + } 374 + } 375 + 376 + fn format_bssid(bssid: [u8; 6]) -> String<17> { 377 + let mut s = String::new(); 378 + for (i, byte) in bssid.iter().enumerate() { 379 + if i != 0 { 380 + let _ = s.write_char(':'); 381 + } 382 + core::fmt::write(&mut s, format_args!("{:02x}", byte)).unwrap(); 383 + } 384 + s 320 385 }
+62
src/save.rs
··· 1 + use crate::FLASH_SIZE; 2 + use embassy_rp::flash::{Async, ERASE_SIZE}; 3 + use embassy_rp::peripherals::FLASH; 4 + use heapless::{String, Vec}; 5 + use postcard::{from_bytes, to_slice}; 6 + use serde::{Deserialize, Serialize}; 7 + use {defmt_rtt as _, panic_probe as _}; 8 + 9 + const BSSID_LEN: usize = 1_000; 10 + 11 + pub fn save_postcard_to_flash( 12 + base_offset: u32, 13 + flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, 14 + offset: u32, 15 + data: &Save, 16 + ) -> Result<(), &'static str> { 17 + let mut buf = [0u8; ERASE_SIZE]; 18 + 19 + let mut write_buf = [0u8; ERASE_SIZE]; 20 + let written = to_slice(data, &mut write_buf).map_err(|_| "Serialization error")?; 21 + 22 + if written.len() > ERASE_SIZE { 23 + return Err("Data too large for flash sector"); 24 + } 25 + 26 + flash 27 + .blocking_erase( 28 + base_offset + offset, 29 + base_offset + offset + ERASE_SIZE as u32, 30 + ) 31 + .map_err(|_| "Erase error")?; 32 + 33 + buf[..written.len()].copy_from_slice(&written); 34 + 35 + flash 36 + .blocking_write(base_offset + offset, &buf) 37 + .map_err(|_| "Write error")?; 38 + 39 + Ok(()) 40 + } 41 + 42 + pub fn read_postcard_from_flash( 43 + base_offset: u32, 44 + flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, 45 + offset: u32, 46 + ) -> Result<Save, &'static str> { 47 + let mut buf = [0u8; ERASE_SIZE]; 48 + 49 + flash 50 + .blocking_read(base_offset + offset, &mut buf) 51 + .map_err(|_| "Read error")?; 52 + 53 + let data = from_bytes::<Save>(&buf).map_err(|_| "Deserialization error")?; 54 + 55 + Ok(data) 56 + } 57 + 58 + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] 59 + pub struct Save { 60 + pub wifi_counted: u32, 61 + pub bssid: Vec<String<17>, BSSID_LEN>, 62 + }
+1 -1
src/temp_sensor.rs
··· 33 33 combined.humidity.as_percent() as u8, 34 34 core::sync::atomic::Ordering::Relaxed, 35 35 ); 36 - Timer::after_secs(120).await; 36 + Timer::after_secs(30).await; 37 37 } 38 38 }