This commit is contained in:
Кобелев Андрей Андреевич
2026-01-28 01:36:35 +05:00
commit 446a710e01
11 changed files with 1191 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
use anyhow::Result;
use encoding_rs::IBM866;
use serialport::SerialPort;
use std::io::Write;
use std::time::Duration;
const TABLE_CYR: u8 = 6; // PD-2600: your Cyrillic table (ESC t 6)
const FIXED_BAUD: u32 = 9600;
#[derive(Debug, Clone)]
pub struct VfdConfig {
pub port_name: String,
pub width: usize,
pub timeout: Duration,
}
impl VfdConfig {
pub fn new(port_name: impl Into<String>) -> Self {
Self {
port_name: port_name.into(),
width: 20,
timeout: Duration::from_millis(100),
}
}
pub fn with_width(mut self, width: usize) -> Self {
self.width = width.max(1);
self
}
#[allow(dead_code)]
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
}
pub struct Vfd {
port: Box<dyn SerialPort>,
pub width: usize,
blank_line: String,
}
impl Vfd {
pub fn open(cfg: VfdConfig) -> Result<Self> {
let mut port = serialport::new(cfg.port_name, FIXED_BAUD)
.timeout(cfg.timeout)
.open()?;
// ESC @
port.write_all(&[0x1B, 0x40])?;
// ESC t 6
port.write_all(&[0x1B, 0x74, TABLE_CYR])?;
Ok(Self {
port,
width: cfg.width,
blank_line: " ".repeat(cfg.width),
})
}
pub fn clear(&mut self) -> std::io::Result<()> {
self.port.write_all(&[0x0C])
}
pub fn print_line(&mut self, line: u8, text: &str) -> std::io::Result<()> {
if !(1..=2).contains(&line) {
return Ok(());
}
// overwrite the whole line to avoid leftovers
self.goto_xy(1, line)?;
// FIX: take owned copy so self isn't immutably borrowed during &mut self call
let blank = self.blank_line.clone();
self.write_cp866(&blank)?;
self.goto_xy(1, line)?;
let fitted = fit_to_width(&sanitize_for_cp866(text), self.width);
self.write_cp866(&fitted)
}
/// Print already-prepared fixed-width frame at (1, line) without clearing.
/// Use this for marquee to avoid double-writes.
pub fn print_frame(&mut self, line: u8, frame: &str) -> std::io::Result<()> {
if !(1..=2).contains(&line) {
// silently ignore invalid lines (or return an error if you want)
return Ok(());
}
self.goto_xy(1, line)?;
// frame should already be width-sized; if not, fit it
let fitted = fit_to_width(frame, self.width);
self.write_cp866(&fitted)
}
#[allow(dead_code)]
pub fn print_at(&mut self, x: u8, y: u8, text: &str) -> std::io::Result<()> {
self.goto_xy(x, y)?;
self.write_cp866(&sanitize_for_cp866(text))
}
fn goto_xy(&mut self, x: u8, y: u8) -> std::io::Result<()> {
// US $ x y
self.port.write_all(&[0x1F, 0x24, x, y])
}
fn write_cp866(&mut self, s: &str) -> std::io::Result<()> {
let (bytes, _, _) = IBM866.encode(s);
self.port.write_all(&bytes)
}
/// Epson customer display: US X n (brightness), n=1..4
pub fn set_brightness(&mut self, n: u8) -> Result<()> {
let n = n.clamp(1, 4);
let cmd = [0x1F, 0x58, n]; // US 'X' n
self.port.write_all(&cmd)?;
self.port.flush()?;
std::thread::sleep(std::time::Duration::from_millis(2));
Ok(())
}
}
pub fn sanitize_for_cp866(s: &str) -> String {
s.chars()
.map(|c| match c {
'…' => '.', //
'—' | '' => '-', //
'№' => '#', //
'\t' => ' ',
'“' | '”' => '"',
'' | '' => '\'',
_ => c,
})
.collect()
}
pub fn fit_to_width(s: &str, width: usize) -> String {
let mut out: String = s.chars().take(width).collect();
let len = out.chars().count();
if len < width {
out.push_str(&" ".repeat(width - len));
}
out
}