escpos-vfd 0.2.0

ESC/POS-compatible VFD customer display driver with sync and optional Tokio APIs
Documentation
//! Ошибки конфигурации, I/O и жизненного цикла worker.
//!
//! Библиотека возвращает типизированные ошибки вместо `anyhow`, чтобы вызывающий код
//! мог отдельно обработать неверные настройки, недоступный serial-порт, ошибку записи,
//! неправильные координаты и остановленный worker. Текст [`std::fmt::Display`]
//! ориентирован на диагностику, а варианты enum - на машинную обработку.

use std::sync::mpsc;

/// Ошибки проверки конфигурации до открытия serial-порта.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfigError {
    /// Имя serial-порта не задано или состоит только из пробельных символов.
    EmptyPortName,
    /// Скорость serial-порта равна нулю.
    ///
    /// Значение `baud_rate` задаётся в бодах, например `9600` или `115200`.
    ZeroBaudRate,
    /// Ширина дисплея не входит в диапазон `1..=255`.
    ///
    /// Поле содержит переданное количество колонок.
    InvalidColumns(usize),
    /// Высота дисплея не входит в диапазон `1..=255`.
    ///
    /// Поле содержит переданное количество строк.
    InvalidRows(usize),
    /// Диапазон яркости задан некорректно.
    InvalidBrightnessRange {
        /// Нижняя граница диапазона яркости.
        min: u8,
        /// Верхняя граница диапазона яркости.
        max: u8,
    },
    /// Ёмкость очереди worker равна нулю.
    ///
    /// Worker использует bounded queue, поэтому ему нужна ёмкость хотя бы `1`.
    ZeroQueueCapacity,
}

/// Ошибки выполнения команд дисплея.
#[derive(Debug)]
pub enum VfdError {
    /// Конфигурация не прошла проверку до открытия транспорта.
    Config(ConfigError),
    /// Ошибка `serialport` при открытии или настройке устройства.
    Serial(serialport::Error),
    /// Ошибка записи или flush в транспорт.
    Io(std::io::Error),
    /// Координата находится вне геометрии дисплея.
    InvalidCoordinate {
        /// Запрошенная колонка в координатах от единицы.
        x: u8,
        /// Запрошенная строка в координатах от единицы.
        y: u8,
        /// Настроенное количество колонок дисплея.
        columns: usize,
        /// Настроенное количество строк дисплея.
        rows: usize,
    },
    /// Строка находится вне геометрии дисплея.
    InvalidLine {
        /// Запрошенная строка в координатах от единицы.
        line: u8,
        /// Настроенное количество строк дисплея.
        rows: usize,
    },
    /// Запрошенная яркость не поддерживается конфигурацией.
    UnsupportedBrightness {
        /// Запрошенный уровень яркости.
        level: u8,
        /// Минимальный поддерживаемый уровень.
        min: u8,
        /// Максимальный поддерживаемый уровень.
        max: u8,
    },
    /// Очередь фонового worker закрыта.
    QueueClosed,
    /// Worker остановлен до подтверждения команды.
    WorkerStopped,
    /// Фоновый поток завершился с panic.
    WorkerPanicked,
    /// Async task worker был отменён до завершения shutdown.
    #[cfg(feature = "tokio")]
    WorkerCancelled,
}

impl std::fmt::Display for ConfigError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::EmptyPortName => f.write_str("serial port name must not be empty"),
            Self::ZeroBaudRate => f.write_str("baud rate must not be zero"),
            Self::InvalidColumns(columns) => {
                write!(f, "columns must be in 1..=255, got {columns}")
            }
            Self::InvalidRows(rows) => write!(f, "rows must be in 1..=255, got {rows}"),
            Self::InvalidBrightnessRange { min, max } => {
                write!(
                    f,
                    "brightness range must be ordered and non-zero, got {min}..={max}"
                )
            }
            Self::ZeroQueueCapacity => f.write_str("queue capacity must not be zero"),
        }
    }
}

impl std::error::Error for ConfigError {}

impl std::fmt::Display for VfdError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Config(error) => error.fmt(f),
            Self::Serial(error) => error.fmt(f),
            Self::Io(error) => error.fmt(f),
            Self::InvalidCoordinate {
                x,
                y,
                columns,
                rows,
            } => write!(
                f,
                "coordinate ({x}, {y}) is outside display geometry {columns}x{rows}"
            ),
            Self::InvalidLine { line, rows } => {
                write!(f, "line {line} is outside display rows 1..={rows}")
            }
            Self::UnsupportedBrightness { level, min, max } => {
                write!(
                    f,
                    "brightness {level} is outside supported range {min}..={max}"
                )
            }
            Self::QueueClosed => f.write_str("VFD worker queue is closed"),
            Self::WorkerStopped => f.write_str("VFD worker stopped before acknowledging command"),
            Self::WorkerPanicked => f.write_str("VFD worker thread panicked"),
            #[cfg(feature = "tokio")]
            Self::WorkerCancelled => f.write_str("VFD async worker task was cancelled"),
        }
    }
}

impl std::error::Error for VfdError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Config(error) => Some(error),
            Self::Serial(error) => Some(error),
            Self::Io(error) => Some(error),
            _ => None,
        }
    }
}

impl From<ConfigError> for VfdError {
    fn from(value: ConfigError) -> Self {
        Self::Config(value)
    }
}

impl From<serialport::Error> for VfdError {
    fn from(value: serialport::Error) -> Self {
        Self::Serial(value)
    }
}

impl From<std::io::Error> for VfdError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

impl<T> From<mpsc::SendError<T>> for VfdError {
    fn from(_: mpsc::SendError<T>) -> Self {
        Self::QueueClosed
    }
}

impl From<mpsc::RecvError> for VfdError {
    fn from(_: mpsc::RecvError) -> Self {
        Self::WorkerStopped
    }
}

/// Результат операций VFD.
pub type Result<T> = std::result::Result<T, VfdError>;