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