├── 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 |
--------------------------------------------------------------------------------