1use std::sync::mpsc;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum ConfigError {
13 EmptyPortName,
15 ZeroBaudRate,
19 InvalidColumns(usize),
23 InvalidRows(usize),
27 InvalidBrightnessRange {
29 min: u8,
31 max: u8,
33 },
34 ZeroQueueCapacity,
38}
39
40#[derive(Debug)]
42pub enum VfdError {
43 Config(ConfigError),
45 Serial(serialport::Error),
47 Io(std::io::Error),
49 InvalidCoordinate {
51 x: u8,
53 y: u8,
55 columns: usize,
57 rows: usize,
59 },
60 InvalidLine {
62 line: u8,
64 rows: usize,
66 },
67 UnsupportedBrightness {
69 level: u8,
71 min: u8,
73 max: u8,
75 },
76 TextTooLong {
78 max: usize,
80 },
81 RawPayloadTooLarge {
83 length: usize,
85 max: usize,
87 },
88 InvalidMarqueeSpeed {
90 cps: u32,
92 max: u32,
94 },
95 QueueClosed,
97 WorkerStopped,
99 WorkerPanicked,
101 #[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
211pub type Result<T> = std::result::Result<T, VfdError>;