escpos-vfd 0.3.0

ESC/POS-compatible VFD customer display driver with sync and optional Tokio APIs
Documentation
//! Общие утилиты командной строки для демонстрационных программ.
//!
//! Каждый пример компилируется как отдельный binary. Этот модуль убирает повторение:
//! первый аргумент всегда serial-порт, второй - ширина дисплея, остальные параметры
//! зависят от конкретного примера.
#![allow(
    dead_code,
    reason = "модуль компилируется отдельно для каждого примера, поэтому часть API используется в соседних примерах"
)]

use escpos_vfd::{Preset, VfdConfig};
use std::error::Error;
use std::str::FromStr;
use std::vec::IntoIter;

const DEFAULT_PORT: &str = "/dev/cu.usbmodem101";
const DEFAULT_WIDTH: usize = 20;

/// Общие аргументы примера: готовая конфигурация, ширина дисплея и оставшиеся параметры.
pub struct ExampleArgs {
    /// Конфигурация VFD на основе пресета текущего дисплея.
    pub config: VfdConfig,
    /// Ширина дисплея в символах.
    pub columns: usize,
    remaining: IntoIter<String>,
}

impl ExampleArgs {
    /// Читает общие аргументы из командной строки и применяет значения по умолчанию.
    pub fn from_env() -> Result<Self, Box<dyn Error>> {
        let mut values = std::env::args().skip(1);

        // Примеры должны запускаться без длинной CLI-команды на авторском стенде, но
        // первый аргумент позволяет сразу проверить другой USB/COM-порт.
        let port_name = values.next().unwrap_or_else(|| DEFAULT_PORT.to_string());

        // Геометрия дисплея участвует в проверке координат и обрезке строк. Меняем
        // только ширину, потому что демонстрации ниже рассчитаны на две строки.
        let columns = values
            .next()
            .and_then(|value| value.parse().ok())
            .unwrap_or(DEFAULT_WIDTH);
        let config = preset_config(port_name, columns)?;

        Ok(Self {
            columns,
            config,
            remaining: values.collect::<Vec<_>>().into_iter(),
        })
    }

    /// Читает следующий аргумент нужного типа или возвращает переданное значение по умолчанию.
    pub fn parse_or<T>(&mut self, default: T) -> T
    where
        T: FromStr,
    {
        self.remaining
            .next()
            .and_then(|value| value.parse().ok())
            .unwrap_or(default)
    }

    /// Читает следующий строковый аргумент или возвращает строку по умолчанию.
    pub fn string_or(&mut self, default: &str) -> String {
        self.remaining.next().unwrap_or_else(|| default.to_string())
    }
}

/// Приостанавливает пример на указанное число миллисекунд.
pub fn sleep_ms(delay_ms: u64) {
    std::thread::sleep(std::time::Duration::from_millis(delay_ms));
}

/// Создаёт конфигурацию пресета текущего дисплея с переопределяемой шириной.
pub fn preset_config(
    port_name: impl Into<String>,
    columns: usize,
) -> Result<VfdConfig, Box<dyn Error>> {
    let mut config = VfdConfig::preset(port_name, Preset::Epson20x2Cp866)?;

    // Пресет сохраняет baud/code table/кодировку, а ширину даём менять из CLI, чтобы
    // теми же примерами проверять 16x2, 20x2 и другие ESC/POS-совместимые VFD.
    config.display.columns = columns;
    config.validate()?;
    Ok(config)
}