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
epaper-2in9