Files
vfd/README.md
T

237 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VFD — управление дисплеями в Epson-совместимом режиме
Небольшая Rust-библиотека для двухстрочных VFD-дисплеев покупателя, работающих через
serial-порт в Epson/ESC-совместимом режиме. Проект ориентирован на дисплеи семейства
PD-2600/PD-2800 и похожие модели с поддержкой команд позиционирования, яркости и
таблицы символов CP866.
Библиотека подходит для часов, локальных дашбордов, кассовых приложений, уведомлений,
индикаторов состояния и других проектов, где данные нужно обновлять без мерцания всей
строки.
## Возможности
- вывод текста в первую или вторую строку;
- печать с координаты `(x, y)`;
- обновление только изменившихся диапазонов строки через `print_line_diff`;
- фоновая бегущая строка с регулируемой скоростью и паузой;
- четыре уровня яркости;
- кириллица через CP866;
- потокобезопасный интерфейс `VfdHandle`, который можно клонировать;
- автоматическая обрезка текста по правому краю и дополнение строк пробелами.
## Поддерживаемый протокол
При открытии устройства библиотека использует следующие параметры и команды:
| Параметр | Значение |
| --- | --- |
| Скорость 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 = "../vfd" }
anyhow = "1"
```
Имя crate сейчас — `m`, поэтому импорт начинается с `m::`.
## Быстрый старт
```rust,no_run
use anyhow::Result;
use m::vfd::VfdConfig;
use m::worker::VfdWorker;
use std::time::Duration;
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, игнорируются, чтобы два источника
не перезаписывали друг друга.
## Примеры
Все примеры принимают два общих аргумента:
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 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-символьную раскладку и завершится с понятной
ошибкой, если передать меньшую ширину.
Те же программы доступны через [Task](https://taskfile.dev/):
```bash
task vfd:clock
task vfd:marquee
task vfd:brightness
task vfd:position
task vfd:update_at
task vfd:blink
```
Значения можно переопределять переменными, например:
```bash
task vfd:marquee VFD_PORT=/dev/ttyUSB0 VFD_WIDTH=20 VFD_CPS=10
```
## Кодировка текста
Перед записью библиотека заменяет несколько типографских символов на совместимые
аналоги:
- `` → `.`;
- `` и `` → `-`;
- `` → `#`;
- типографские кавычки → обычные кавычки;
- табуляция → пробел.
Остальной текст кодируется с помощью `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-порт автоматически.