Skip to main content

escpos_vfd/
error.rs

1//! Ошибки конфигурации, I/O и жизненного цикла worker.
2//!
3//! Библиотека возвращает типизированные ошибки вместо `anyhow`, чтобы вызывающий код
4//! мог отдельно обработать неверные настройки, недоступный serial-порт, ошибку записи,
5//! неправильные координаты и остановленный worker. Текст [`std::fmt::Display`]
6//! ориентирован на диагностику, а варианты enum - на машинную обработку.
7
8use std::sync::mpsc;
9
10/// Ошибки проверки конфигурации до открытия serial-порта.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum ConfigError {
13    /// Имя serial-порта не задано или состоит только из пробельных символов.
14    EmptyPortName,
15    /// Скорость serial-порта равна нулю.
16    ///
17    /// Значение `baud_rate` задаётся в бодах, например `9600` или `115200`.
18    ZeroBaudRate,
19    /// Ширина дисплея не входит в диапазон `1..=255`.
20    ///
21    /// Поле содержит переданное количество колонок.
22    InvalidColumns(usize),
23    /// Высота дисплея не входит в диапазон `1..=255`.
24    ///
25    /// Поле содержит переданное количество строк.
26    InvalidRows(usize),
27    /// Диапазон яркости задан некорректно.
28    InvalidBrightnessRange {
29        /// Нижняя граница диапазона яркости.
30        min: u8,
31        /// Верхняя граница диапазона яркости.
32        max: u8,
33    },
34    /// Ёмкость очереди worker равна нулю.
35    ///
36    /// Worker использует bounded queue, поэтому ему нужна ёмкость хотя бы `1`.
37    ZeroQueueCapacity,
38}
39
40/// Ошибки выполнения команд дисплея.
41#[derive(Debug)]
42pub enum VfdError {
43    /// Конфигурация не прошла проверку до открытия транспорта.
44    Config(ConfigError),
45    /// Ошибка `serialport` при открытии или настройке устройства.
46    Serial(serialport::Error),
47    /// Ошибка записи или flush в транспорт.
48    Io(std::io::Error),
49    /// Координата находится вне геометрии дисплея.
50    InvalidCoordinate {
51        /// Запрошенная колонка в координатах от единицы.
52        x: u8,
53        /// Запрошенная строка в координатах от единицы.
54        y: u8,
55        /// Настроенное количество колонок дисплея.
56        columns: usize,
57        /// Настроенное количество строк дисплея.
58        rows: usize,
59    },
60    /// Строка находится вне геометрии дисплея.
61    InvalidLine {
62        /// Запрошенная строка в координатах от единицы.
63        line: u8,
64        /// Настроенное количество строк дисплея.
65        rows: usize,
66    },
67    /// Запрошенная яркость не поддерживается конфигурацией.
68    UnsupportedBrightness {
69        /// Запрошенный уровень яркости.
70        level: u8,
71        /// Минимальный поддерживаемый уровень.
72        min: u8,
73        /// Максимальный поддерживаемый уровень.
74        max: u8,
75    },
76    /// Текст бегущей строки превышает безопасный предел очереди worker-а.
77    TextTooLong {
78        /// Максимально допустимое число символов.
79        max: usize,
80    },
81    /// Raw-команда превышает безопасный предел очереди worker-а.
82    RawPayloadTooLarge {
83        /// Фактический размер команды в байтах.
84        length: usize,
85        /// Максимально допустимый размер в байтах.
86        max: usize,
87    },
88    /// Скорость бегущей строки превышает поддерживаемый предел.
89    InvalidMarqueeSpeed {
90        /// Запрошенная скорость в символах/кадрах в секунду.
91        cps: u32,
92        /// Максимально допустимая скорость.
93        max: u32,
94    },
95    /// Очередь фонового worker закрыта.
96    QueueClosed,
97    /// Worker остановлен до подтверждения команды.
98    WorkerStopped,
99    /// Фоновый поток завершился с panic.
100    WorkerPanicked,
101    /// Async task worker был отменён до завершения shutdown.
102    #[cfg(feature = "tokio")]
103    WorkerCancelled,
104}
105
106impl std::fmt::Display for ConfigError {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        match self {
109            Self::EmptyPortName => f.write_str("serial port name must not be empty"),
110            Self::ZeroBaudRate => f.write_str("baud rate must not be zero"),
111            Self::InvalidColumns(columns) => {
112                write!(f, "columns must be in 1..=255, got {columns}")
113            }
114            Self::InvalidRows(rows) => write!(f, "rows must be in 1..=255, got {rows}"),
115            Self::InvalidBrightnessRange { min, max } => {
116                write!(
117                    f,
118                    "brightness range must be ordered and non-zero, got {min}..={max}"
119                )
120            }
121            Self::ZeroQueueCapacity => f.write_str("queue capacity must not be zero"),
122        }
123    }
124}
125
126impl std::error::Error for ConfigError {}
127
128impl std::fmt::Display for VfdError {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        match self {
131            Self::Config(error) => error.fmt(f),
132            Self::Serial(error) => error.fmt(f),
133            Self::Io(error) => error.fmt(f),
134            Self::InvalidCoordinate {
135                x,
136                y,
137                columns,
138                rows,
139            } => write!(
140                f,
141                "coordinate ({x}, {y}) is outside display geometry {columns}x{rows}"
142            ),
143            Self::InvalidLine { line, rows } => {
144                write!(f, "line {line} is outside display rows 1..={rows}")
145            }
146            Self::UnsupportedBrightness { level, min, max } => {
147                write!(
148                    f,
149                    "brightness {level} is outside supported range {min}..={max}"
150                )
151            }
152            Self::TextTooLong { max } => {
153                write!(f, "marquee text exceeds the limit of {max} characters")
154            }
155            Self::RawPayloadTooLarge { length, max } => {
156                write!(f, "raw payload is {length} bytes, maximum is {max}")
157            }
158            Self::InvalidMarqueeSpeed { cps, max } => {
159                write!(f, "marquee speed {cps} exceeds maximum {max}")
160            }
161            Self::QueueClosed => f.write_str("VFD worker queue is closed"),
162            Self::WorkerStopped => f.write_str("VFD worker stopped before acknowledging command"),
163            Self::WorkerPanicked => f.write_str("VFD worker thread panicked"),
164            #[cfg(feature = "tokio")]
165            Self::WorkerCancelled => f.write_str("VFD async worker task was cancelled"),
166        }
167    }
168}
169
170impl std::error::Error for VfdError {
171    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
172        match self {
173            Self::Config(error) => Some(error),
174            Self::Serial(error) => Some(error),
175            Self::Io(error) => Some(error),
176            _ => None,
177        }
178    }
179}
180
181impl From<ConfigError> for VfdError {
182    fn from(value: ConfigError) -> Self {
183        Self::Config(value)
184    }
185}
186
187impl From<serialport::Error> for VfdError {
188    fn from(value: serialport::Error) -> Self {
189        Self::Serial(value)
190    }
191}
192
193impl From<std::io::Error> for VfdError {
194    fn from(value: std::io::Error) -> Self {
195        Self::Io(value)
196    }
197}
198
199impl<T> From<mpsc::SendError<T>> for VfdError {
200    fn from(_: mpsc::SendError<T>) -> Self {
201        Self::QueueClosed
202    }
203}
204
205impl From<mpsc::RecvError> for VfdError {
206    fn from(_: mpsc::RecvError) -> Self {
207        Self::WorkerStopped
208    }
209}
210
211/// Результат операций VFD.
212pub type Result<T> = std::result::Result<T, VfdError>;