//! Имитирует компактный дашборд и обновляет только изменяемые поля через `print_at`. mod common; use std::error::Error; use escpos_vfd::VfdWorker; fn pad_left(s: &str, width: usize) -> String { // Подгоняем ширину поля, чтобы при уменьшении числа не оставались "хвосты" от // предыдущего значения. Например, после 1000 должно появиться " 999", а не "9990". if s.chars().count() >= width { s.chars().take(width).collect() } else { format!("{:>width$}", s, width = width) } } fn main() -> Result<(), Box> { // Аргументы: // 1) port - serial-порт дисплея; // 2) width - ширина строки; // 3) delay_ms - пауза между изменениями данных. let mut args = common::ExampleArgs::from_env()?; let delay_ms: u64 = args.parse_or(200); let columns = args.columns; // Координаты ниже подобраны под 20 символов. Для более узких дисплеев лучше // сделать другой layout, поэтому пример явно сообщает об ограничении. if columns < 20 { return Err("update_at requires a display width of at least 20 columns".into()); } // Worker сохраняет кэш строк. Благодаря этому `print_at` и `print_line_diff` // согласованно обновляют только изменившиеся области. let worker = VfdWorker::start(args.config)?; let vfd = worker.handle(); vfd.clear()?; // Рисуем "шаблон" один раз. Дальше меняем только числовые поля внутри шаблона. // 12345678901234567890 // Temp: __.__C RPM:____ // Load: ___% Uptime:____ vfd.print_line_diff(1, "Temp: 00.00C RPM:0000")?; vfd.print_line_diff(2, "Load: 000% Up:0000s")?; // Координаты (1-based): // "Temp: 00.00C ..." -> числа начинаются с x=7, длина 5 (00.00) let temp_x = 7u8; let temp_w = 5usize; // "RPM:0000" -> числа начинаются после "RPM:"; в строке выше это x=16, длина 4 let rpm_x = 16u8; let rpm_w = 4usize; // "Load: 000%" -> числа x=7, длина 3 let load_x = 7u8; let load_w = 3usize; // "Up:0000s" -> числа x=12, длина 4 let up_x = 12u8; let up_w = 4usize; let mut t: f32 = 21.50; let mut rpm: u32 = 900; let mut load: u32 = 10; let mut uptime: u32 = 0; let spinner = ["|", "/", "-", "\\"]; let mut si = 0usize; loop { // Чуть "шевелим" данные, чтобы на дисплее были видны частичные обновления. t += 0.03; if t > 29.99 { t = 21.50; } rpm = (rpm + 37) % 9999; load = (load + 3) % 100; uptime = uptime.wrapping_add(1); // Важно: обновляем только куски строки в фиксированных местах. Это меньше // нагружает serial-линию и обычно выглядит спокойнее, чем полная перерисовка. let temp_s = pad_left(&format!("{:.2}", t), temp_w); let rpm_s = pad_left(&format!("{}", rpm), rpm_w); let load_s = pad_left(&format!("{}", load), load_w); let up_s = pad_left(&format!("{}", uptime % 10000), up_w); vfd.print_at(temp_x, 1, temp_s)?; vfd.print_at(rpm_x, 1, rpm_s)?; vfd.print_at(load_x, 2, load_s)?; vfd.print_at(up_x, 2, up_s)?; // "Живой" индикатор справа показывает, что цикл продолжает выполняться. if columns >= 20 { vfd.print_at(20, 2, spinner[si])?; si = (si + 1) % spinner.len(); } common::sleep_ms(delay_ms); } }