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