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

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
+197 -85
View File
@@ -1,124 +1,236 @@
# VFD — библиотека управления VFD-дисплеями (Epson mode)
# VFD — управление дисплеями в Epson-совместимом режиме
Небольшая Rust-библиотека для управления **VFD-дисплеями Epson/ESC-совместимого режима**
(например PD-2600 / PD-2800 и похожие).
Небольшая Rust-библиотека для двухстрочных VFD-дисплеев покупателя, работающих через
serial-порт в Epson/ESC-совместимом режиме. Проект ориентирован на дисплеи семейства
PD-2600/PD-2800 и похожие модели с поддержкой команд позиционирования, яркости и
таблицы символов CP866.
Подходит для:
- домашних IoT-проектов
- индикаторов состояния
- часов, дашбордов, уведомлений
- ретро-интерфейсов
Поддержка:
- печать текста по координатам
- обновление строк через diff (без мерцания)
- яркость дисплея
- программный marquee (бегущая строка)
- работа через serial (USB-UART)
---
Библиотека подходит для часов, локальных дашбордов, кассовых приложений, уведомлений,
индикаторов состояния и других проектов, где данные нужно обновлять без мерцания всей
строки.
## Возможности
- Двухстрочный дисплей (ширина настраивается)
- `print_line_diff` — обновление только изменённых символов
- Управление яркостью (14)
- Бегущая строка (marquee) в отдельном воркере
- Потокобезопасный API (worker + handle)
- вывод текста в первую или вторую строку;
- печать с координаты `(x, y)`;
- обновление только изменившихся диапазонов строки через `print_line_diff`;
- фоновая бегущая строка с регулируемой скоростью и паузой;
- четыре уровня яркости;
- кириллица через CP866;
- потокобезопасный интерфейс `VfdHandle`, который можно клонировать;
- автоматическая обрезка текста по правому краю и дополнение строк пробелами.
---
## Поддерживаемый протокол
## Установка
При открытии устройства библиотека использует следующие параметры и команды:
Добавьте библиотеку в `Cargo.toml`:
| Параметр | Значение |
| --- | --- |
| Скорость serial-порта | `9600 baud` |
| Таблица символов | CP866, команда `ESC t 6` |
| Инициализация | `ESC @` |
| Очистка | `FF` (`0x0C`) |
| Координаты | `US $ x y`, нумерация от 1 |
| Яркость | `US X n`, где `n = 1..4` |
| Число строк | 2 |
| Допустимая ширина | `1..=255` символов |
Перед запуском убедитесь, что дисплей переведён DIP-переключателями в совместимый
режим. Точные настройки конкретной модели смотрите в документации из каталога
[`docs`](docs/).
## Подключение
Для локального проекта добавьте библиотеку как path-зависимость:
```toml
[dependencies]
m = { path = "./path/to/vfd-lib" }
m = { path = "../vfd" }
anyhow = "1"
```
---
Имя crate сейчас — `m`, поэтому импорт начинается с `m::`.
## Быстрый старт
```rust
```rust,no_run
use anyhow::Result;
use m::vfd::VfdConfig;
use m::worker::VfdWorker;
let cfg = VfdConfig::new("/dev/cu.usbmodem101")
.with_width(20);
let worker = VfdWorker::start(cfg)?;
let vfd = worker.handle();
vfd.clear();
vfd.set_brightness(2);
vfd.print_line_diff(1, "Hello VFD!");
vfd.print_line_diff(2, "It works!");
```
---
## Бегущая строка (marquee)
```rust
use std::time::Duration;
vfd.set_marquee_text(" Это пример бегущей строки ");
vfd.start_marquee(
2, // строка
8, // символов в секунду
Duration::from_millis(1500), // пауза в конце
fn main() -> Result<()> {
let config = VfdConfig::new("/dev/cu.usbmodem101")
.with_width(20);
// Serial-порт открывается до запуска фонового потока,
// поэтому ошибка подключения вернётся из start().
let worker = VfdWorker::start(config)?;
let display = worker.handle();
display.clear();
display.set_brightness(2);
display.print_line_diff(1, "Привет!")?;
display.print_line_diff(2, "VFD готов")?;
// Команды выполняются асинхронно: не завершаем программу сразу после отправки.
std::thread::sleep(Duration::from_secs(2));
Ok(())
}
```
`VfdWorker` последовательно выполняет команды в отдельном потоке. При удалении worker
отправляет команду завершения и дожидается остановки потока. Сохраняйте `worker` в
области видимости, пока дисплей используется.
## Основной API
### Конфигурация
```rust,no_run
use m::vfd::VfdConfig;
use std::time::Duration;
let config = VfdConfig::new("/dev/ttyUSB0")
.with_width(20)
.with_timeout(Duration::from_millis(250));
```
Ширина автоматически ограничивается диапазоном `1..=255`, потому что координаты
протокола передаются одним байтом.
### Полная и дифференциальная запись
```rust,ignore
display.print_line(1, "Полная перезапись")?;
display.print_line_diff(2, "Изменились цифры: 42")?;
```
`print_line_diff` хранит последний кадр каждой строки и группирует соседние изменения.
Если текст не изменился, serial-команда не отправляется. Это полезно для часов и
дашбордов, которые обновляются часто.
### Печать по координатам
```rust,ignore
display.print_at(7, 1, "21.50")?;
display.print_at(16, 1, "1200")?;
```
Координаты начинаются с единицы. Допустимы строки `1` и `2`; текст, выходящий за правую
границу, обрезается по символам без повреждения UTF-8.
### Бегущая строка
```rust,ignore
use std::time::Duration;
display.set_marquee_text("Длинное сообщение для посетителя");
display.start_marquee(
2, // строка
8, // символов в секунду
Duration::from_millis(1500), // пауза после полного прохода
);
// При необходимости:
display.stop_marquee();
```
Marquee работает в фоне и не мешает `print_line_diff`,
если не писать в ту же строку.
---
## Управление яркостью
```rust
vfd.set_brightness(1); // диапазон 1..4
```
Яркость можно менять динамически (ночной режим, «дыхание» и т.п.).
---
Обычные команды записи в строку, занятую marquee, игнорируются, чтобы два источника
не перезаписывали друг друга.
## Примеры
Запуск через `cargo run --example` или `task`:
Все примеры принимают два общих аргумента:
1. serial-порт, по умолчанию `/dev/cu.usbmodem101`;
2. ширина дисплея, по умолчанию `20`.
Дополнительные аргументы зависят от примера:
| Пример | Что демонстрирует | Дополнительные аргументы |
| --- | --- | --- |
| `clock` | Дату, время, день недели и часть суток | `[brightness=2]` |
| `marquee` | Фоновую бегущую строку | `[cps=8] [pause_ms=1500] [brightness=2]` |
| `brightness` | Перебор уровней яркости `1..=4` | `[delay_ms=800]` |
| `position` | Движение символа по второй строке | `[delay_ms=80]` |
| `update_at` | Частичное обновление полей дашборда | `[delay_ms=200]` |
| `blink` | Мигание текста в заданной позиции | `[delay_ms=400] [x=1] [y=1] [text=BLINK]` |
Примеры запуска:
```bash
cargo run --example clock -- /dev/cu.usbmodem101 20 8
cargo run --example clock -- /dev/cu.usbmodem101 20 2
cargo run --example marquee -- /dev/cu.usbmodem101 20 8 1500 2
cargo run --example brightness -- /dev/cu.usbmodem101 20 800
cargo run --example position -- /dev/cu.usbmodem101 20 80
cargo run --example update_at -- /dev/cu.usbmodem101 20 200
cargo run --example blink -- /dev/cu.usbmodem101 20 400 1 2 BLINK
```
Примеры:
`update_at` использует фиксированную 20-символьную раскладку и завершится с понятной
ошибкой, если передать меньшую ширину.
- `clock` — часы
- `marquee` — бегущая строка
- `brightness` — демонстрация яркости
Те же программы доступны через [Task](https://taskfile.dev/):
---
```bash
task vfd:clock
task vfd:marquee
task vfd:brightness
task vfd:position
task vfd:update_at
task vfd:blink
```
## Требования
Значения можно переопределять переменными, например:
- Дисплей в **Epson-режиме** (DIP-переключатели)
- Скорость: 9600 baud
- Кодировка: CP866 (кириллица)
```bash
task vfd:marquee VFD_PORT=/dev/ttyUSB0 VFD_WIDTH=20 VFD_CPS=10
```
---
## Кодировка текста
## Идеи для использования
Перед записью библиотека заменяет несколько типографских символов на совместимые
аналоги:
- часы / дата / температура
- MQTT-дашборд
- уведомления умного дома
- статус сервиса / сборки
- ретро-индикатор
- `` → `.`;
- `` и `` → `-`;
- `` → `#`;
- типографские кавычки → обычные кавычки;
- табуляция → пробел.
Остальной текст кодируется с помощью `encoding_rs::IBM866`.
## Структура проекта
```text
src/vfd.rs низкоуровневые команды и CP866
src/worker.rs очередь команд, diff и marquee
examples/common/mod.rs общие CLI-утилиты примеров
examples/*.rs демонстрационные программы
docs/ руководства для дисплеев
taskfile.yml команды запуска примеров
```
## Разработка и проверка
```bash
cargo fmt --all -- --check
cargo clippy --all-targets --all-features --locked -- -D warnings
cargo test --all-targets --locked
cargo check --examples --locked
```
Автоматические тесты проверяют Unicode, CP866-санацию, ширину строк, обновление кэша,
группировку diff-диапазонов, таймер marquee и функции примера часов. Для полной проверки
нужен отдельный smoke-test с физическим дисплеем и фактическим serial-портом.
## Ограничения
- поддерживаются только две строки;
- скорость подключения фиксирована на `9600 baud`;
- очередь команд в worker не ограничена по размеру;
- аппаратные ошибки после запуска worker выводятся в stderr из фонового потока;
- пример `clock` использует системную команду `date` и рассчитан на Unix-подобную среду;
- библиотека не определяет serial-порт автоматически.