A personal rust firmware for the Badger 2040 W
at main 21 kB view raw
1#![no_std] 2#![no_main] 3 4use crate::pcf85063a::{Control, Error}; 5use badge_display::display_image::DisplayImage; 6use badge_display::{ 7 CHANGE_IMAGE, CURRENT_IMAGE, DISPLAY_CHANGED, FORCE_SCREEN_REFRESH, RECENT_WIFI_NETWORKS, 8 RTC_TIME_STRING, RecentWifiNetworksVec, SCREEN_TO_SHOW, Screen, WIFI_COUNT, run_the_display, 9}; 10use core::cell::RefCell; 11use core::fmt::Write; 12use core::str::from_utf8; 13use cyw43::JoinOptions; 14use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi}; 15use defmt::info; 16use defmt::*; 17use embassy_embedded_hal::shared_bus::I2cDeviceError; 18use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; 19use embassy_executor::Spawner; 20use embassy_net::StackResources; 21use embassy_net::dns::DnsSocket; 22use embassy_net::tcp::client::{TcpClient, TcpClientState}; 23use embassy_rp::clocks::RoscRng; 24use embassy_rp::flash::Async; 25use embassy_rp::gpio::Input; 26use embassy_rp::i2c::I2c; 27use embassy_rp::peripherals::{DMA_CH0, I2C0, PIO0, SPI0}; 28use embassy_rp::pio::{InterruptHandler, Pio}; 29use embassy_rp::rtc::{DateTime, DayOfWeek}; 30use embassy_rp::spi::Spi; 31use embassy_rp::spi::{self}; 32use embassy_rp::watchdog::Watchdog; 33use embassy_rp::{bind_interrupts, gpio, i2c}; 34use embassy_sync::blocking_mutex::NoopMutex; 35use embassy_sync::blocking_mutex::raw::NoopRawMutex; 36use embassy_sync::mutex::Mutex; 37use embassy_time::{Duration, Timer}; 38use env::env_value; 39use gpio::{Level, Output, Pull}; 40use heapless::{String, Vec}; 41use helpers::easy_format; 42use pcf85063a::PCF85063; 43use reqwless::client::{HttpClient, TlsConfig, TlsVerify}; 44use reqwless::request::Method; 45use save::{Save, read_postcard_from_flash, save_postcard_to_flash}; 46use serde::Deserialize; 47use static_cell::StaticCell; 48use temp_sensor::run_the_temp_sensor; 49use time::PrimitiveDateTime; 50use {defmt_rtt as _, panic_probe as _}; 51 52mod badge_display; 53mod env; 54mod helpers; 55mod pcf85063a; 56mod save; 57mod temp_sensor; 58 59type Spi0Bus = Mutex<NoopRawMutex, Spi<'static, SPI0, spi::Async>>; 60type I2c0Bus = NoopMutex<RefCell<I2c<'static, I2C0, i2c::Blocking>>>; 61 62const BSSID_LEN: usize = 1_000; 63const ADDR_OFFSET: u32 = 0x100000; 64const SAVE_OFFSET: u32 = 0x00; 65 66const FLASH_SIZE: usize = 2 * 1024 * 1024; 67 68bind_interrupts!(struct Irqs { 69 PIO0_IRQ_0 => InterruptHandler<PIO0>; 70}); 71 72#[embassy_executor::main] 73async fn main(spawner: Spawner) { 74 let p = embassy_rp::init(Default::default()); 75 let mut user_led = Output::new(p.PIN_22, Level::High); 76 user_led.set_high(); 77 78 //Wifi driver and cyw43 setup 79 let fw = include_bytes!("../cyw43-firmware/43439A0.bin"); 80 let clm = include_bytes!("../cyw43-firmware/43439A0_clm.bin"); 81 82 let pwr = Output::new(p.PIN_23, Level::Low); 83 let cs = Output::new(p.PIN_25, Level::High); 84 let mut pio = Pio::new(p.PIO0, Irqs); 85 let spi = PioSpi::new( 86 &mut pio.common, 87 pio.sm0, 88 DEFAULT_CLOCK_DIVIDER, 89 pio.irq0, 90 cs, 91 p.PIN_24, 92 p.PIN_29, 93 p.DMA_CH0, 94 ); 95 static STATE: StaticCell<cyw43::State> = StaticCell::new(); 96 let state = STATE.init(cyw43::State::new()); 97 let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; 98 spawner.must_spawn(cyw43_task(runner)); 99 100 control.init(clm).await; 101 control 102 .set_power_management(cyw43::PowerManagementMode::PowerSave) 103 .await; 104 105 let miso = p.PIN_16; 106 let mosi = p.PIN_19; 107 let clk = p.PIN_18; 108 let dc = p.PIN_20; 109 let cs = p.PIN_17; 110 let busy = p.PIN_26; 111 let reset = p.PIN_21; 112 let power = p.PIN_10; 113 114 let reset = Output::new(reset, Level::Low); 115 let mut power = Output::new(power, Level::Low); 116 power.set_high(); 117 118 let dc = Output::new(dc, Level::Low); 119 let cs = Output::new(cs, Level::High); 120 let busy = Input::new(busy, Pull::Up); 121 122 let btn_up = Input::new(p.PIN_15, Pull::Down); 123 let btn_down = Input::new(p.PIN_11, Pull::Down); 124 let btn_a = Input::new(p.PIN_12, Pull::Down); 125 let btn_b = Input::new(p.PIN_13, Pull::Down); 126 let btn_c = Input::new(p.PIN_14, Pull::Down); 127 let rtc_alarm = Input::new(p.PIN_8, Pull::Down); 128 let mut watchdog = Watchdog::new(p.WATCHDOG); 129 130 //Setup i2c bus 131 let config = embassy_rp::i2c::Config::default(); 132 let i2c = i2c::I2c::new_blocking(p.I2C0, p.PIN_5, p.PIN_4, config); 133 static I2C_BUS: StaticCell<I2c0Bus> = StaticCell::new(); 134 let i2c_bus = NoopMutex::new(RefCell::new(i2c)); 135 let i2c_bus = I2C_BUS.init(i2c_bus); 136 137 let i2c_dev = I2cDevice::new(i2c_bus); 138 let mut rtc_device = PCF85063::new(i2c_dev); 139 140 if btn_a.is_high() { 141 //Clears the alarm on start if A button is pressed (manual start) 142 _ = rtc_device.disable_all_alarms(); 143 _ = rtc_device.clear_alarm_flag(); 144 } 145 146 // //RTC alarm stuff 147 // let mut go_to_sleep = false; 148 // let mut reset_cycles_till_sleep = 0; 149 // //Like 15ish mins?? 150 // let sleep_after_cycles = 2; 151 // 152 // if rtc_alarm.is_high() { 153 // //sleep happened 154 // go_to_sleep = true; 155 // info!("Alarm went off"); 156 // _ = rtc_device.disable_all_alarms(); 157 // _ = rtc_device.clear_alarm_flag(); 158 // } else { 159 // info!("Alarm was clear") 160 // } 161 162 let spi = Spi::new( 163 p.SPI0, 164 clk, 165 mosi, 166 miso, 167 p.DMA_CH1, 168 p.DMA_CH2, 169 spi::Config::default(), 170 ); 171 172 //SPI Bus setup to run the e-ink display 173 static SPI_BUS: StaticCell<Spi0Bus> = StaticCell::new(); 174 let spi_bus = SPI_BUS.init(Mutex::new(spi)); 175 176 info!("led on!"); 177 // control.gpio_set(0, true).await; 178 179 //wifi setup 180 let mut rng = RoscRng; 181 182 let config = embassy_net::Config::dhcpv4(Default::default()); 183 let seed = rng.next_u64(); 184 185 // Init network stack 186 static RESOURCES: StaticCell<StackResources<5>> = StaticCell::new(); 187 let (stack, runner) = embassy_net::new( 188 net_device, 189 config, 190 RESOURCES.init(StackResources::new()), 191 seed, 192 ); 193 194 //If the watch dog isn't fed in 8 seconds reboot to help with hang up 195 watchdog.start(Duration::from_secs(8)); 196 197 spawner.must_spawn(net_task(runner)); 198 //Attempt to connect to wifi to get RTC time loop for 2 minutes 199 let mut wifi_connection_attempts = 0; 200 let mut connected_to_wifi = false; 201 202 let wifi_ssid = env_value("WIFI_SSID"); 203 let wifi_password = env_value("WIFI_PASSWORD"); 204 while wifi_connection_attempts < 30 { 205 watchdog.feed(); 206 match control 207 .join(wifi_ssid, JoinOptions::new(wifi_password.as_bytes())) 208 .await 209 { 210 Ok(_) => { 211 connected_to_wifi = true; 212 info!("join successful"); 213 break; 214 } 215 Err(err) => { 216 info!("join failed with status={}", err.status); 217 } 218 } 219 Timer::after(Duration::from_secs(1)).await; 220 wifi_connection_attempts += 1; 221 } 222 223 if connected_to_wifi { 224 //Feed the dog if it makes it this far 225 watchdog.feed(); 226 info!("waiting for DHCP..."); 227 while !stack.is_config_up() { 228 Timer::after_millis(100).await; 229 } 230 info!("DHCP is now up!"); 231 232 info!("waiting for link up..."); 233 while !stack.is_link_up() { 234 Timer::after_millis(500).await; 235 } 236 info!("Link is up!"); 237 238 info!("waiting for stack to be up..."); 239 stack.wait_config_up().await; 240 info!("Stack is up!"); 241 242 //RTC Web request 243 let mut rx_buffer = [0; 8192]; 244 let mut tls_read_buffer = [0; 16640]; 245 let mut tls_write_buffer = [0; 16640]; 246 let client_state = TcpClientState::<1, 1024, 1024>::new(); 247 let tcp_client = TcpClient::new(stack, &client_state); 248 let dns_client = DnsSocket::new(stack); 249 let tls_config = TlsConfig::new( 250 seed, 251 &mut tls_read_buffer, 252 &mut tls_write_buffer, 253 TlsVerify::None, 254 ); 255 256 Timer::after(Duration::from_millis(500)).await; 257 // let mut http_client = HttpClient::new(&tcp_client, &dns_client); 258 let mut http_client = HttpClient::new_with_tls(&tcp_client, &dns_client, tls_config); 259 260 let url = env_value("TIME_API"); 261 info!("connecting to {}", &url); 262 263 // Feeds the dog again for one last time 264 watchdog.feed(); 265 266 //If the call goes through set the rtc 267 match http_client.request(Method::GET, &url).await { 268 Ok(mut request) => { 269 let response = match request.send(&mut rx_buffer).await { 270 Ok(resp) => resp, 271 Err(e) => { 272 error!("Failed to send HTTP request: {:?}", e); 273 // error!("Failed to send HTTP request"); 274 return; // handle the error; 275 } 276 }; 277 278 let body = match from_utf8(response.body().read_to_end().await.unwrap()) { 279 Ok(b) => b, 280 Err(_e) => { 281 error!("Failed to read response body"); 282 return; // handle the error 283 } 284 }; 285 info!("Response body: {:?}", &body); 286 287 let bytes = body.as_bytes(); 288 match serde_json_core::de::from_slice::<TimeApiResponse>(bytes) { 289 Ok((output, _used)) => { 290 //Deadlines am i right? 291 info!("Datetime: {:?}", output.datetime); 292 //split at T 293 let datetime = output.datetime.split('T').collect::<Vec<&str, 2>>(); 294 //split at - 295 let date = datetime[0].split('-').collect::<Vec<&str, 3>>(); 296 let year = date[0].parse::<u16>().unwrap(); 297 let month = date[1].parse::<u8>().unwrap(); 298 let day = date[2].parse::<u8>().unwrap(); 299 //split at : 300 let time = datetime[1].split(':').collect::<Vec<&str, 4>>(); 301 let hour = time[0].parse::<u8>().unwrap(); 302 let minute = time[1].parse::<u8>().unwrap(); 303 //split at . 304 let second_split = time[2].split('.').collect::<Vec<&str, 2>>(); 305 let second = second_split[0].parse::<f64>().unwrap(); 306 let rtc_time = DateTime { 307 year: year, 308 month: month, 309 day: day, 310 day_of_week: match output.day_of_week { 311 0 => DayOfWeek::Sunday, 312 1 => DayOfWeek::Monday, 313 2 => DayOfWeek::Tuesday, 314 3 => DayOfWeek::Wednesday, 315 4 => DayOfWeek::Thursday, 316 5 => DayOfWeek::Friday, 317 6 => DayOfWeek::Saturday, 318 _ => DayOfWeek::Sunday, 319 }, 320 hour, 321 minute, 322 second: second as u8, 323 }; 324 325 rtc_device 326 .set_datetime(&rtc_time) 327 .expect("TODO: panic message"); 328 } 329 Err(_e) => { 330 error!("Failed to parse response body"); 331 // return; // handle the error 332 } 333 } 334 } 335 Err(e) => { 336 error!("Failed to make HTTP request: {:?}", e); 337 // return; // handle the error 338 } 339 }; 340 //leave the wifi no longer needed 341 let _ = control.leave().await; 342 } 343 344 //Set up saving 345 let mut flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(p.FLASH, p.DMA_CH3); 346 let mut save = 347 read_postcard_from_flash(ADDR_OFFSET, &mut flash, SAVE_OFFSET).unwrap_or_else(|err| { 348 error!("Error getting the save from the flash: {:?}", err); 349 Save::new() 350 }); 351 WIFI_COUNT.store(save.wifi_counted, core::sync::atomic::Ordering::Relaxed); 352 353 //Task spawning 354 spawner.must_spawn(run_the_temp_sensor(i2c_bus)); 355 spawner.must_spawn(run_the_display(spi_bus, cs, dc, busy, reset)); 356 357 //Input loop 358 let cycle = Duration::from_millis(100); 359 let mut current_cycle = 0; 360 let mut time_to_scan = true; 361 //5 minutes(ish) idk it's late and my math is so bad rn 362 let reset_cycle = 3_000; 363 364 //Turn off led to signify that the badge is ready 365 // user_led.set_low(); 366 367 loop { 368 //Keep feeding the dog 369 watchdog.feed(); 370 371 //Change Image Button 372 if btn_c.is_high() { 373 info!("Button C pressed"); 374 // reset_cycles_till_sleep = 0; 375 let current_image = CURRENT_IMAGE.load(core::sync::atomic::Ordering::Relaxed); 376 let new_image = DisplayImage::from_u8(current_image).unwrap().next(); 377 CURRENT_IMAGE.store(new_image.as_u8(), core::sync::atomic::Ordering::Relaxed); 378 CHANGE_IMAGE.store(true, core::sync::atomic::Ordering::Relaxed); 379 Timer::after(Duration::from_millis(500)).await; 380 continue; 381 } 382 383 if btn_a.is_high() { 384 println!("{:?}", current_cycle); 385 info!("Button A pressed"); 386 // reset_cycles_till_sleep = 0; 387 user_led.toggle(); 388 Timer::after(Duration::from_millis(500)).await; 389 continue; 390 } 391 392 if btn_down.is_high() { 393 info!("Button Down pressed"); 394 // reset_cycles_till_sleep = 0; 395 SCREEN_TO_SHOW.lock(|screen| { 396 screen.replace(Screen::WifiList); 397 }); 398 DISPLAY_CHANGED.store(true, core::sync::atomic::Ordering::Relaxed); 399 Timer::after(Duration::from_millis(500)).await; 400 continue; 401 } 402 403 if btn_up.is_high() { 404 info!("Button Up pressed"); 405 // reset_cycles_till_sleep = 0; 406 SCREEN_TO_SHOW.lock(|screen| { 407 screen.replace(Screen::Badge); 408 }); 409 DISPLAY_CHANGED.store(true, core::sync::atomic::Ordering::Relaxed); 410 Timer::after(Duration::from_millis(500)).await; 411 continue; 412 } 413 414 if btn_b.is_high() { 415 info!("Button B pressed"); 416 // reset_cycles_till_sleep = 0; 417 SCREEN_TO_SHOW.lock(|screen| { 418 if *screen.borrow() == Screen::Badge { 419 //IF on badge screen and b pressed reset wifi count 420 save.wifi_counted = 0; 421 save.bssid.clear(); 422 WIFI_COUNT.store(0, core::sync::atomic::Ordering::Relaxed); 423 current_cycle = 0; 424 } 425 }); 426 427 let mut recent_networks = RecentWifiNetworksVec::new(); 428 let mut scanner = control.scan(Default::default()).await; 429 430 while let Some(bss) = scanner.next().await { 431 process_bssid(bss.bssid, &mut save.wifi_counted, &mut save.bssid); 432 if recent_networks.len() < 8 { 433 let possible_ssid = core::str::from_utf8(&bss.ssid); 434 match possible_ssid { 435 Ok(ssid) => { 436 let removed_zeros = ssid.trim_end_matches(char::from(0)); 437 let ssid_string: String<32> = 438 easy_format::<32>(format_args!("{}", removed_zeros)); 439 440 if recent_networks.contains(&ssid_string) { 441 continue; 442 } 443 if ssid_string != "" { 444 let _ = recent_networks.push(ssid_string); 445 } 446 } 447 Err(_) => { 448 continue; 449 } 450 } 451 } 452 } 453 RECENT_WIFI_NETWORKS.lock(|recent_networks_vec| { 454 recent_networks_vec.replace(recent_networks); 455 }); 456 457 FORCE_SCREEN_REFRESH.store(true, core::sync::atomic::Ordering::Relaxed); 458 Timer::after(Duration::from_millis(500)).await; 459 460 continue; 461 } 462 463 match rtc_device.get_datetime() { 464 Ok(now) => set_display_time(now), 465 Err(_err) => { 466 error!("Error getting time"); 467 RTC_TIME_STRING.lock(|rtc_time_string| { 468 rtc_time_string.borrow_mut().clear(); 469 rtc_time_string.borrow_mut().push_str("Error").unwrap(); 470 }); 471 } 472 }; 473 474 if time_to_scan { 475 info!("Scanning for wifi networks"); 476 // reset_cycles_till_sleep += 1; 477 time_to_scan = false; 478 let mut scanner = control.scan(Default::default()).await; 479 while let Some(bss) = scanner.next().await { 480 process_bssid(bss.bssid, &mut save.wifi_counted, &mut save.bssid); 481 } 482 WIFI_COUNT.store(save.wifi_counted, core::sync::atomic::Ordering::Relaxed); 483 save_postcard_to_flash(ADDR_OFFSET, &mut flash, SAVE_OFFSET, &save).unwrap(); 484 info!("wifi_counted: {}", save.wifi_counted); 485 } 486 if current_cycle >= reset_cycle { 487 current_cycle = 0; 488 time_to_scan = true; 489 } 490 491 // if reset_cycles_till_sleep >= sleep_after_cycles { 492 // info!("Going to sleep"); 493 // reset_cycles_till_sleep = 0; 494 // go_to_sleep = true; 495 // } 496 // 497 // if go_to_sleep { 498 // info!("going to sleep"); 499 // //SO i need to wait for 25 seconds to make sure the display updates fully...But i need to keep feeding the dog atleast every 8 seconds 500 // for _ in 0..25 { 501 // //watchdog.feed(); 502 // Timer::after(Duration::from_secs(1)).await; 503 // } 504 // //Set the rtc and sleep for 15 minutes 505 // //goes to sleep for 15 mins 506 // _ = rtc_device.disable_all_alarms(); 507 // _ = rtc_device.clear_alarm_flag(); 508 // _ = rtc_device.set_alarm_minutes(5); 509 // _ = rtc_device.control_alarm_minutes(Control::On); 510 // _ = rtc_device.control_alarm_interrupt(Control::On); 511 // power.set_low(); 512 // } 513 514 current_cycle += 1; 515 Timer::after(cycle).await; 516 } 517} 518 519fn set_display_time(time: PrimitiveDateTime) { 520 let mut am = true; 521 let twelve_hour = if time.hour() == 0 { 522 12 523 } else if time.hour() == 12 { 524 am = false; 525 12 526 } else if time.hour() > 12 { 527 am = false; 528 time.hour() - 12 529 } else { 530 time.hour() 531 }; 532 533 let am_pm = if am { "AM" } else { "PM" }; 534 535 let formatted_time = easy_format::<8>(format_args!( 536 "{:02}:{:02} {}", 537 twelve_hour, 538 time.minute(), 539 am_pm 540 )); 541 542 RTC_TIME_STRING.lock(|rtc_time_string| { 543 rtc_time_string.borrow_mut().clear(); 544 rtc_time_string 545 .borrow_mut() 546 .push_str(formatted_time.as_str()) 547 .unwrap(); 548 }); 549} 550 551#[embassy_executor::task] 552async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { 553 runner.run().await 554} 555 556#[embassy_executor::task] 557async fn cyw43_task( 558 runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, 559) -> ! { 560 runner.run().await 561} 562 563#[derive(Deserialize)] 564struct TimeApiResponse<'a> { 565 datetime: &'a str, 566 day_of_week: u8, 567} 568 569fn process_bssid(bssid: [u8; 6], wifi_counted: &mut u32, bssids: &mut Vec<String<17>, BSSID_LEN>) { 570 let bssid_str = format_bssid(bssid); 571 if !bssids.contains(&bssid_str) { 572 *wifi_counted += 1; 573 WIFI_COUNT.store(*wifi_counted, core::sync::atomic::Ordering::Relaxed); 574 // info!("bssid: {:x}", bssid_str); 575 let result = bssids.push(bssid_str); 576 if result.is_err() { 577 info!("bssid list full"); 578 bssids.clear(); 579 } 580 } 581} 582 583fn format_bssid(bssid: [u8; 6]) -> String<17> { 584 let mut s = String::new(); 585 for (i, byte) in bssid.iter().enumerate() { 586 if i != 0 { 587 let _ = s.write_char(':'); 588 } 589 core::fmt::write(&mut s, format_args!("{:02x}", byte)).unwrap(); 590 } 591 s 592}