use std::{thread, sync::mpsc::{Sender, self}, time::Duration};

use embedded_graphics::{mono_font::MonoTextStyleBuilder, pixelcolor::BinaryColor, text::{TextStyleBuilder, Baseline, Text}, prelude::{Point, DrawTarget}, Drawable};
use embedded_svc::{wifi::{Configuration, ClientConfiguration, AuthMethod}, utils::mqtt::client::ConnState, mqtt::client::{MessageImpl, QoS, Connection, Event, Message}};
use epd_waveshare::{epd2in9_v2::{Display2in9, Epd2in9}, prelude::{WaveshareDisplay, Display, DisplayRotation}};
use esp_idf_hal::{prelude::Peripherals, gpio::{PinDriver, Gpio2, AnyIOPin}, spi::{config::Config, SpiDeviceDriver, SpiDriverConfig}, delay::Ets};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition, wifi::{EspWifi, BlockingWifi}, mqtt::client::{EspMqttClient, MqttClientConfiguration}};
use esp_idf_sys::{self as _, EspError}; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported

// To run this project, you will need to compile it locally, with a sdkconfig.defaults file with the following variable set:
// CONFIG_ESP_MAIN_TASK_STACK_SIZE=20000
// This is required to prevent a stack overflow. For this reason, the project will not run on the online IDE.

// Wokwi credentials taken from the guide: https://docs.wokwi.com/guides/esp32-wifi#connecting-to-the-wifi
const WIFI_SSID: &str = "Wokwi-GUEST";
const WIFI_PASS: &str = "";

// MQTT configuration. You can use http://www.emqx.io/online-mqtt-client to connect to the queue and send plaintext messages, which will be automatically displayed on the e-paper screen.
const MQTT_ENDPOINT: &str = "mqtts://broker.emqx.io:8883"; // host:port
const MQTT_CLIENT_ID: &str = "esp32_epaper_test_publish";
const MQTT_TOPIC_NAME: &str = "esp32_epaper_test_publish_topic";

fn main() -> anyhow::Result<()> {
    // It is necessary to call this function once. Otherwise some patches to the runtime
    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
    esp_idf_sys::link_patches();
    // Bind the log crate to the ESP Logging facilities
    esp_idf_svc::log::EspLogger::initialize_default();

    let peripherals = Peripherals::take().unwrap();
    let sys_loop = EspSystemEventLoop::take()?;
    let nvs = EspDefaultNvsPartition::take()?;

    // Blocking so that we can block until the IP is obtained
    let mut wifi = BlockingWifi::wrap(
        EspWifi::new(peripherals.modem, sys_loop.clone(), Some(nvs))?,
        sys_loop,
    )?;

    configure_wifi(&mut wifi)?;

	// Configure the E-paper display, see https://wokwi.com/projects/366167936725307393 for more examples
	let mut display = Display2in9::default();
    let spi = peripherals.spi2;
    let sclk = peripherals.pins.gpio18;
    let serial_out = peripherals.pins.gpio23;
    let cs = PinDriver::output(peripherals.pins.gpio5)?;
    let busy_in = PinDriver::input(peripherals.pins.gpio4)?;
    let dc = PinDriver::output(peripherals.pins.gpio22)?;
    let rst = PinDriver::output(peripherals.pins.gpio21)?;

    let config = Config::new().baudrate(112500.into());
    let mut device = SpiDeviceDriver::new_single(
        spi,
        sclk,
        serial_out,
        Option::<Gpio2>::None,
        Option::<AnyIOPin>::None,
        &SpiDriverConfig::default(),
        &config,
    )?;

    let mut delay = Ets;

    let mut epd = Epd2in9::new(&mut device, cs, busy_in, dc, rst, &mut delay)?;
    println!("E-paper init done!");

	// Set up a channel to send messages received from the MQTT queue (separate thread) to the main thread, to display them on the e-paper module
	let (sender, receiver) = mpsc::channel::<String>();
    let _mqtt_client: EspMqttClient<ConnState<MessageImpl, EspError>> = setup_mqtt_client(sender)?;
    
	loop {
		// Check for new messages every 2 seconds
        let message = receiver.recv_timeout(Duration::from_millis(2000));
        if let Ok(message) = message {
            println!("Message received in main thread: {:?}", message);
			display.clear(BinaryColor::Off)?;
            draw_text(&mut display, &message, 0, 0);
			epd.update_frame(&mut device, display.buffer(), &mut delay)?;
			epd.display_frame(&mut device, &mut delay)?;
        }
    }
}

fn configure_wifi(wifi: &mut BlockingWifi<EspWifi>) -> Result<(), EspError> {
    wifi.set_configuration(&Configuration::Client(ClientConfiguration {
        ssid: WIFI_SSID.into(),
        password: WIFI_PASS.into(),
        auth_method: AuthMethod::None,
        ..Default::default()
    }))?;
    wifi.start()?;
    println!("Wifi started!");

    wifi.connect()?;
    println!("Wifi connected!");

    wifi.wait_netif_up()?;
    println!("Wifi ready!");
    
    Ok(())
}


fn setup_mqtt_client(sender: Sender<String>) -> Result<EspMqttClient<ConnState<MessageImpl, EspError>>, EspError> {
    println!("About to start MQTT client");

    let conf = MqttClientConfiguration {
        client_id: Some(MQTT_CLIENT_ID),
        crt_bundle_attach: Some(esp_idf_sys::esp_crt_bundle_attach),
        ..Default::default()
    };
    let (mut client, mut connection) =
        EspMqttClient::new_with_conn(MQTT_ENDPOINT, &conf)?;

    println!("MQTT client started");

    thread::spawn(move || {
        println!("MQTT Listening for messages");

		// Send received messages back to the main thread to display them
        while let Some(msg) = connection.next() {
            match msg {
                Err(e) => println!("MQTT Message ERROR: {}", e),
                Ok(msg) => {
                    println!("MQTT Message: {:?}", msg);
                    if let Event::Received(msg) = msg {
                        let parsed_string = String::from_utf8(msg.data().to_vec());
                        if let Ok(parsed_string) = parsed_string {
                            println!("Parsed MQTT message: {:?}", parsed_string);
                            sender.send(parsed_string).unwrap();
                        }
                    }
                },
            }
        }

        println!("MQTT connection loop exit");
    });

    client.subscribe(MQTT_TOPIC_NAME, QoS::AtMostOnce)?;

    println!("Subscribed to all topics ({})", MQTT_TOPIC_NAME);

	// This will be the first message appearing on the screen
    client.publish(
        MQTT_TOPIC_NAME,
        QoS::AtMostOnce,
        false,
        format!("Hello from {}!", MQTT_TOPIC_NAME).as_bytes(),
    )?;

    println!("Published a hello message to topic \"{}\".", MQTT_TOPIC_NAME);

    Ok(client)
}

pub fn draw_text(display: &mut Display2in9, text: &str, x: i32, y: i32) {
	// Rotate the display to take into account the horizontal placement of the e-paper module
	display.set_rotation(DisplayRotation::Rotate90);
    let style = MonoTextStyleBuilder::new()
        .font(&embedded_graphics::mono_font::ascii::FONT_6X10)
        .text_color(BinaryColor::Off)
        .background_color(BinaryColor::On)
        .build();

    let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build();


    let _ = Text::with_text_style(text, Point::new(x, y), style, text_style).draw(display);
	// Reset the rotation so that the clear() method will work properly
	display.set_rotation(DisplayRotation::Rotate0);
}	
Loading
epaper-2in9