├── rust-toolchain.toml ├── sol_logo.raw ├── .gitignore ├── sol_logo_32_32.png ├── cfg.toml.example ├── .cargo └── config.toml ├── sdkconfig.defaults ├── Cargo.toml ├── README.md └── src ├── wifi.rs ├── main.rs ├── http.rs └── display.rs /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | -------------------------------------------------------------------------------- /sol_logo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mantistc/esp32-ssd1306-solana/HEAD/sol_logo.raw -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /.embuild 3 | /target 4 | /Cargo.lock 5 | .env 6 | cfg.toml 7 | .DS_Store -------------------------------------------------------------------------------- /sol_logo_32_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mantistc/esp32-ssd1306-solana/HEAD/sol_logo_32_32.png -------------------------------------------------------------------------------- /cfg.toml.example: -------------------------------------------------------------------------------- 1 | [esp32-ssd1306-solana] 2 | wifi_ssid = "YOUR WIFI NAME: HOME123" 3 | wifi_psk = "YOUR WIFI PASSWORD: SOLANATHEBEST123" 4 | sol_rpc = "YOUR SOL RPC" 5 | wallet_address = "aKgfWjGePnbFgDAuCqxB5oymuFxQskvCtrw6eYfDa7fg" 6 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "xtensa-esp32-espidf" 3 | 4 | [target.xtensa-esp32-espidf] 5 | linker = "ldproxy" 6 | runner = "espflash flash --monitor" 7 | rustflags = [ "--cfg", "espidf_time64"] 8 | 9 | [unstable] 10 | build-std = ["std", "panic_abort"] 11 | 12 | [env] 13 | MCU="esp32" 14 | # Note: this variable is not used by the pio builder (`cargo build --features pio`) 15 | ESP_IDF_VERSION = "v5.3.2" 16 | 17 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) 2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 3 | 4 | # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). 5 | # This allows to use 1 ms granularity for thread sleeps (10 ms by default). 6 | #CONFIG_FREERTOS_HZ=1000 7 | 8 | # Workaround for https://github.com/espressif/esp-idf/issues/7631 9 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n 10 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esp32-ssd1306-solana" 3 | version = "0.1.0" 4 | authors = ["Mantistc"] 5 | edition = "2021" 6 | 7 | [[bin]] 8 | name = "esp32-ssd1306-solana" 9 | harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors 10 | 11 | [profile.release] 12 | opt-level = "s" 13 | 14 | [profile.dev] 15 | debug = true # Symbols are nice and they don't increase the size on Flash 16 | opt-level = "z" 17 | 18 | [features] 19 | default = [] 20 | 21 | experimental = ["esp-idf-svc/experimental"] 22 | 23 | [dependencies] 24 | log = "0.4" 25 | esp-idf-svc = { version = "0.50", features = ["critical-section", "embassy-time-driver", "embassy-sync"] } 26 | esp-idf-hal = "0.45.0" 27 | ssd1306 = "0.9.0" 28 | embedded-graphics = "0.8.1" 29 | serde = "1.0.217" 30 | serde_json = "1.0.134" 31 | embedded-svc = "0.28.0" 32 | toml-cfg = "0.2.0" 33 | qrcodegen = "1.8.0" 34 | 35 | [build-dependencies] 36 | embuild = "0.33" 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Esp32-ssd1306-solana 2 | 3 | Show Solana real-time data on a **SSD1306** mini display using an **ESP32** microcontroller. 4 | The ESP32 handles **Wi-Fi**, **HTTP/HTTPS requests**, and **GPIO connections** to interact with the display. 5 | 6 | --- 7 | 8 | ## **Features** 9 | - Wi-Fi & Bluetooth support 10 | - HTTP/HTTPS requests handling 11 | - Multiple GPIO pin connections 12 | 13 | --- 14 | 15 | ## **Getting Started** 16 | 17 | ### **Requirements** 18 | 1. **Rust** (latest stable version) 19 | 2. **ESP32** microcontroller 20 | 3. **SSD1306** mini display 21 | 4. **Jumper wires** 22 | 5. **USB cable** to connect ESP32 to your computer 23 | 24 | --- 25 | 26 | ### **Installation** 27 | 28 | #### **1) Install `espup`** 29 | ```bash 30 | cargo install espup 31 | ``` 32 | 33 | #### **2) Install Necessary Toolchains** 34 | ```bash 35 | espup install 36 | ``` 37 | 38 | #### **3) Install Espressif toolchain** 39 | ```bash 40 | cargo install cargo-espflash espflash ldproxy 41 | ``` 42 | 43 | #### **4) Clone this repository** 44 | ```bash 45 | git clone https://github.com/Mantistc/esp32-ssd1306-solana 46 | cd esp32-ssd1306-solana 47 | ``` 48 | 49 | #### **5) Create your configuration file** 50 | Create a `cfg.toml` file based on the example file: 51 | ```bash 52 | cp cfg.toml.example cfg.toml 53 | ``` 54 | Then, edit it and add your custom settings. 55 | 56 | #### **6) Connect your hardware** 57 | - Connect your **ESP32** to your computer via USB. 58 | - Wire the **SSD1306** display to the correct ESP32 pins. 59 | 60 | #### **7) Build the application** 61 | ```bash 62 | cargo build --release 63 | ``` 64 | 65 | #### **8) Flash the firmware to the ESP32** 66 | ```bash 67 | cargo espflash flash --monitor 68 | ``` 69 | or 70 | 71 | ```bash 72 | cargo run --release 73 | ``` 74 | --- 75 | 76 | ## **Enjoy!** 77 | Now your cool mini display will show you **real-time Solana data**! 78 | 79 | --- 80 | 81 |

82 | Made with ❤️ by @lich.sol 83 |

84 | -------------------------------------------------------------------------------- /src/wifi.rs: -------------------------------------------------------------------------------- 1 | use embedded_svc::wifi::Configuration; 2 | use esp_idf_hal::modem::Modem; 3 | use esp_idf_svc::{ 4 | eventloop::EspSystemEventLoop, 5 | wifi::{BlockingWifi, ClientConfiguration, EspWifi}, 6 | }; 7 | use log::info; 8 | 9 | pub fn wifi(modem: Modem, ssid: &str, password: &str) -> BlockingWifi> { 10 | let sysloop = EspSystemEventLoop::take().expect("failed sysloop ownership take"); 11 | let esp_wifi = EspWifi::new(modem, sysloop.clone(), None).unwrap(); 12 | let mut wifi = BlockingWifi::wrap(esp_wifi, sysloop).unwrap(); 13 | 14 | wifi.set_configuration(&Configuration::Client(ClientConfiguration::default())) 15 | .unwrap(); 16 | 17 | info!("Starting wifi..."); 18 | 19 | wifi.start().unwrap(); 20 | 21 | // let networks = wifi.scan().unwrap(); 22 | // for network in networks { 23 | // info!("SSID: {}, RSSI: {}", network.ssid, network.signal_strength); 24 | // } 25 | 26 | let ap_infos = wifi.scan().unwrap(); 27 | 28 | let ours = ap_infos.into_iter().find(|a| a.ssid == ssid); 29 | 30 | let channel = if let Some(ours) = ours { 31 | info!( 32 | "Found configured access point {} on channel {}", 33 | ssid, ours.channel 34 | ); 35 | Some(ours.channel) 36 | } else { 37 | info!( 38 | "Configured access point {} not found during scanning, will go with unknown channel", 39 | ssid 40 | ); 41 | None 42 | }; 43 | 44 | wifi.set_configuration(&Configuration::Client(ClientConfiguration { 45 | ssid: ssid 46 | .try_into() 47 | .expect("Could not parse the given SSID into WiFi config"), 48 | password: password 49 | .try_into() 50 | .expect("Could not parse the given password into WiFi config"), 51 | channel, 52 | ..Default::default() 53 | })) 54 | .unwrap(); 55 | 56 | info!("Connecting wifi..."); 57 | 58 | match wifi.connect() { 59 | Ok(value) => { 60 | info!("success"); 61 | value 62 | } 63 | Err(err) => { 64 | info!("Error: {}", err); 65 | panic!("Error on connect: {}", err); 66 | } 67 | }; 68 | 69 | info!("Waiting for DHCP lease..."); 70 | 71 | wifi.wait_netif_up().unwrap(); 72 | 73 | let ip_info = wifi.wifi().sta_netif().get_ip_info().unwrap(); 74 | 75 | info!("Wifi DHCP info: {:?}", ip_info); 76 | wifi 77 | } 78 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::{ 3 | atomic::{AtomicBool, Ordering}, 4 | Arc, 5 | }, 6 | time::Duration, 7 | }; 8 | 9 | use display::DisplayModule; 10 | use embedded_graphics::mono_font::ascii::FONT_6X10; 11 | use esp_idf_hal::{ 12 | gpio::{PinDriver, Pull}, 13 | prelude::Peripherals, 14 | sys::{esp_err_to_name, nvs_flash_init, ESP_OK}, 15 | }; 16 | use esp_idf_svc::sntp::EspSntp; 17 | use http::Http; 18 | use wifi::wifi; 19 | 20 | mod display; 21 | mod http; 22 | mod wifi; 23 | 24 | #[toml_cfg::toml_config] 25 | pub struct Config { 26 | #[default("")] 27 | wifi_ssid: &'static str, 28 | #[default("")] 29 | wifi_psk: &'static str, 30 | #[default("")] 31 | sol_rpc: &'static str, 32 | #[default("")] 33 | wallet_address: &'static str, 34 | } 35 | 36 | fn main() { 37 | // It is necessary to call this function once. Otherwise some patches to the runtime 38 | // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 39 | esp_idf_svc::sys::link_patches(); 40 | 41 | // Bind the log crate to the ESP Logging facilities 42 | esp_idf_svc::log::EspLogger::initialize_default(); 43 | 44 | let app_config = CONFIG; 45 | 46 | let init_result = unsafe { nvs_flash_init() }; 47 | if init_result != ESP_OK { 48 | unsafe { 49 | log::error!("Error initializing nvs: {:?}", esp_err_to_name(init_result)); 50 | } 51 | } 52 | 53 | let peripherals = Peripherals::take().unwrap(); 54 | 55 | let i2c = peripherals.i2c0; 56 | let sda = peripherals.pins.gpio21; 57 | let scl = peripherals.pins.gpio22; 58 | 59 | let mut led_1 = PinDriver::output(peripherals.pins.gpio19).unwrap(); 60 | let mut led_2 = PinDriver::output(peripherals.pins.gpio14).unwrap(); 61 | let mut led_3 = PinDriver::output(peripherals.pins.gpio15).unwrap(); 62 | let mut button = PinDriver::input(peripherals.pins.gpio18).unwrap(); 63 | button.set_pull(Pull::Up).unwrap(); 64 | 65 | let is_on = Arc::new(AtomicBool::new(true)); 66 | let is_on_clone = Arc::clone(&is_on); 67 | let mut display_module = DisplayModule::init(i2c, sda, scl, &app_config.wallet_address); 68 | 69 | std::thread::spawn(move || loop { 70 | if button.is_low() { 71 | is_on_clone.store(!is_on_clone.load(Ordering::SeqCst), Ordering::SeqCst); 72 | println!( 73 | "Button toggled. is_on: {}", 74 | is_on_clone.load(Ordering::SeqCst) 75 | ); 76 | } else { 77 | std::thread::sleep(Duration::from_millis(500)); // pulse btn time 78 | continue; 79 | } 80 | std::thread::sleep(Duration::from_millis(10000)); // min time to change the state (On,Off) again 81 | }); 82 | 83 | // initialize display 84 | 85 | let solana_cool_app_text = "Connecting wifi..."; 86 | 87 | led_1.set_high().unwrap(); 88 | 89 | display_module.create_centered_text(&solana_cool_app_text, FONT_6X10); 90 | 91 | // initialize wifi 92 | let _wifi = wifi( 93 | peripherals.modem, 94 | &app_config.wifi_ssid, 95 | app_config.wifi_psk, 96 | ); 97 | 98 | let mut http = Http::init(&app_config.sol_rpc).expect("Http module initialization failed"); 99 | display_module.create_black_rectangle(); 100 | 101 | let device_ready = "Device Ready"; 102 | 103 | led_1.set_high().unwrap(); 104 | 105 | display_module.create_centered_text(&device_ready, FONT_6X10); 106 | 107 | let _sntp = EspSntp::new_default().unwrap(); 108 | 109 | std::thread::sleep(Duration::from_millis(3000)); 110 | 111 | led_1.set_low().unwrap(); 112 | let mut previous_state = true; 113 | loop { 114 | let show_data = is_on.load(Ordering::SeqCst); 115 | if show_data { 116 | led_2.set_high().unwrap(); 117 | display_module.create_black_rectangle(); 118 | if !previous_state { 119 | previous_state = true; 120 | } 121 | led_3.set_low().unwrap(); 122 | display_module.perpetual_data(&mut http); 123 | } else if !show_data && previous_state { 124 | display_module.create_black_rectangle(); 125 | println!("Device Off"); 126 | display_module.draw_image(); 127 | led_2.set_low().unwrap(); 128 | led_3.set_high().unwrap(); 129 | previous_state = false; 130 | std::thread::sleep(Duration::from_millis(3000)); 131 | } 132 | std::thread::sleep(Duration::from_millis(500)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/http.rs: -------------------------------------------------------------------------------- 1 | use core::str; 2 | use embedded_svc::http::client::Client; 3 | use esp_idf_svc::http::{ 4 | client::{Configuration, EspHttpConnection}, 5 | Method, 6 | }; 7 | use serde::Serialize; 8 | use serde_json::json; 9 | use std::error::Error; 10 | 11 | pub const LAMPORTS_PER_SOL: u32 = 1_000_000_000; 12 | 13 | pub struct Http { 14 | sol_endpoint: String, 15 | http_client: Client, 16 | } 17 | 18 | impl Http { 19 | pub fn init(endpoint: &str) -> Result> { 20 | let connection = EspHttpConnection::new(&Configuration { 21 | timeout: Some(std::time::Duration::from_secs(30)), 22 | use_global_ca_store: true, 23 | crt_bundle_attach: Some(esp_idf_svc::sys::esp_crt_bundle_attach), 24 | ..Default::default() 25 | })?; 26 | let client = Client::wrap(connection); 27 | Ok(Self { 28 | sol_endpoint: endpoint.to_string(), 29 | http_client: client, 30 | }) 31 | } 32 | 33 | pub fn http_request( 34 | &mut self, 35 | method: Method, 36 | uri: &str, 37 | headers: &[(&str, &str)], 38 | payload: Option<&str>, 39 | ) -> Result> { 40 | let client = &mut self.http_client; 41 | let mut request = client.request(method, uri, &headers)?; 42 | if let Some(payload_str) = payload { 43 | request.write(payload_str.as_bytes())?; 44 | }; 45 | let response = request.submit()?; 46 | let status = response.status(); 47 | 48 | println!("Response code: {}\n", status); 49 | if !(200..=299).contains(&status) { 50 | return Err(format!("HTTP Error: Status code {}", status).into()); 51 | } 52 | 53 | // read the response body in chunks 54 | let mut buf = [0_u8; 256]; // buffer for storing chunks 55 | let mut response_body = String::new(); // string to hold the full response 56 | let mut reader = response; 57 | loop { 58 | let size = reader.read(&mut buf)?; // read data into the buffer 59 | if size == 0 { 60 | break; // exit loop when no more data is available 61 | } 62 | response_body.push_str(str::from_utf8(&buf[..size])?); // append the chunk to the response body 63 | } 64 | println!("Raw response body: {}", response_body); 65 | // deserialize the response JSON 66 | let json_response: serde_json::Value = serde_json::from_str(&response_body)?; 67 | 68 | // result 69 | Ok(json_response.clone()) 70 | } 71 | 72 | pub fn http_sol_request( 73 | &mut self, 74 | method: &str, 75 | params: Params, 76 | ) -> Result> 77 | where 78 | Params: Serialize, 79 | { 80 | let payload = json!({ 81 | "jsonrpc": "2.0", 82 | "id": 1, 83 | "method": method, 84 | "params": [params] 85 | }); 86 | 87 | let payload_str = serde_json::to_string(&payload)?; 88 | 89 | let headers = [ 90 | ("Content-Type", "application/json"), 91 | ("Content-Length", &payload_str.len().to_string()), 92 | ]; 93 | let endpoint = self.sol_endpoint.clone(); 94 | let result = self.http_request(Method::Post, &endpoint, &headers, Some(&payload_str))?; 95 | Ok(result["result"].clone()) 96 | } 97 | 98 | pub fn get_balance(&mut self, wallet: &str) -> Result> { 99 | let method = "getBalance"; 100 | match self.http_sol_request(method, wallet) { 101 | Ok(response) => { 102 | let balance = response["value"].as_u64().unwrap_or(0); 103 | Ok(balance) 104 | } 105 | Err(e) => { 106 | println!("Error occurred: {}", e); 107 | Ok(0) 108 | } 109 | } 110 | } 111 | 112 | pub fn get_tps(&mut self) -> Result<(u64, u64), Box> { 113 | let method = "getRecentPerformanceSamples"; 114 | 115 | match self.http_sol_request(method, 1) { 116 | Ok(rps) => { 117 | let rps_result = rps 118 | .as_array() 119 | .and_then(|array| array.get(0)) 120 | .ok_or("no performance samples found in the response")?; 121 | 122 | let num_tx = rps_result["numTransactions"].as_u64().unwrap_or(0); 123 | let slot = rps_result["slot"].as_u64().unwrap_or(0); 124 | let total_tx = num_tx / 60; 125 | Ok((slot, total_tx)) 126 | } 127 | Err(e) => { 128 | println!("Error occurred: {}", e); 129 | Ok((0, 0)) 130 | } 131 | } 132 | } 133 | 134 | pub fn get_solana_price(&mut self) -> Result> { 135 | let headers = [("accept", "application/json")]; 136 | let url = "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd"; 137 | match self.http_request(Method::Get, &url, &headers, None) { 138 | Ok(response) => { 139 | let sol_price = response["solana"]["usd"].as_f64().unwrap_or(0.0); 140 | Ok(sol_price) 141 | } 142 | Err(e) => { 143 | println!("Error occurred: {}", e); 144 | Ok(0.0) 145 | } 146 | } 147 | } 148 | 149 | pub fn utc_offset_time(&mut self) -> Result<(String, String), Box> { 150 | let headers = [("accept", "application/json")]; 151 | let url = "https://timeapi.io/api/time/current/zone?timeZone=America/Bogota"; 152 | match self.http_request(Method::Get, &url, &headers, None) { 153 | Ok(response) => { 154 | let year = response["year"].as_i64().unwrap_or(0); 155 | let month = response["month"].as_i64().unwrap_or(0); 156 | let day = response["day"].as_i64().unwrap_or(0); 157 | let hour = response["hour"].as_i64().unwrap_or(0); 158 | let minute = response["minute"].as_i64().unwrap_or(0); 159 | let seconds = response["seconds"].as_i64().unwrap_or(0); 160 | 161 | let date_string = format!("{}-{:02}-{:02}", year, month, day); 162 | let time_string = format!("{:02}:{:02}:{:02}", hour, minute, seconds); 163 | Ok((time_string, date_string)) 164 | } 165 | Err(e) => { 166 | println!("Error occurred: {}", e); 167 | Ok((String::new(), String::new())) 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/display.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics::{ 2 | image::{Image, ImageRaw}, 3 | mono_font::{ 4 | ascii::{FONT_4X6, FONT_6X10}, 5 | MonoFont, MonoTextStyleBuilder, 6 | }, 7 | pixelcolor::BinaryColor, 8 | prelude::{Point, Primitive, Size}, 9 | primitives::{PrimitiveStyle, PrimitiveStyleBuilder, Rectangle}, 10 | text::{Baseline, Text}, 11 | Drawable, 12 | }; 13 | use esp_idf_hal::{ 14 | gpio::{Gpio21, Gpio22}, 15 | i2c::{I2cConfig, I2cDriver, I2C0}, 16 | units::Hertz, 17 | }; 18 | use log::info; 19 | use qrcodegen::{QrCode, QrCodeEcc}; 20 | use ssd1306::{ 21 | mode::{BufferedGraphicsMode, DisplayConfig}, 22 | prelude::{DisplayRotation, I2CInterface}, 23 | size::DisplaySize128x64, 24 | I2CDisplayInterface, Ssd1306, 25 | }; 26 | use std::time::Duration; 27 | 28 | use crate::http::{Http, LAMPORTS_PER_SOL}; 29 | 30 | pub struct DisplayModule { 31 | pub display: Ssd1306< 32 | I2CInterface>, 33 | DisplaySize128x64, 34 | BufferedGraphicsMode, 35 | >, 36 | pub wallet_address: String, 37 | } 38 | 39 | impl DisplayModule { 40 | pub fn init(i2c: I2C0, sda: Gpio21, scl: Gpio22, wallet_address: &str) -> Self { 41 | let mut i2c = 42 | I2cDriver::new(i2c, sda, scl, &I2cConfig::new().baudrate(Hertz(400))).unwrap(); 43 | 44 | for address in 0x00..=0x7F { 45 | if i2c.write(address, &[], 5000).is_ok() { 46 | info!("Found device at address: 0x{:02X}", address); 47 | } 48 | } 49 | let interface = I2CDisplayInterface::new(i2c); 50 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 51 | .into_buffered_graphics_mode(); 52 | 53 | let on = PrimitiveStyleBuilder::new() 54 | .stroke_width(1) 55 | .stroke_color(BinaryColor::On) 56 | .build(); 57 | 58 | match display.init() { 59 | Ok(value) => { 60 | info!("init success"); 61 | value 62 | } 63 | Err(err) => { 64 | info!("Error: {:?}", err); 65 | panic!("Error on init: {:?}", err); 66 | } 67 | }; 68 | 69 | Rectangle::new(Point::new(0, 0), Size::new(127, 63)) 70 | .into_styled(on) 71 | .draw(&mut display) 72 | .unwrap(); 73 | Self { 74 | display, 75 | wallet_address: wallet_address.to_string(), 76 | } 77 | } 78 | 79 | pub fn create_centered_text(&mut self, text: &str, font: MonoFont) { 80 | let text_width = text.len() as u8 * 6; 81 | let text_height = 10u8; 82 | 83 | let x = (128u8 - text_width) / 2; 84 | let y = (64u8 - text_height) / 2; 85 | 86 | self.create_text(text, x, y, font); 87 | } 88 | 89 | pub fn create_text(&mut self, text: &str, x_c: u8, y_c: u8, font: MonoFont) { 90 | let text_style = MonoTextStyleBuilder::new() 91 | .font(&font) 92 | .text_color(BinaryColor::On) 93 | .build(); 94 | 95 | let display = &mut self.display; 96 | Text::with_baseline( 97 | text, 98 | Point::new(x_c.into(), y_c.into()), 99 | text_style, 100 | Baseline::Top, 101 | ) 102 | .draw(display) 103 | .unwrap(); 104 | display.flush().unwrap(); 105 | } 106 | 107 | pub fn create_black_rectangle(&mut self) { 108 | let display = &mut self.display; 109 | let on = PrimitiveStyleBuilder::new() 110 | .stroke_width(1) 111 | .stroke_color(BinaryColor::On) 112 | .fill_color(BinaryColor::Off) 113 | .build(); 114 | 115 | Rectangle::new(Point::new(0, 0), Size::new(127, 63)) 116 | .into_styled(on) 117 | .draw(display) 118 | .unwrap(); 119 | } 120 | 121 | pub fn draw_image(&mut self) { 122 | let display = &mut self.display; 123 | let size = 32i32; 124 | let raw: ImageRaw = 125 | ImageRaw::new(include_bytes!("../sol_logo.raw"), size as u32); 126 | let im = Image::new(&raw, Point::new((128 - size) / 2, (64 - size) / 2)); 127 | im.draw(display).unwrap(); 128 | display.flush().unwrap(); 129 | } 130 | 131 | pub fn draw_qr_code(&mut self) { 132 | let display = &mut self.display; 133 | let qr = QrCode::encode_text(&self.wallet_address, QrCodeEcc::Low).unwrap(); 134 | let qr_size = qr.size(); 135 | 136 | let max_width = 128; 137 | let max_height = 64; 138 | let padding_y = 6; 139 | 140 | let available_height = max_height - (padding_y * 2); 141 | let scale = 2; 142 | 143 | // scale the QR to be able to scan it 144 | let qr_width = qr_size * scale; 145 | let qr_height = qr_size * scale; 146 | 147 | let offset_x = (max_width - qr_width) / 2; 148 | let offset_y = ((available_height - qr_height) / 2) + padding_y; 149 | 150 | for y in 0..qr_size { 151 | for x in 0..qr_size { 152 | // this condition determines whether we need to draw a pixel or not. 153 | if qr.get_module(x, y) { 154 | Rectangle::new( 155 | Point::new(offset_x + x * scale, offset_y + y * scale), 156 | Size::new(scale as u32, scale as u32), 157 | ) 158 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) 159 | .draw(display) 160 | .unwrap(); 161 | } 162 | } 163 | } 164 | display.flush().unwrap(); // write the data 165 | } 166 | 167 | pub fn draw_time(&mut self, data: (&str, &str)) { 168 | let x = 5; 169 | let y = 64 - 9; 170 | let (time, date) = data; 171 | self.create_text(&date, x as u8, y, FONT_4X6); 172 | let x_time = 128 - (time.len() * 4) - 5; 173 | self.create_text(&time, x_time as u8, y, FONT_4X6); 174 | } 175 | 176 | pub fn perpetual_data(&mut self, http: &mut Http) { 177 | self.create_black_rectangle(); 178 | let (time, date) = http.utc_offset_time().unwrap_or_default(); 179 | let max_width_size = 128; 180 | let label = "Sol Balance:"; 181 | let label_x_c = (max_width_size - label.len() * 6) / 2; 182 | let label_y_c = 16; 183 | 184 | let wallet_balance = http.get_balance(&self.wallet_address).unwrap_or(0); 185 | let readable_result = wallet_balance as f32 / LAMPORTS_PER_SOL as f32; 186 | 187 | let formatted = format!("{:.2}", readable_result); 188 | let value_x_c = (max_width_size - formatted.len() * 6) / 2; 189 | let value_x_y = 33; 190 | 191 | self.create_text(&label, label_x_c as u8, label_y_c, FONT_6X10); 192 | self.create_text(&formatted, value_x_c as u8, value_x_y, FONT_6X10); 193 | self.draw_time((&time, &date)); 194 | 195 | std::thread::sleep(Duration::from_millis(1500)); 196 | 197 | let (slot, tps) = http.get_tps().unwrap_or_default(); 198 | 199 | self.create_black_rectangle(); 200 | 201 | let height_constant = 6 + 5; 202 | let font_width_4x = 4; 203 | let font_width_6x = 6; 204 | 205 | let slot_label = "Slot:"; 206 | let slot_label_x_c = (max_width_size - slot_label.len() * font_width_4x) / 2; 207 | let slot_label_y_c = 8; 208 | 209 | let slot_value_x_c = (max_width_size - slot.to_string().len() * font_width_6x) / 2; 210 | let slot_value_y_c = slot_label_y_c + height_constant; 211 | 212 | let tps_label = "TPS:"; 213 | let tps_label_x_c = (max_width_size - tps_label.len() * font_width_4x) / 2; 214 | let tps_label_y_c = slot_value_y_c + height_constant + 6; 215 | 216 | let tps_value_x_c = (max_width_size - tps.to_string().len() * font_width_6x) / 2; 217 | let tps_value_y_c = tps_label_y_c + height_constant; 218 | 219 | //slot 220 | self.create_text(&slot_label, slot_label_x_c as u8, slot_label_y_c, FONT_4X6); 221 | self.create_text( 222 | &slot.to_string(), 223 | slot_value_x_c as u8, 224 | slot_value_y_c, 225 | FONT_6X10, 226 | ); 227 | self.draw_time((&time, &date)); 228 | 229 | // tps 230 | self.create_text(&tps_label, tps_label_x_c as u8, tps_label_y_c, FONT_4X6); 231 | self.create_text( 232 | &tps.to_string(), 233 | tps_value_x_c as u8, 234 | tps_value_y_c, 235 | FONT_6X10, 236 | ); 237 | self.draw_time((&time, &date)); 238 | 239 | std::thread::sleep(Duration::from_millis(1500)); 240 | 241 | let sol_price_label = "Sol USD Price:"; 242 | let sol_price_label_x_c = (max_width_size - sol_price_label.len() * 6) / 2; 243 | let sol_price_label_y_c = 16; 244 | 245 | let sol_price = http.get_solana_price().unwrap_or_default(); 246 | 247 | self.create_black_rectangle(); 248 | 249 | let sol_price_formatted = format!("{:.2}", sol_price); 250 | let sol_price_x_c = (max_width_size - sol_price_formatted.len() * 6) / 2; 251 | let sol_price_x_y = 33; 252 | 253 | self.create_text( 254 | &sol_price_label, 255 | sol_price_label_x_c as u8, 256 | sol_price_label_y_c, 257 | FONT_6X10, 258 | ); 259 | self.create_text( 260 | &sol_price_formatted, 261 | sol_price_x_c as u8, 262 | sol_price_x_y, 263 | FONT_6X10, 264 | ); 265 | self.draw_time((&time, &date)); 266 | 267 | std::thread::sleep(Duration::from_millis(1500)); 268 | 269 | self.create_black_rectangle(); 270 | 271 | // draw the QR code 272 | self.draw_qr_code(); 273 | 274 | std::thread::sleep(Duration::from_secs(6)); 275 | } 276 | } 277 | --------------------------------------------------------------------------------