рефакторинг библиотеки.

This commit is contained in:
Кобелев Андрей Андреевич
2026-08-15 23:53:45 +05:00
parent 896670b62f
commit efc53e6ef5
13 changed files with 584 additions and 371 deletions
+12 -28
View File
@@ -1,13 +1,11 @@
//! Демонстрирует мигание произвольного текста в выбранной позиции через `print_at`.
mod common;
use anyhow::Result;
use std::time::Duration;
use m::vfd::VfdConfig;
use m::worker::VfdWorker;
fn spaces(n: usize) -> String {
" ".repeat(n)
}
fn main() -> Result<()> {
// args:
// 1) port (default: /dev/cu.usbmodem101)
@@ -16,28 +14,14 @@ fn main() -> Result<()> {
// 4) x (default: 1) 1-based
// 5) y (default: 1) 1-based line
// 6) text (default: "BLINK")
let mut args = std::env::args().skip(1);
let port_name = args.next().unwrap_or("/dev/cu.usbmodem101".into());
let width: usize = args.next().as_deref().unwrap_or("20").parse().unwrap_or(20);
let delay_ms: u64 = args
.next()
.as_deref()
.unwrap_or("400")
.parse()
.unwrap_or(400);
let x: u8 = args.next().as_deref().unwrap_or("1").parse().unwrap_or(1);
let y: u8 = args.next().as_deref().unwrap_or("1").parse().unwrap_or(1);
let text = args.next().unwrap_or("BLINK".into());
let mut args = common::ExampleArgs::from_env();
let delay_ms: u64 = args.parse_or(400);
let x: u8 = args.parse_or(1);
let y: u8 = args.parse_or(1);
let text = args.string_or("BLINK");
let text_len = text.chars().count();
let cfg = VfdConfig::new(port_name).with_width(width);
let worker = VfdWorker::start(cfg)?;
let worker = VfdWorker::start(args.config)?;
let vfd = worker.handle();
vfd.clear();
@@ -47,7 +31,7 @@ fn main() -> Result<()> {
let _ = vfd.print_at(1, y, ""); // просто чтобы “активировать” строку у некоторых дисплеев
}
let blank = spaces(text_len);
let blank = " ".repeat(text_len);
let mut on = false;
loop {
@@ -57,6 +41,6 @@ fn main() -> Result<()> {
let _ = vfd.print_at(x, y, &text);
}
on = !on;
std::thread::sleep(Duration::from_millis(delay_ms));
common::sleep_ms(delay_ms);
}
}
+8 -23
View File
@@ -1,7 +1,9 @@
//! Демонстрирует четыре уровня яркости и обновление строки без полного мерцания.
mod common;
use anyhow::Result;
use std::time::Duration;
use m::vfd::VfdConfig;
use m::worker::VfdWorker;
fn main() -> Result<()> {
@@ -9,26 +11,9 @@ fn main() -> Result<()> {
// 1) port (default: /dev/cu.usbmodem101)
// 2) width (default: 20)
// 3) delay_ms (default: 800)
let port_name = std::env::args()
.nth(1)
.unwrap_or("/dev/cu.usbmodem101".into());
let width: usize = std::env::args()
.nth(2)
.as_deref()
.unwrap_or("20")
.parse()
.unwrap_or(20);
let delay_ms: u64 = std::env::args()
.nth(3)
.as_deref()
.unwrap_or("800")
.parse()
.unwrap_or(800);
let cfg = VfdConfig::new(port_name).with_width(width);
let worker = VfdWorker::start(cfg)?;
let mut args = common::ExampleArgs::from_env();
let delay_ms: u64 = args.parse_or(800);
let worker = VfdWorker::start(args.config)?;
let vfd = worker.handle();
vfd.clear();
@@ -47,7 +32,7 @@ fn main() -> Result<()> {
let _ = vfd.print_line_diff(2, line2);
std::thread::sleep(Duration::from_millis(delay_ms));
common::sleep_ms(delay_ms);
}
}
}
+44 -44
View File
@@ -1,8 +1,11 @@
// examples/clock_ru.rs
//! Показывает локальные дату, время, день недели и часть суток с обновлением раз в секунду.
mod common;
use anyhow::Result;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use m::vfd::VfdConfig;
use m::vfd::fit_to_width;
use m::worker::VfdWorker;
fn run_date(args: &[&str]) -> Option<String> {
@@ -16,16 +19,19 @@ fn run_date(args: &[&str]) -> Option<String> {
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
fn local_datetime_ddmmyyyy_hhmmss() -> String {
// 28.01.2006 12:03:34
run_date(&["+%d.%m.%Y %H:%M:%S"]).unwrap_or_else(|| "??.??.???? ??:??:??".to_string())
fn parse_datetime_and_weekday(value: &str) -> Option<(String, u8)> {
let (datetime, weekday) = value.split_once('|')?;
let weekday = weekday.parse::<u8>().ok()?;
(1..=7)
.contains(&weekday)
.then(|| (datetime.to_string(), weekday))
}
fn local_weekday_num_1_7() -> u8 {
// 1..7 (Mon..Sun)
run_date(&["+%u"])
.and_then(|s| s.parse::<u8>().ok())
.unwrap_or(1)
fn local_datetime_and_weekday() -> (String, u8) {
run_date(&["+%d.%m.%Y %H:%M:%S|%u"])
.as_deref()
.and_then(parse_datetime_and_weekday)
.unwrap_or_else(|| ("??.??.???? ??:??:??".to_string(), 1))
}
fn weekday_ru_full(n: u8) -> &'static str {
@@ -74,51 +80,22 @@ fn extract_hour(datetime: &str) -> u8 {
.unwrap_or(12)
}
fn fit_to_width(s: &str, width: usize) -> String {
// простое “обрезать по символам”, чтобы кириллицу не порвать
let mut out = String::new();
for (i, ch) in s.chars().enumerate() {
if i >= width {
break;
}
out.push(ch);
}
out
}
fn main() -> Result<()> {
// args:
// 1) port (default: /dev/cu.usbmodem101)
// 2) width (default: 20)
// 3) brightness 1..4 (default: 2)
let port_name = std::env::args()
.nth(1)
.unwrap_or("/dev/cu.usbmodem101".into());
let width: usize = std::env::args()
.nth(2)
.as_deref()
.unwrap_or("20")
.parse()
.unwrap_or(20);
let brightness: u8 = std::env::args()
.nth(3)
.as_deref()
.unwrap_or("2")
.parse()
.unwrap_or(2);
let cfg = VfdConfig::new(port_name).with_width(width);
let worker = VfdWorker::start(cfg)?;
let mut args = common::ExampleArgs::from_env();
let brightness: u8 = args.parse_or(2);
let width = args.width;
let worker = VfdWorker::start(args.config)?;
let vfd = worker.handle();
vfd.clear();
vfd.set_brightness(brightness);
loop {
let dt = local_datetime_ddmmyyyy_hhmmss();
let wd = local_weekday_num_1_7();
let (dt, wd) = local_datetime_and_weekday();
let hour = extract_hour(&dt);
let tod = time_of_day_ru(hour);
@@ -148,3 +125,26 @@ fn main() -> Result<()> {
std::thread::sleep(Duration::from_millis(ms_to_next));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_date_and_weekday_from_one_process_result() {
assert_eq!(
parse_datetime_and_weekday("15.08.2026 23:59:58|6"),
Some(("15.08.2026 23:59:58".to_string(), 6))
);
assert_eq!(parse_datetime_and_weekday("invalid"), None);
assert_eq!(parse_datetime_and_weekday("15.08.2026 23:59:58|8"), None);
}
#[test]
fn classifies_hours_and_extracts_them_safely() {
assert_eq!(extract_hour("15.08.2026 05:00:00"), 5);
assert_eq!(extract_hour("invalid"), 12);
assert_eq!(time_of_day_ru(5), "утро");
assert_eq!(time_of_day_ru(23), "ночь");
}
}
+59
View File
@@ -0,0 +1,59 @@
//! Общие утилиты командной строки для всех демонстрационных программ.
#![allow(
dead_code,
reason = "модуль компилируется отдельно для каждого примера, поэтому часть API используется в соседних примерах"
)]
use m::vfd::VfdConfig;
use std::str::FromStr;
use std::vec::IntoIter;
const DEFAULT_PORT: &str = "/dev/cu.usbmodem101";
const DEFAULT_WIDTH: usize = 20;
/// Общие аргументы примера: serial-порт, ширина дисплея и оставшиеся параметры.
pub struct ExampleArgs {
pub config: VfdConfig,
pub width: usize,
remaining: IntoIter<String>,
}
impl ExampleArgs {
/// Читает общие аргументы из командной строки и применяет безопасные значения по умолчанию.
pub fn from_env() -> Self {
let mut values = std::env::args().skip(1);
let port_name = values.next().unwrap_or_else(|| DEFAULT_PORT.to_string());
let width = values
.next()
.and_then(|value| value.parse().ok())
.unwrap_or(DEFAULT_WIDTH);
let config = VfdConfig::new(port_name).with_width(width);
Self {
width: config.width,
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));
}
+10 -36
View File
@@ -1,7 +1,10 @@
//! Демонстрирует фоновую бегущую строку с регулируемой скоростью и паузой в конце.
mod common;
use anyhow::Result;
use std::time::Duration;
use m::vfd::VfdConfig;
use m::worker::VfdWorker;
fn main() -> Result<()> {
@@ -11,40 +14,11 @@ fn main() -> Result<()> {
// 3) cps (chars per second, default: 8)
// 4) end_pause_ms (default: 1500)
// 5) brightness 1..4 (default: 2)
let port_name = std::env::args()
.nth(1)
.unwrap_or("/dev/cu.usbmodem101".into());
let width: usize = std::env::args()
.nth(2)
.as_deref()
.unwrap_or("20")
.parse()
.unwrap_or(20);
let cps: u32 = std::env::args()
.nth(3)
.as_deref()
.unwrap_or("8")
.parse()
.unwrap_or(8);
let end_pause_ms: u64 = std::env::args()
.nth(4)
.as_deref()
.unwrap_or("1500")
.parse()
.unwrap_or(1500);
let brightness: u8 = std::env::args()
.nth(5)
.as_deref()
.unwrap_or("2")
.parse()
.unwrap_or(2);
let cfg = VfdConfig::new(port_name).with_width(width);
let worker = VfdWorker::start(cfg)?;
let mut args = common::ExampleArgs::from_env();
let cps: u32 = args.parse_or(8);
let end_pause_ms: u64 = args.parse_or(1500);
let brightness: u8 = args.parse_or(2);
let worker = VfdWorker::start(args.config)?;
let vfd = worker.handle();
vfd.clear();
@@ -61,6 +35,6 @@ fn main() -> Result<()> {
let _ = vfd.print_line_diff(1, "Бегущая строка");
loop {
std::thread::sleep(Duration::from_secs(3600));
common::sleep_ms(3_600_000);
}
}
+9 -23
View File
@@ -1,7 +1,9 @@
//! Демонстрирует позиционирование символа и его движение по второй строке дисплея.
mod common;
use anyhow::Result;
use std::time::Duration;
use m::vfd::VfdConfig;
use m::worker::VfdWorker;
fn main() -> Result<()> {
@@ -9,26 +11,10 @@ fn main() -> Result<()> {
// 1) port (default: /dev/cu.usbmodem101)
// 2) width (default: 20)
// 3) delay_ms (default: 80)
let port_name = std::env::args()
.nth(1)
.unwrap_or("/dev/cu.usbmodem101".into());
let width: usize = std::env::args()
.nth(2)
.as_deref()
.unwrap_or("20")
.parse()
.unwrap_or(20);
let delay_ms: u64 = std::env::args()
.nth(3)
.as_deref()
.unwrap_or("80")
.parse()
.unwrap_or(80);
let cfg = VfdConfig::new(port_name).with_width(width);
let worker = VfdWorker::start(cfg)?;
let mut args = common::ExampleArgs::from_env();
let delay_ms: u64 = args.parse_or(80);
let width = args.width;
let worker = VfdWorker::start(args.config)?;
let vfd = worker.handle();
vfd.clear();
@@ -69,6 +55,6 @@ fn main() -> Result<()> {
}
}
std::thread::sleep(Duration::from_millis(delay_ms));
common::sleep_ms(delay_ms);
}
}
+12 -23
View File
@@ -1,7 +1,9 @@
//! Имитирует компактный дашборд и обновляет только изменяемые поля через `print_at`.
mod common;
use anyhow::Result;
use std::time::Duration;
use m::vfd::VfdConfig;
use m::worker::VfdWorker;
fn pad_left(s: &str, width: usize) -> String {
@@ -18,26 +20,13 @@ fn main() -> Result<()> {
// 1) port (default: /dev/cu.usbmodem101)
// 2) width (default: 20)
// 3) delay_ms (default: 200)
let port_name = std::env::args()
.nth(1)
.unwrap_or("/dev/cu.usbmodem101".into());
let width: usize = std::env::args()
.nth(2)
.as_deref()
.unwrap_or("20")
.parse()
.unwrap_or(20);
let delay_ms: u64 = std::env::args()
.nth(3)
.as_deref()
.unwrap_or("200")
.parse()
.unwrap_or(200);
let cfg = VfdConfig::new(port_name).with_width(width);
let worker = VfdWorker::start(cfg)?;
let mut args = common::ExampleArgs::from_env();
let delay_ms: u64 = args.parse_or(200);
let width = args.width;
if width < 20 {
anyhow::bail!("update_at requires a display width of at least 20 columns");
}
let worker = VfdWorker::start(args.config)?;
let vfd = worker.handle();
vfd.clear();
@@ -103,6 +92,6 @@ fn main() -> Result<()> {
si = (si + 1) % spinner.len();
}
std::thread::sleep(Duration::from_millis(delay_ms));
common::sleep_ms(delay_ms);
}
}