A personal rust firmware for the Badger 2040 W
1//! This example test the RP Pico W on board LED.
2//!
3//! It does not work with the RP Pico board.
4
5#![no_std]
6#![no_main]
7
8use badge_display::display_image::DisplayImage;
9use badge_display::{
10 CHANGE_IMAGE, CURRENT_IMAGE, DISPLAY_CHANGED, FORCE_SCREEN_REFRESH, RECENT_WIFI_NETWORKS,
11 RTC_TIME_STRING, RecentWifiNetworksVec, SCREEN_TO_SHOW, Screen, WIFI_COUNT, run_the_display,
12};
13use core::cell::RefCell;
14use core::fmt::Write;
15use core::str::from_utf8;
16use cyw43::JoinOptions;
17// use cyw43_driver::setup_cyw43;
18use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi};
19use defmt::info;
20use defmt::*;
21use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
22use embassy_executor::Spawner;
23use embassy_net::dns::DnsSocket;
24use embassy_net::tcp::client::{TcpClient, TcpClientState};
25use embassy_net::{Stack, StackResources};
26use embassy_rp::clocks::RoscRng;
27use embassy_rp::flash::Async;
28use embassy_rp::gpio::Input;
29use embassy_rp::i2c::I2c;
30use embassy_rp::peripherals::{DMA_CH0, I2C0, PIO0, SPI0};
31use embassy_rp::pio::{InterruptHandler, Pio};
32use embassy_rp::rtc::{DateTime, DayOfWeek};
33use embassy_rp::spi::Spi;
34use embassy_rp::spi::{self};
35use embassy_rp::{bind_interrupts, gpio, i2c};
36use embassy_sync::blocking_mutex::NoopMutex;
37use embassy_sync::blocking_mutex::raw::NoopRawMutex;
38use embassy_sync::mutex::Mutex;
39use embassy_time::{Duration, Timer};
40use env::env_value;
41use gpio::{Level, Output, Pull};
42use heapless::{String, Vec};
43use helpers::easy_format;
44use rand::RngCore;
45use reqwless::client::{HttpClient, TlsConfig, TlsVerify};
46use reqwless::request::Method;
47use save::{Save, read_postcard_from_flash, save_postcard_to_flash};
48use serde::Deserialize;
49use static_cell::StaticCell;
50use temp_sensor::run_the_temp_sensor;
51use {defmt_rtt as _, panic_probe as _};
52
53mod badge_display;
54mod env;
55mod helpers;
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.spawn(cyw43_task(runner));
99
100 control.init(clm).await;
101 control
102 .set_power_management(cyw43::PowerManagementMode::PowerSave)
103 .await;
104 // let (net_device, mut control) = setup_cyw43(
105 // p.PIO0, p.PIN_23, p.PIN_24, p.PIN_25, p.PIN_29, p.DMA_CH0, spawner,
106 // )
107 // .await;
108
109 let miso = p.PIN_16;
110 let mosi = p.PIN_19;
111 let clk = p.PIN_18;
112 let dc = p.PIN_20;
113 let cs = p.PIN_17;
114 let busy = p.PIN_26;
115 let reset = p.PIN_21;
116 let power = p.PIN_10;
117
118 let reset = Output::new(reset, Level::Low);
119 let mut power = Output::new(power, Level::Low);
120 power.set_high();
121
122 let dc = Output::new(dc, Level::Low);
123 let cs = Output::new(cs, Level::High);
124 let busy = Input::new(busy, Pull::Up);
125
126 let btn_up = Input::new(p.PIN_15, Pull::Down);
127 let btn_down = Input::new(p.PIN_11, Pull::Down);
128 let btn_a = Input::new(p.PIN_12, Pull::Down);
129 let btn_b = Input::new(p.PIN_13, Pull::Down);
130 let btn_c = Input::new(p.PIN_14, Pull::Down);
131
132 let spi = Spi::new(
133 p.SPI0,
134 clk,
135 mosi,
136 miso,
137 p.DMA_CH1,
138 p.DMA_CH2,
139 spi::Config::default(),
140 );
141
142 //SPI Bus setup to run the e-ink display
143 static SPI_BUS: StaticCell<Spi0Bus> = StaticCell::new();
144 let spi_bus = SPI_BUS.init(Mutex::new(spi));
145
146 info!("led on!");
147 // control.gpio_set(0, true).await;
148
149 //wifi setup
150 let mut rng = RoscRng;
151
152 let config = embassy_net::Config::dhcpv4(Default::default());
153 let seed = rng.next_u64();
154
155 // Init network stack
156 static STACK: StaticCell<Stack> = StaticCell::new();
157 static RESOURCES: StaticCell<StackResources<5>> = StaticCell::new();
158 let (stack, runner) = embassy_net::new(
159 net_device,
160 config,
161 RESOURCES.init(StackResources::new()),
162 seed,
163 );
164 // let stack = &*STACK.init(Stack::new(
165 // net_device,
166 // config,
167 // RESOURCES.init(StackResources::<5>::new()),
168 // seed,
169 // ));
170 //rtc setup
171 let mut rtc = embassy_rp::rtc::Rtc::new(p.RTC);
172
173 spawner.spawn(net_task(runner));
174 //Attempt to connect to wifi to get RTC time loop for 2 minutes
175 let mut wifi_connection_attempts = 0;
176 let mut connected_to_wifi = false;
177
178 let wifi_ssid = env_value("WIFI_SSID");
179 let wifi_password = env_value("WIFI_PASSWORD");
180 while wifi_connection_attempts < 30 {
181 match control
182 .join(wifi_ssid, JoinOptions::new(wifi_password.as_bytes()))
183 .await
184 {
185 Ok(_) => {
186 connected_to_wifi = true;
187 info!("join successful");
188 break;
189 }
190 Err(err) => {
191 info!("join failed with status={}", err.status);
192 }
193 }
194 Timer::after(Duration::from_secs(1)).await;
195 wifi_connection_attempts += 1;
196 }
197
198 let mut time_was_set = false;
199 if connected_to_wifi {
200 info!("waiting for DHCP...");
201 while !stack.is_config_up() {
202 Timer::after_millis(100).await;
203 }
204 info!("DHCP is now up!");
205
206 info!("waiting for link up...");
207 while !stack.is_link_up() {
208 Timer::after_millis(500).await;
209 }
210 info!("Link is up!");
211
212 info!("waiting for stack to be up...");
213 stack.wait_config_up().await;
214 info!("Stack is up!");
215
216 //RTC Web request
217 let mut rx_buffer = [0; 8192];
218 let mut tls_read_buffer = [0; 16640];
219 let mut tls_write_buffer = [0; 16640];
220 let client_state = TcpClientState::<1, 1024, 1024>::new();
221 let tcp_client = TcpClient::new(stack, &client_state);
222 let dns_client = DnsSocket::new(stack);
223 let tls_config = TlsConfig::new(
224 seed,
225 &mut tls_read_buffer,
226 &mut tls_write_buffer,
227 TlsVerify::None,
228 );
229
230 let mut http_client = HttpClient::new_with_tls(&tcp_client, &dns_client, tls_config);
231 let url = env_value("TIME_API");
232 info!("connecting to {}", &url);
233
234 let mut request = match http_client.request(Method::GET, &url).await {
235 Ok(req) => req,
236 Err(e) => {
237 error!("Failed to make HTTP request: {:?}", e);
238 return; // handle the error
239 }
240 };
241
242 let response = match request.send(&mut rx_buffer).await {
243 Ok(resp) => resp,
244 Err(_e) => {
245 error!("Failed to send HTTP request");
246 return; // handle the error;
247 }
248 };
249
250 let body = match from_utf8(response.body().read_to_end().await.unwrap()) {
251 Ok(b) => b,
252 Err(_e) => {
253 error!("Failed to read response body");
254 return; // handle the error
255 }
256 };
257 info!("Response body: {:?}", &body);
258
259 let bytes = body.as_bytes();
260 match serde_json_core::de::from_slice::<TimeApiResponse>(bytes) {
261 Ok((output, _used)) => {
262 //Deadlines am i right?
263 info!("Datetime: {:?}", output.datetime);
264 //split at T
265 let datetime = output.datetime.split('T').collect::<Vec<&str, 2>>();
266 //split at -
267 let date = datetime[0].split('-').collect::<Vec<&str, 3>>();
268 let year = date[0].parse::<u16>().unwrap();
269 let month = date[1].parse::<u8>().unwrap();
270 let day = date[2].parse::<u8>().unwrap();
271 //split at :
272 let time = datetime[1].split(':').collect::<Vec<&str, 4>>();
273 let hour = time[0].parse::<u8>().unwrap();
274 let minute = time[1].parse::<u8>().unwrap();
275 //split at .
276 let second_split = time[2].split('.').collect::<Vec<&str, 2>>();
277 let second = second_split[0].parse::<f64>().unwrap();
278 let rtc_time = DateTime {
279 year: year,
280 month: month,
281 day: day,
282 day_of_week: match output.day_of_week {
283 0 => DayOfWeek::Sunday,
284 1 => DayOfWeek::Monday,
285 2 => DayOfWeek::Tuesday,
286 3 => DayOfWeek::Wednesday,
287 4 => DayOfWeek::Thursday,
288 5 => DayOfWeek::Friday,
289 6 => DayOfWeek::Saturday,
290 _ => DayOfWeek::Sunday,
291 },
292 hour,
293 minute,
294 second: second as u8,
295 };
296 rtc.set_datetime(rtc_time).unwrap();
297 time_was_set = true;
298 let _ = control.leave().await;
299 }
300 Err(_e) => {
301 error!("Failed to parse response body");
302 // return; // handle the error
303 }
304 }
305 }
306
307 //Set up saving
308 let mut flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(p.FLASH, p.DMA_CH3);
309 let mut save: Save = read_postcard_from_flash(ADDR_OFFSET, &mut flash, SAVE_OFFSET).unwrap();
310 WIFI_COUNT.store(save.wifi_counted, core::sync::atomic::Ordering::Relaxed);
311
312 //Setup i2c bus
313 let config = embassy_rp::i2c::Config::default();
314 let mut i2c = i2c::I2c::new_blocking(p.I2C0, p.PIN_5, p.PIN_4, config);
315 static I2C_BUS: StaticCell<I2c0Bus> = StaticCell::new();
316 // static I2C_BUS: StaticCell<I2c0Bus> = StaticCell::new();
317 let i2c_bus = NoopMutex::new(RefCell::new(i2c));
318 let i2c_bus = I2C_BUS.init(i2c_bus);
319
320 //Task spawning
321 spawner.must_spawn(run_the_temp_sensor(i2c_bus));
322 spawner.must_spawn(run_the_display(spi_bus, cs, dc, busy, reset));
323
324 //Input loop
325 let cycle = Duration::from_millis(100);
326 let mut current_cycle = 0;
327 let mut time_to_scan = true;
328 //5 minutes(ish) idk it's late and my math is so bad rn
329 let reset_cycle = 3_000;
330 //Turn off led to signify that the badge is ready
331 user_led.set_low();
332
333 loop {
334 //Change Image Button
335 if btn_c.is_high() {
336 info!("Button C pressed");
337 let current_image = CURRENT_IMAGE.load(core::sync::atomic::Ordering::Relaxed);
338 let new_image = DisplayImage::from_u8(current_image).unwrap().next();
339 CURRENT_IMAGE.store(new_image.as_u8(), core::sync::atomic::Ordering::Relaxed);
340 CHANGE_IMAGE.store(true, core::sync::atomic::Ordering::Relaxed);
341 Timer::after(Duration::from_millis(500)).await;
342 continue;
343 }
344
345 if btn_a.is_high() {
346 println!("{:?}", current_cycle);
347 info!("Button A pressed");
348 user_led.toggle();
349 Timer::after(Duration::from_millis(500)).await;
350 continue;
351 }
352
353 if btn_down.is_high() {
354 info!("Button Down pressed");
355 SCREEN_TO_SHOW.lock(|screen| {
356 screen.replace(Screen::WifiList);
357 });
358 DISPLAY_CHANGED.store(true, core::sync::atomic::Ordering::Relaxed);
359 Timer::after(Duration::from_millis(500)).await;
360 continue;
361 }
362
363 if btn_up.is_high() {
364 info!("Button Up pressed");
365 SCREEN_TO_SHOW.lock(|screen| {
366 screen.replace(Screen::Badge);
367 });
368 DISPLAY_CHANGED.store(true, core::sync::atomic::Ordering::Relaxed);
369 Timer::after(Duration::from_millis(500)).await;
370 continue;
371 }
372
373 if btn_b.is_high() {
374 info!("Button B pressed");
375
376 SCREEN_TO_SHOW.lock(|screen| {
377 if *screen.borrow() == Screen::Badge {
378 //IF on badge screen and b pressed reset wifi count
379 save.wifi_counted = 0;
380 save.bssid.clear();
381 WIFI_COUNT.store(0, core::sync::atomic::Ordering::Relaxed);
382 current_cycle = 0;
383 }
384 });
385
386 let mut recent_networks = RecentWifiNetworksVec::new();
387 let mut scanner = control.scan(Default::default()).await;
388
389 while let Some(bss) = scanner.next().await {
390 process_bssid(bss.bssid, &mut save.wifi_counted, &mut save.bssid);
391 if recent_networks.len() < 8 {
392 let possible_ssid = core::str::from_utf8(&bss.ssid);
393 match possible_ssid {
394 Ok(ssid) => {
395 let removed_zeros = ssid.trim_end_matches(char::from(0));
396 let ssid_string: String<32> =
397 easy_format::<32>(format_args!("{}", removed_zeros));
398
399 if recent_networks.contains(&ssid_string) {
400 continue;
401 }
402 if ssid_string != "" {
403 let _ = recent_networks.push(ssid_string);
404 }
405 }
406 Err(_) => {
407 continue;
408 }
409 }
410 }
411 }
412 RECENT_WIFI_NETWORKS.lock(|recent_networks_vec| {
413 recent_networks_vec.replace(recent_networks);
414 });
415
416 FORCE_SCREEN_REFRESH.store(true, core::sync::atomic::Ordering::Relaxed);
417 Timer::after(Duration::from_millis(500)).await;
418
419 continue;
420 }
421
422 if time_was_set {
423 let now = rtc.now();
424 match now {
425 Ok(time) => set_display_time(time),
426 Err(_) => {
427 info!("Error getting time");
428 }
429 }
430 } else {
431 RTC_TIME_STRING.lock(|rtc_time_string| {
432 rtc_time_string.borrow_mut().clear();
433 rtc_time_string.borrow_mut().push_str("No Wifi").unwrap();
434 });
435 }
436 if time_to_scan {
437 info!("Scanning for wifi networks");
438 time_to_scan = false;
439 let mut scanner = control.scan(Default::default()).await;
440 while let Some(bss) = scanner.next().await {
441 process_bssid(bss.bssid, &mut save.wifi_counted, &mut save.bssid);
442 }
443 WIFI_COUNT.store(save.wifi_counted, core::sync::atomic::Ordering::Relaxed);
444 save_postcard_to_flash(ADDR_OFFSET, &mut flash, SAVE_OFFSET, &save).unwrap();
445 info!("wifi_counted: {}", save.wifi_counted);
446 }
447 if current_cycle >= reset_cycle {
448 current_cycle = 0;
449 time_to_scan = true;
450 }
451 current_cycle += 1;
452 Timer::after(cycle).await;
453 }
454}
455
456fn set_display_time(time: DateTime) {
457 let mut am = true;
458 let twelve_hour = if time.hour > 12 {
459 am = false;
460 time.hour - 12
461 } else if time.hour == 0 {
462 12
463 } else {
464 time.hour
465 };
466
467 let am_pm = if am { "AM" } else { "PM" };
468
469 let formatted_time = easy_format::<8>(format_args!(
470 "{:02}:{:02} {}",
471 twelve_hour, time.minute, am_pm
472 ));
473
474 RTC_TIME_STRING.lock(|rtc_time_string| {
475 rtc_time_string.borrow_mut().clear();
476 rtc_time_string
477 .borrow_mut()
478 .push_str(formatted_time.as_str())
479 .unwrap();
480 });
481}
482
483#[embassy_executor::task]
484async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! {
485 runner.run().await
486}
487
488#[embassy_executor::task]
489async fn cyw43_task(
490 runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>,
491) -> ! {
492 runner.run().await
493}
494
495#[derive(Deserialize)]
496struct TimeApiResponse<'a> {
497 datetime: &'a str,
498 day_of_week: u8,
499}
500
501fn process_bssid(bssid: [u8; 6], wifi_counted: &mut u32, bssids: &mut Vec<String<17>, BSSID_LEN>) {
502 let bssid_str = format_bssid(bssid);
503 if !bssids.contains(&bssid_str) {
504 *wifi_counted += 1;
505 WIFI_COUNT.store(*wifi_counted, core::sync::atomic::Ordering::Relaxed);
506 // info!("bssid: {:x}", bssid_str);
507 let result = bssids.push(bssid_str);
508 if result.is_err() {
509 info!("bssid list full");
510 bssids.clear();
511 }
512 }
513}
514
515fn format_bssid(bssid: [u8; 6]) -> String<17> {
516 let mut s = String::new();
517 for (i, byte) in bssid.iter().enumerate() {
518 if i != 0 {
519 let _ = s.write_char(':');
520 }
521 core::fmt::write(&mut s, format_args!("{:02x}", byte)).unwrap();
522 }
523 s
524}