Improved sprite rendering

This commit is contained in:
2026-08-01 10:13:29 +02:00
parent 9b8dd759ca
commit 8ae2650e30
3 changed files with 337 additions and 109 deletions
Generated
+8 -1
View File
@@ -71,6 +71,12 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64-simd"
version = "0.7.0"
@@ -296,6 +302,7 @@ dependencies = [
name = "creaturedex"
version = "0.1.0"
dependencies = [
"base64 0.22.1",
"critical-section",
"display-interface-spi",
"embassy-executor",
@@ -3137,7 +3144,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50"
dependencies = [
"base64",
"base64 0.13.1",
"proc-macro2",
"quote",
"syn 1.0.109",
+1
View File
@@ -38,6 +38,7 @@ embedded-hal = "1.0.0"
embedded-hal-bus = "0.3.0"
embedded-graphics = "0.8.2"
embedded-graphics-core = "0.4.1"
base64 = { version = "0.22", default-features = false, features = ["alloc"] }
# For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html
+309 -89
View File
@@ -7,18 +7,25 @@
)]
#![deny(clippy::large_stack_frames)]
use alloc::{format, string::{String, ToString}};
use alloc::{
format,
string::{String, ToString},
};
use base64::{Engine as _, engine::general_purpose};
use display_interface_spi::SPIInterface;
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;
use embedded_graphics::prelude::Point;
use embedded_graphics::prelude::RgbColor;
use embedded_graphics::text::Text;
use embedded_graphics::{
Drawable,
prelude::*,
primitives::{PrimitiveStyle, Rectangle},
};
use embedded_graphics_core::draw_target::DrawTarget;
use embedded_hal_bus::spi::ExclusiveDevice;
use esp_hal::clock::CpuClock;
@@ -48,8 +55,64 @@ extern crate alloc;
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();
// The mock payload acting as our simulated NFC card read
const MOCK_PAYLOAD2: [u8; 858] = [
0xE9, 0x24, 0x03, 0xDA, 0x45, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0xAA, 0xA0, 0xF0, 0xAA, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0xAA, 0xA0, 0xF0, 0xAA, 0x0F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xAA, 0xAA, 0x00, 0xAA,
0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0xAA,
0xAA, 0x0A, 0xAA, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0xAA, 0xAA, 0xAA, 0x0A, 0xAA, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xF0, 0xAA, 0xAA, 0xA0, 0xAA, 0xA0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xAA, 0xAA, 0xA0, 0xAA, 0x0A, 0x0F, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xAA, 0xAA, 0xA0, 0x0A, 0x0A, 0x0F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xAA, 0xAA, 0xAA,
0x00, 0xAA, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x0A, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xAA, 0xFA, 0xAA, 0xA6, 0xAA, 0xAA, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xAF, 0xFF, 0xFA, 0xAA, 0xAA, 0xAA, 0x0F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xAA, 0xFA, 0xAA, 0xAA, 0xAA,
0xFA, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0xAA, 0xAA,
0xAA, 0xAA, 0xFF, 0xA0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xDD, 0xDD, 0xDD, 0x0F,
0x0A, 0xAA, 0xAA, 0xA6, 0xAA, 0xAA, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0D, 0xDF,
0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xA0, 0xAA, 0xA0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0D, 0x00, 0x0F, 0xF0, 0xAA, 0xAA, 0xAA, 0x0F, 0x0A, 0xA0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x0D, 0x0A, 0xAF, 0xF0, 0xAA, 0xAA, 0x0A, 0x0F, 0xFF, 0x0F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0D, 0x00, 0x0F, 0xF0, 0xAA, 0xAA, 0xF0, 0xFF, 0xF0,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0D, 0x0E, 0x0F, 0xF0, 0xAA, 0xAA,
0xDD, 0xDD, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0D, 0x00, 0x0F,
0xF0, 0xAF, 0xAF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x0D, 0x0A, 0xAF, 0xF0, 0xAA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x0D, 0x00, 0x0F, 0xF0, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0D, 0xDF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xDD, 0xDD, 0xDF, 0x0F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 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, 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, 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, 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,
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, 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, 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, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xB3, 0x11, 0xCF, 0x04,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x3B, 0x9A,
0x26, 0x10, 0x98, 0x23, 0xFC, 0x01, 0x00, 0x00, 0x00, 0xCE, 0xF7, 0xB2, 0xA9, 0x9B, 0xCA, 0x23,
0x0A, 0x00, 0x00, 0x00, 0x00, 0xD7, 0x86, 0xBA, 0xC0, 0x9C, 0xBE, 0x40, 0x98, 0xA2, 0x1F, 0x00,
0x00, 0x54, 0x68, 0x65, 0x20, 0x63, 0x61, 0x6B, 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x6C,
0x69, 0x65, 0x20, 0xF0, 0x9F, 0x8E, 0x82, 0x00, 0x00, 0x87, 0xF7, 0xB1, 0x44, 0xB0, 0x55, 0x5E,
0xF9, 0x22, 0xD6, 0x71, 0x45, 0xE9, 0xD5, 0xA7, 0xF8, 0x0F, 0xBE, 0x8F, 0xF6, 0xB9, 0x77, 0x62,
0x13, 0xF3, 0x84, 0x55, 0xD2, 0xBC, 0x4E, 0xCB, 0xDF, 0xDB, 0xC0, 0x91, 0x5F, 0x39, 0xE0, 0xF5,
0x95, 0x2D, 0x14, 0xBD, 0x43, 0xB2, 0x40, 0x76, 0x78, 0x00,
];
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,
@@ -104,7 +167,7 @@ const MOCK_PAYLOAD: [u8; 858] = [
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
0x1F, 0x8C, 0x6B, 0x3E, 0x32, 0xE2, 0x53, 0xD9, 0x8F, 0x00,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -120,71 +183,199 @@ struct TypeStyle {
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),
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),
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),
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),
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),
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),
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),
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),
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) },
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)]
@@ -216,11 +407,8 @@ fn split_nfc_hex(payload: &[u8]) -> Option<NfcPayload<'_>> {
})
}
fn render_sprite_onto_ili9341<D>(
ili9341: &mut D,
sprite: &[u8],
palette: &[Rgb; 16],
) where
fn render_sprite_onto_ili9341<D>(ili9341: &mut D, sprite: &[u8], palette: &[Rgb; 16])
where
D: DrawTarget<Color = Rgb565>,
{
let pixel_count = sprite.len() * 2;
@@ -228,6 +416,12 @@ fn render_sprite_onto_ili9341<D>(
return;
}
// OPTIMIZATION 1: Pre-compute the 16-color palette to Rgb565 once.
let mut palette565 = [Rgb565::new(0, 0, 0); 16];
for (i, Rgb(r, g, b)) in palette.iter().enumerate() {
palette565[i] = Rgb565::new(*r >> 3, *g >> 2, *b >> 3);
}
let mut src_width = 1usize;
while src_width * src_width < pixel_count {
src_width += 1;
@@ -243,29 +437,36 @@ fn render_sprite_onto_ili9341<D>(
break;
}
let byte_index = pixel_index / 2;
// Bitwise operations are slightly faster than division/modulo
let byte_index = pixel_index >> 1;
let pixel_byte = sprite[byte_index];
let color_index = if pixel_index % 2 == 0 {
let color_index = if pixel_index & 1 == 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);
let color = palette565[color_index as usize];
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,
)));
}
let start_x = (x * scale) as i32;
let start_y = (y * scale) as i32;
let end_x = core::cmp::min(start_x + scale as i32, target_width as i32);
let end_y = core::cmp::min(start_y + scale as i32, target_width as i32);
let width = (end_x - start_x) as u32;
let height = (end_y - start_y) as u32;
if width == 0 || height == 0 {
continue;
}
// OPTIMIZATION 2: Draw a rectangle block.
// The display driver sets the SPI window ONCE per block.
let rect = Rectangle::new(Point::new(start_x, start_y), Size::new(width, height));
let style = PrimitiveStyle::with_fill(color);
let _ = rect.into_styled(style).draw(ili9341);
}
}
}
@@ -287,7 +488,6 @@ fn decode_secret(hex: &[u8]) -> alloc::string::String {
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);
}
@@ -324,7 +524,6 @@ fn decode_packed_card_text(bytes: &[u8]) -> (String, String, String, String) {
(name, trait1, trait2, trait3)
}
#[cfg(not(feature = "wokwi"))]
async fn setup_wifi(
wifi: esp_hal::peripherals::WIFI<'static>,
@@ -344,14 +543,7 @@ async fn setup_wifi(
wm_settings.wifi_conn_timeout = 30000;
wm_settings.esp_reset_timeout = Some(300000); // 5min
let wifi_res = esp_hal_wifimanager::init_wm(
wm_settings,
spawner,
Some(&nvs),
wifi,
None,
)
.await;
let wifi_res = esp_hal_wifimanager::init_wm(wm_settings, spawner, Some(&nvs), wifi, None).await;
log::info!("wifi_res: {wifi_res:?}");
}
@@ -393,14 +585,7 @@ async fn setup_wifi(
wm_settings.wifi_conn_timeout = 30000;
// Initialize Wi-Fi connection through WiFi Manager using the pre-seeded NVS data.
let wifi_res = esp_hal_wifimanager::init_wm(
wm_settings,
spawner,
Some(&nvs),
wifi,
None,
)
.await;
let wifi_res = esp_hal_wifimanager::init_wm(wm_settings, spawner, Some(&nvs), wifi, None).await;
log::info!("Wokwi wifi_res: {wifi_res:?}");
}
@@ -445,11 +630,10 @@ async fn main(spawner: Spawner) {
esp_hal::interrupt::software::SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
esp_rtos::start(timg0.timer0, sw_interrupt.software_interrupt0);
let spi_bus = Spi::new(
peripherals.SPI2,
Config::default()
.with_frequency(Rate::from_khz(100))
.with_frequency(Rate::from_mhz(40))
.with_mode(Mode::_0),
)
.unwrap()
@@ -477,6 +661,17 @@ async fn main(spawner: Spawner) {
display.clear(Rgb565::BLACK).unwrap();
let nfc_payload: Option<NfcPayload<'_>> = split_nfc_hex(&MOCK_PAYLOAD);
let simulated_time: u64 = 1780757711176; // Simulated time in milliseconds since epoch (2023-06-05 12:15:11 UTC)
let mut final_payload = [0u8; 867];
final_payload[..858].copy_from_slice(&MOCK_PAYLOAD);
final_payload[858..866].copy_from_slice(&simulated_time.to_be_bytes());
final_payload[866] = 0x00;
let b64_string = general_purpose::STANDARD.encode(&final_payload);
log::info!("Base64 String:");
log::info!("{}", b64_string);
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);
@@ -485,13 +680,38 @@ async fn main(spawner: Spawner) {
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)
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)
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::<String>()), Point::new(20, 270), style)
Text::new(
&format!(
"UUID: {}",
nfc_payload
.card_uuid
.iter()
.map(|b| format!("{:02X}", b))
.collect::<String>()
),
Point::new(20, 270),
style,
)
.draw(&mut display)
.unwrap();
Text::new(&format!("Trait1: {}", trait1), Point::new(20, 280), style)