diff --git a/diagram.json b/diagram.json index c8b66ff..04d2356 100644 --- a/diagram.json +++ b/diagram.json @@ -5,7 +5,7 @@ "parts": [ { "type": "board-esp32-s3-devkitc-1", "id": "esp1", "top": 67.2, "left": -263.96, "attrs": {} }, { - "type": "board-ili9341-cap-touch", + "type": "wokwi-ili9341", "id": "lcd1", "top": -191.24, "left": -0.38, diff --git a/src/bin/main.rs b/src/bin/main.rs index 6156863..a877e68 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -7,10 +7,12 @@ )] #![deny(clippy::large_stack_frames)] +use alloc::{format, string::{String, ToString}}; use embassy_executor::Spawner; use embassy_time::Timer; use display_interface_spi::SPIInterface; use embedded_graphics::Drawable; +use embedded_graphics::Pixel; use embedded_graphics::mono_font::MonoTextStyle; use embedded_graphics::mono_font::ascii::FONT_6X10; use embedded_graphics::pixelcolor::Rgb565; @@ -47,6 +49,282 @@ extern crate alloc; esp_bootloader_esp_idf::esp_app_desc!(); +// The mock payload acting as our simulated NFC card read +const MOCK_PAYLOAD: [u8; 858] = [ + 0xE9, 0x24, 0x01, 0x47, 0xF2, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, 0x77, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x77, 0x77, 0x77, 0x77, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xF7, 0x77, 0x77, 0x77, + 0x77, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x77, + 0x77, 0xFF, 0xFF, 0x77, 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0xF7, 0x77, 0x7F, 0xFF, 0xFF, 0xF7, 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF7, 0xF7, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x77, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x7F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xF7, 0x77, 0x7F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x0F, 0xFF, 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x77, 0x7F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x77, 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x17, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, 0x7F, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, + 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF7, 0x77, 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, 0x7F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xF7, 0x7F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, 0x7F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, + 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, 0x7F, 0xFF, 0xFF, 0x00, + 0x0F, 0xFF, 0xF7, 0x77, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x77, 0x7F, + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF7, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0x77, 0x7F, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF7, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x7F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF7, 0x7F, 0xFF, 0xF0, 0x00, + 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x7F, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x07, 0x7F, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x7F, 0x7F, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0x7F, + 0xFF, 0xFF, 0xFF, 0x77, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x77, 0xF7, 0x7F, 0xFF, 0xF7, 0x77, 0x77, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x7F, 0x77, 0x77, 0x77, 0x77, 0x77, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0xFF, 0x77, 0x77, 0x77, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x77, 0x77, 0x77, 0x77, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0xF9, 0x7E, 0x00, 0x94, + 0xB2, 0xEE, 0x66, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3B, 0x9E, + 0x1B, 0x3B, 0x03, 0x50, 0xCA, 0xBA, 0x9B, 0x09, 0x00, 0x81, 0xFA, 0xB1, 0xEE, 0xCB, 0x7E, 0xC0, + 0xD4, 0x8D, 0x1F, 0xDA, 0x7D, 0x43, 0x8A, 0xB6, 0x1F, 0x0B, 0x7F, 0x2D, 0x30, 0xA9, 0xDF, 0xE7, + 0x01, 0x53, 0x70, 0x65, 0x65, 0x64, 0x79, 0x20, 0x74, 0x68, 0x69, 0x6E, 0x67, 0x20, 0x67, 0x6F, + 0x65, 0x73, 0x20, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x23, 0xC0, 0xD6, 0x5A, 0x9D, 0xA2, 0xAB, + 0xC4, 0x80, 0xC3, 0x06, 0xDB, 0xE8, 0x88, 0x7A, 0x7E, 0xB2, 0x40, 0x10, 0xA2, 0xDF, 0xC0, 0x89, + 0x03, 0x63, 0x8F, 0x4C, 0x2F, 0xBA, 0xCC, 0x17, 0x23, 0x51, 0x60, 0x27, 0x3B, 0x00, 0x1C, 0x6D, + 0x1F, 0x8C, 0x6B, 0x3E, 0x32, 0xE2, 0x53, 0xD9, 0x8F, 0x00 +]; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Rgb(pub u8, pub u8, pub u8); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct TypeStyle { + pub name: &'static str, + pub primary: Rgb, + pub text: Rgb, +} + +const PALETTES: [[Rgb; 16]; 8] = [ + // Palette Default + [ + Rgb(31, 23, 35), Rgb(60, 47, 82), Rgb(178, 39, 65), Rgb(70, 86, 165), + Rgb(245, 70, 76), Rgb(216, 104, 48), Rgb(140, 127, 144), Rgb(73, 149, 243), + Rgb(62, 197, 75), Rgb(240, 149, 72), Rgb(188, 176, 179), Rgb(247, 156, 136), + Rgb(114, 222, 235), Rgb(180, 230, 86), Rgb(239, 208, 129), Rgb(255, 255, 255), + ], + // Palette Blueprint + [ + Rgb(83, 67, 127), Rgb(44, 111, 153), Rgb(142, 88, 111), Rgb(0, 188, 170), + Rgb(153, 104, 226), Rgb(109, 129, 255), Rgb(255, 84, 112), Rgb(196, 143, 158), + Rgb(168, 159, 204), Rgb(190, 155, 255), Rgb(255, 155, 113), Rgb(127, 206, 255), + Rgb(255, 155, 182), Rgb(255, 217, 174), Rgb(255, 217, 232), Rgb(255, 255, 255), + ], + // Palette CYBER + [ + Rgb(0, 0, 0), Rgb(0, 0, 126), Rgb(0, 0, 255), Rgb(126, 0, 0), + Rgb(126, 0, 126), Rgb(4, 126, 0), Rgb(254, 0, 0), Rgb(4, 126, 126), + Rgb(254, 0, 255), Rgb(126, 126, 0), Rgb(126, 126, 126), Rgb(6, 255, 4), + Rgb(6, 255, 255), Rgb(190, 190, 190), Rgb(255, 255, 4), Rgb(255, 255, 255), + ], + // Palette Nature + [ + Rgb(35, 34, 40), Rgb(40, 66, 97), Rgb(30, 116, 83), Rgb(117, 77, 69), + Rgb(95, 88, 84), Rgb(36, 133, 166), Rgb(198, 80, 70), Rgb(85, 160, 88), + Rgb(135, 133, 115), Rgb(84, 186, 210), Rgb(161, 191, 65), Rgb(230, 146, 138), + Rgb(184, 176, 149), Rgb(227, 192, 84), Rgb(195, 213, 199), Rgb(255, 255, 255), + ], + // Palette Fossil + [ + Rgb(75, 61, 68), Rgb(119, 66, 81), Rgb(78, 84, 99), Rgb(100, 83, 85), + Rgb(156, 82, 78), Rgb(132, 109, 89), Rgb(91, 125, 115), Rgb(125, 123, 98), + Rgb(140, 124, 121), Rgb(185, 122, 96), Rgb(168, 138, 94), Rgb(142, 159, 125), + Rgb(170, 162, 93), Rgb(169, 156, 141), Rgb(204, 168, 123), Rgb(255, 255, 255), + ], + // Palette Ghost + [ + Rgb(0, 0, 0), Rgb(24, 24, 24), Rgb(40, 40, 40), Rgb(56, 56, 56), + Rgb(71, 71, 71), Rgb(86, 86, 86), Rgb(100, 100, 100), Rgb(113, 113, 113), + Rgb(126, 126, 126), Rgb(140, 140, 140), Rgb(155, 155, 155), Rgb(171, 171, 171), + Rgb(189, 189, 189), Rgb(209, 209, 209), Rgb(231, 231, 231), Rgb(255, 255, 255), + ], + // Palette Daemon + [ + Rgb(0, 3, 60), Rgb(96, 0, 136), Rgb(0, 56, 132), Rgb(42, 46, 121), + Rgb(0, 82, 96), Rgb(177, 5, 133), Rgb(255, 0, 78), Rgb(172, 41, 206), + Rgb(0, 157, 74), Rgb(0, 138, 197), Rgb(78, 110, 168), Rgb(255, 92, 255), + Rgb(10, 255, 82), Rgb(0, 247, 255), Rgb(173, 212, 250), Rgb(255, 255, 255), + ], + // Palette Fairy + [ + Rgb(40, 40, 46), Rgb(108, 86, 113), Rgb(135, 168, 137), Rgb(249, 130, 132), + Rgb(176, 169, 228), Rgb(222, 163, 139), Rgb(172, 204, 228), Rgb(254, 170, 228), + Rgb(217, 200, 191), Rgb(255, 195, 132), Rgb(176, 235, 147), Rgb(179, 227, 218), + Rgb(233, 245, 157), Rgb(255, 230, 198), Rgb(255, 247, 160), Rgb(255, 255, 255), + ], +]; + +const TYPE_STYLES: [TypeStyle; 8] = [ + TypeStyle { name: "Default", primary: Rgb(0xD8, 0x68, 0x30), text: Rgb(0xFF, 0xFF, 0xFF) }, + TypeStyle { name: "Blueprint", primary: Rgb(0x6D, 0x81, 0xFF), text: Rgb(0xFF, 0xFF, 0xFF) }, + TypeStyle { name: "CYBER", primary: Rgb(0xFF, 0xFF, 0x04), text: Rgb(0x00, 0x00, 0x00) }, + TypeStyle { name: "Nature", primary: Rgb(0x55, 0xA0, 0x58), text: Rgb(0xFF, 0xFF, 0xFF) }, + TypeStyle { name: "Fossil", primary: Rgb(0xB9, 0x7A, 0x60), text: Rgb(0xFF, 0xFF, 0xFF) }, + TypeStyle { name: "Ghost", primary: Rgb(0xAB, 0xAB, 0xAB), text: Rgb(0xFF, 0xFF, 0xFF) }, + TypeStyle { name: "Daemon", primary: Rgb(0x60, 0x00, 0x88), text: Rgb(0xFF, 0xFF, 0xFF) }, + TypeStyle { name: "Fairy", primary: Rgb(0xB0, 0xA9, 0xE4), text: Rgb(0xFF, 0xFF, 0xFF) }, +]; + +#[derive(Debug, Clone, Copy)] +struct NfcPayload<'a> { + pub event_encoding: &'a [u8], + pub card_type: u8, + pub card_uuid: &'a [u8], + pub _reserved: &'a [u8], + pub sprite: &'a [u8], + pub packed_card_text: &'a [u8], + pub secret: &'a [u8], + pub _opaque_trailer: &'a [u8], +} + +fn split_nfc_hex(payload: &[u8]) -> Option> { + if payload.len() < 0x35A { + return None; + } + + Some(NfcPayload { + event_encoding: &payload[0..2], + card_type: payload[2], + card_uuid: &payload[3..5], + _reserved: &payload[5..9], + sprite: &payload[9..0x2DB], + packed_card_text: &payload[0x2DB..0x311], + secret: &payload[0x311..0x329], + _opaque_trailer: &payload[0x329..0x35A], + }) +} + +fn render_sprite_onto_ili9341( + ili9341: &mut D, + sprite: &[u8], + palette: &[Rgb; 16], +) where + D: DrawTarget, +{ + let pixel_count = sprite.len() * 2; + if pixel_count == 0 { + return; + } + + let mut src_width = 1usize; + while src_width * src_width < pixel_count { + src_width += 1; + } + + let target_width = 240usize; + let scale = core::cmp::max(1, target_width / src_width); + + for y in 0..src_width { + for x in 0..src_width { + let pixel_index = y * src_width + x; + if pixel_index >= pixel_count { + break; + } + + let byte_index = pixel_index / 2; + let pixel_byte = sprite[byte_index]; + let color_index = if pixel_index % 2 == 0 { + (pixel_byte >> 4) & 0x0F + } else { + pixel_byte & 0x0F + }; + + let Rgb(r, g, b) = palette[color_index as usize]; + let color = Rgb565::new(r >> 3, g >> 2, b >> 3); + let start_x = x * scale; + let start_y = y * scale; + let end_x = core::cmp::min(start_x + scale, target_width); + let end_y = core::cmp::min(start_y + scale, target_width); + + for draw_y in start_y..end_y { + for draw_x in start_x..end_x { + let _ = ili9341.draw_iter(core::iter::once(Pixel( + Point::new(draw_x as i32, draw_y as i32), + color, + ))); + } + } + } + } +} + +fn decode_known_event(event_encoding: &[u8]) -> Option<&'static str> { + match event_encoding { + [0x39, 0xC3] => Some("39C3"), + [0x40, 0xC3] => Some("40C3"), // future proofing :) + [0xE9, 0x24] => Some("GPN 24"), + [0xE9, 0x25] => Some("GPN 25"), // future proofing :) + _ => None, + } +} + +fn decode_secret(hex: &[u8]) -> alloc::string::String { + let mut ascii_string = alloc::string::String::new(); + for &byte in hex { + if byte == 0 { + break; + } + + // Keep the output readable on the display by excluding control characters. + if byte.is_ascii_graphic() || byte == b' ' { + ascii_string.push(byte as char); + } + } + ascii_string +} + +fn decode_packed_card_text(bytes: &[u8]) -> (String, String, String, String) { + let alphabet = b" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; + let mut full_text = String::with_capacity((bytes.len() * 8) / 6 + 1); + + let mut accumulator: u32 = 0; + let mut bits_in_acc: u32 = 0; + + for &byte in bytes { + accumulator |= (byte as u32) << bits_in_acc; + bits_in_acc += 8; + + while bits_in_acc >= 6 { + let char_index = (accumulator & 0x3F) as usize; + + full_text.push(alphabet[char_index] as char); + + accumulator >>= 6; + bits_in_acc -= 6; + } + } + + let name = full_text[0..24].trim().to_string(); + let trait1 = full_text[24..40].trim().to_string(); + let trait2 = full_text[40..56].trim().to_string(); + let trait3 = full_text[56..72].trim().to_string(); + + (name, trait1, trait2, trait3) +} + + #[cfg(not(feature = "wokwi"))] async fn setup_wifi( wifi: esp_hal::peripherals::WIFI<'static>, @@ -191,17 +469,55 @@ async fn main(spawner: Spawner) { iface, reset_main, &mut delay, - Orientation::Landscape, + Orientation::Portrait, ili9341::DisplaySize240x320, ) .unwrap(); display.clear(Rgb565::BLACK).unwrap(); - let style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); - Text::new("Hello, ESP32!", Point::new(20, 100), style) - .draw(&mut display) - .unwrap(); + let nfc_payload: Option> = split_nfc_hex(&MOCK_PAYLOAD); + if let Some(nfc_payload) = nfc_payload { + let (name, trait1, trait2, trait3) = decode_packed_card_text(nfc_payload.packed_card_text); + let secret = decode_secret(nfc_payload.secret); + + let style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); + Text::new(&format!("Name: {}", name), Point::new(20, 240), style) + .draw(&mut display) + .unwrap(); + Text::new(&format!("Type: {}", TYPE_STYLES[(nfc_payload.card_type - 1) as usize].name), Point::new(20, 250), style) + .draw(&mut display) + .unwrap(); + Text::new(&format!("Event: {}", decode_known_event(nfc_payload.event_encoding).unwrap_or("Unknown")), Point::new(20, 260), style) + .draw(&mut display) + .unwrap(); + Text::new(&format!("UUID: {}", nfc_payload.card_uuid.iter().map(|b| format!("{:02X}", b)).collect::()), Point::new(20, 270), style) + .draw(&mut display) + .unwrap(); + Text::new(&format!("Trait1: {}", trait1), Point::new(20, 280), style) + .draw(&mut display) + .unwrap(); + Text::new(&format!("Trait2: {}", trait2), Point::new(20, 290), style) + .draw(&mut display) + .unwrap(); + Text::new(&format!("Trait3: {}", trait3), Point::new(20, 300), style) + .draw(&mut display) + .unwrap(); + Text::new(&format!("Secret: {}", secret), Point::new(20, 310), style) + .draw(&mut display) + .unwrap(); + + render_sprite_onto_ili9341( + &mut display, + nfc_payload.sprite, + &PALETTES[(nfc_payload.card_type - 1) as usize], + ); + } else { + log::error!( + "NFC payload too short to decode packed text: {} bytes", + MOCK_PAYLOAD.len() + ); + } setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await;