A personal rust firmware for the Badger 2040 W
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}