1use device_envoy_core::lcd_text::{LcdTextDriver, LcdTextError, LcdTextFrame, LcdTextWrite};
107use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
108use embassy_sync::signal::Signal;
109use heapless::Vec;
110
111#[doc(hidden)]
112pub use paste;
113
114pub use device_envoy_core::lcd_text::LcdText;
115
116#[cfg(doc)]
117pub mod lcd_text_generated {
118 use crate::Result;
119
120 pub struct LcdTextGenerated;
125
126 pub struct I2csGenerated;
128
129 pub struct LcdTextGenerated20x4;
131
132 impl I2csGenerated {
133 pub fn new<I2cPeripheral, SdaPin, SclPin>(
136 i2c_peripheral: I2cPeripheral,
137 sda: SdaPin,
138 scl: SclPin,
139 spawner: embassy_executor::Spawner,
140 ) -> Result<(&'static LcdTextGenerated, &'static LcdTextGenerated20x4)> {
141 static INSTANCE_16X2: LcdTextGenerated = LcdTextGenerated;
142 static INSTANCE_20X4: LcdTextGenerated20x4 = LcdTextGenerated20x4;
143 let _ = (i2c_peripheral, sda, scl, spawner);
144 Ok((&INSTANCE_16X2, &INSTANCE_20X4))
145 }
146 }
147
148 impl LcdTextGenerated {
149 pub const WIDTH: usize = 16;
151 pub const HEIGHT: usize = 2;
153 pub const ADDRESS: u8 = 0x27;
155
156 pub fn new<I2cPeripheral, SdaPin, SclPin>(
159 i2c_peripheral: I2cPeripheral,
160 sda: SdaPin,
161 scl: SclPin,
162 spawner: embassy_executor::Spawner,
163 ) -> Result<&'static Self> {
164 static INSTANCE: LcdTextGenerated = LcdTextGenerated;
165 let _ = (i2c_peripheral, sda, scl, spawner);
166 Ok(&INSTANCE)
167 }
168 }
169
170 impl crate::lcd_text::LcdText<16, 2> for LcdTextGenerated {
171 const ADDRESS: u8 = 0x27;
172
173 fn write_text(&self, text: impl AsRef<str>) {
174 let _ = text;
175 }
176 }
177
178 impl LcdTextGenerated20x4 {
179 pub const WIDTH: usize = 20;
181 pub const HEIGHT: usize = 4;
183 pub const ADDRESS: u8 = 0x3F;
185
186 pub fn new<I2cPeripheral, SdaPin, SclPin>(
189 i2c_peripheral: I2cPeripheral,
190 sda: SdaPin,
191 scl: SclPin,
192 spawner: embassy_executor::Spawner,
193 ) -> Result<&'static Self> {
194 static INSTANCE: LcdTextGenerated20x4 = LcdTextGenerated20x4;
195 let _ = (i2c_peripheral, sda, scl, spawner);
196 Ok(&INSTANCE)
197 }
198 }
199
200 impl crate::lcd_text::LcdText<20, 4> for LcdTextGenerated20x4 {
201 const ADDRESS: u8 = 0x3F;
202
203 fn write_text(&self, text: impl AsRef<str>) {
204 let _ = text;
205 }
206 }
207}
208
209#[doc(hidden)]
210pub type __I2csSignal<T> = Signal<CriticalSectionRawMutex, T>;
211#[doc(hidden)]
212pub use device_envoy_core::lcd_text::render_lcd_text_frame as __render_lcd_text_frame;
213#[doc(hidden)]
214pub use device_envoy_core::lcd_text::LcdText as __LcdText;
215#[doc(hidden)]
216pub use device_envoy_core::lcd_text::LcdTextDriver as __LcdTextDriver;
217#[doc(hidden)]
218pub type __LcdTextFrame<const MAX_CHARS: usize> =
219 device_envoy_core::lcd_text::LcdTextFrame<MAX_CHARS>;
220#[doc(hidden)]
221pub const fn __max_lcd_cells<const N: usize>(widths: [usize; N], heights: [usize; N]) -> usize {
222 let mut max_cells = 0;
223 let mut index = 0;
224 while index < N {
225 let cells = widths[index] * heights[index];
226 if cells > max_cells {
227 max_cells = cells;
228 }
229 index += 1;
230 }
231 max_cells
232}
233#[doc(hidden)]
234pub async fn __select_array<Fut, const N: usize>(futures: [Fut; N]) -> (Fut::Output, usize)
235where
236 Fut: core::future::Future,
237{
238 embassy_futures::select::select_array(futures).await
239}
240
241#[doc(hidden)]
242pub const fn __assert_unique_addresses<const N: usize>(addresses: [u8; N]) {
243 let mut first_index = 0;
244 while first_index < N {
245 let mut second_index = first_index + 1;
246 while second_index < N {
247 if addresses[first_index] == addresses[second_index] {
248 panic!("duplicate lcd_text I2C address in i2cs! group");
249 }
250 second_index += 1;
251 }
252 first_index += 1;
253 }
254}
255
256#[doc(hidden)]
257pub async fn __write_lcd_text_cells<const ADDRESS_COUNT: usize, const MAX_CHARS: usize>(
258 lcd_text_driver: &mut LcdTextDriver,
259 lcd_text_write: &mut impl LcdTextWrite,
260 initialized_addresses: &mut Vec<u8, ADDRESS_COUNT>,
261 address: u8,
262 width: usize,
263 height: usize,
264 cells: &[u8],
265) {
266 let first_use_of_address = !initialized_addresses
267 .iter()
268 .any(|initialized_address| *initialized_address == address);
269
270 lcd_text_driver.set_address(address);
271 if first_use_of_address {
272 if lcd_text_driver.init(lcd_text_write).await.is_err() {
273 return;
274 }
275 let _ = initialized_addresses.push(address);
276 }
277
278 let mut lcd_text_frame = LcdTextFrame::<MAX_CHARS>::new_blank(width, height);
279 let cell_count = core::cmp::min(width * height, cells.len());
280 for cell_index in 0..cell_count {
281 lcd_text_frame.cells[cell_index] = cells[cell_index];
282 }
283
284 let _ = lcd_text_driver
285 .write_frame(lcd_text_write, &lcd_text_frame)
286 .await;
287}
288
289#[doc(hidden)]
290pub struct EspLcdTextWrite {
291 i2c: crate::esp_hal::i2c::master::I2c<'static, crate::esp_hal::Blocking>,
292}
293
294impl EspLcdTextWrite {
295 #[doc(hidden)]
296 pub fn __new(i2c: crate::esp_hal::i2c::master::I2c<'static, crate::esp_hal::Blocking>) -> Self {
297 Self { i2c }
298 }
299}
300
301impl LcdTextWrite for EspLcdTextWrite {
302 fn write(&mut self, address: u8, data: u8) -> core::result::Result<(), LcdTextError> {
303 self.i2c
304 .write(address, &[data])
305 .map_err(|_| LcdTextError::I2cWrite { address })
306 }
307}
308
309#[cfg(not(feature = "host"))]
335#[doc(hidden)]
336#[macro_export]
337macro_rules! i2cs {
338 ($($tt:tt)*) => { $crate::__i2cs_impl! { $($tt)* } };
339}
340
341#[cfg(not(feature = "host"))]
342#[doc(hidden)]
343#[macro_export]
344macro_rules! __i2cs_impl {
345 (
346 i2c: $i2c:ident,
347 sda_pin: $sda_pin:ident,
348 scl_pin: $scl_pin:ident,
349 $group_vis:vis $group_name:ident {
350 $(
351 $lcd_vis:vis $lcd_name:ident {
352 width: $width:expr,
353 height: $height:expr,
354 address: $address:expr
355 }
356 ),+ $(,)?
357 }
358 ) => {
359 $crate::lcd_text::paste::paste! {
360 const _: () = {
361 $crate::lcd_text::__assert_unique_addresses([$($address,)+]);
362 };
363 const [<__ $group_name:upper _MAX_LCD_CELLS>]: usize =
364 $crate::lcd_text::__max_lcd_cells([$($width,)+], [$($height,)+]);
365
366 $(
367 static [<$lcd_name:upper _FRAME_SIGNAL>]:
368 $crate::lcd_text::__I2csSignal<
369 $crate::lcd_text::__LcdTextFrame<{ [<__ $group_name:upper _MAX_LCD_CELLS>] }>
370 > =
371 $crate::lcd_text::__I2csSignal::new();
372 )+
373
374 $group_vis struct $group_name;
375
376 struct [<__ $group_name Devices>] {
377 $(
378 [<$lcd_name:snake>]: &'static $lcd_name,
379 )+
380 }
381
382 impl [<__ $group_name Devices>] {
383 fn into_tuple(self) -> ($(&'static $lcd_name,)+) {
384 (
385 $(self.[<$lcd_name:snake>],)+
386 )
387 }
388 }
389
390 impl $group_name {
391 fn __new_devices(
392 i2c_peripheral: $crate::esp_hal::peripherals::$i2c<'static>,
393 sda: $crate::esp_hal::peripherals::$sda_pin<'static>,
394 scl: $crate::esp_hal::peripherals::$scl_pin<'static>,
395 spawner: embassy_executor::Spawner,
396 ) -> $crate::Result<[<__ $group_name Devices>]> {
397 let i2c = $crate::esp_hal::i2c::master::I2c::new(
398 i2c_peripheral,
399 $crate::esp_hal::i2c::master::Config::default(),
400 )
401 .map_err($crate::Error::I2cConfig)?
402 .with_sda(sda)
403 .with_scl(scl);
404
405 let token = [<__i2cs_task_ $group_name:snake>](i2c);
406 spawner.spawn(token).map_err($crate::Error::TaskSpawn)?;
407
408 $(
409 static [<$lcd_name:upper _INSTANCE>]: $lcd_name = $lcd_name;
410 let [<$lcd_name:snake>] = &[<$lcd_name:upper _INSTANCE>];
411 )+
412
413 Ok([<__ $group_name Devices>] {
414 $(
415 [<$lcd_name:snake>],
416 )+
417 })
418 }
419
420 pub fn new(
421 i2c_peripheral: $crate::esp_hal::peripherals::$i2c<'static>,
422 sda: $crate::esp_hal::peripherals::$sda_pin<'static>,
423 scl: $crate::esp_hal::peripherals::$scl_pin<'static>,
424 spawner: embassy_executor::Spawner,
425 ) -> $crate::Result<($(&'static $lcd_name,)+)> {
426 Ok(Self::__new_devices(i2c_peripheral, sda, scl, spawner)?.into_tuple())
427 }
428 }
429
430 $(
431 $lcd_vis struct $lcd_name;
432
433 impl $crate::lcd_text::__LcdText<$width, $height> for $lcd_name {
434 const ADDRESS: u8 = $address;
435
436 fn write_text(&self, text: impl AsRef<str>) {
437 ::core::assert!($width > 0, "lcd_text width must be > 0");
438 ::core::assert!($height > 0, "lcd_text height must be > 0");
439 ::core::assert!(
440 $height <= 4,
441 "lcd_text height must be <= 4 for HD44780 row map"
442 );
443 let lcd_text_frame =
444 $crate::lcd_text::__render_lcd_text_frame::<
445 $width,
446 $height,
447 { [<__ $group_name:upper _MAX_LCD_CELLS>] }
448 >(text.as_ref());
449 [<$lcd_name:upper _FRAME_SIGNAL>].signal(lcd_text_frame);
450 }
451 }
452
453 impl $lcd_name {
454 pub const WIDTH: usize = $width;
455 pub const HEIGHT: usize = $height;
456 pub const ADDRESS: u8 = $address;
457
458 pub fn new(
459 i2c_peripheral: $crate::esp_hal::peripherals::$i2c<'static>,
460 sda: $crate::esp_hal::peripherals::$sda_pin<'static>,
461 scl: $crate::esp_hal::peripherals::$scl_pin<'static>,
462 spawner: embassy_executor::Spawner,
463 ) -> $crate::Result<&'static Self> {
464 let [<__ $group_name:snake _devices>] =
465 $group_name::__new_devices(i2c_peripheral, sda, scl, spawner)?;
466 Ok([<__ $group_name:snake _devices>].[<$lcd_name:snake>])
467 }
468
469 }
470 )+
471
472 #[embassy_executor::task]
473 async fn [<__i2cs_task_ $group_name:snake>](
474 i2c: $crate::esp_hal::i2c::master::I2c<'static, $crate::esp_hal::Blocking>,
475 ) -> ! {
476 let mut esp_lcd_text_write = $crate::lcd_text::EspLcdTextWrite::__new(i2c);
477 let mut lcd_text_driver = $crate::lcd_text::__LcdTextDriver::new(0x27);
478 const ADDRESS_COUNT: usize = [$($address,)+].len();
479 let mut initialized_addresses: heapless::Vec<u8, ADDRESS_COUNT> = heapless::Vec::new();
480 let addresses = [$($address,)+];
481 let widths = [$($width,)+];
482 let heights = [$($height,)+];
483
484 loop {
485 let (lcd_text_frame, ready_index) = $crate::lcd_text::__select_array([
486 $([<$lcd_name:upper _FRAME_SIGNAL>].wait(),)+
487 ]).await;
488 $crate::lcd_text::__write_lcd_text_cells::<
489 ADDRESS_COUNT,
490 { [<__ $group_name:upper _MAX_LCD_CELLS>] }
491 >(
492 &mut lcd_text_driver,
493 &mut esp_lcd_text_write,
494 &mut initialized_addresses,
495 addresses[ready_index],
496 widths[ready_index],
497 heights[ready_index],
498 &lcd_text_frame.cells,
499 ).await;
500 }
501 }
502 }
503 };
504}
505
506#[cfg(not(feature = "host"))]
507#[doc(inline)]
508pub use i2cs;
509
510#[cfg(not(feature = "host"))]
533#[doc(hidden)]
534#[macro_export]
535macro_rules! lcd_text {
536 ($($tt:tt)*) => { $crate::__lcd_text_impl! { $($tt)* } };
537}
538
539#[cfg(not(feature = "host"))]
540#[doc(hidden)]
541#[macro_export]
542macro_rules! __lcd_text_impl {
543 (
544 i2c: $i2c:ident,
545 sda_pin: $sda_pin:ident,
546 scl_pin: $scl_pin:ident,
547 $lcd_vis:vis $lcd_name:ident {
548 width: $width:expr,
549 height: $height:expr,
550 address: $address:expr
551 }
552 ) => {
553 $crate::lcd_text::paste::paste! {
554 $crate::i2cs! {
555 i2c: $i2c,
556 sda_pin: $sda_pin,
557 scl_pin: $scl_pin,
558 [<LcdTextGroupFor $lcd_name>] {
559 $lcd_vis $lcd_name {
560 width: $width,
561 height: $height,
562 address: $address
563 }
564 }
565 }
566 }
567 };
568}
569
570#[cfg(not(feature = "host"))]
571#[doc(inline)]
572pub use lcd_text;