Estrella: сервер для чеков на термопринтере Rust

Пошаговое руководство по установке и настройке Estrella — self-hosted сервера на Rust для управления термопринтером чеков.

Не указано
Алексей Кузнецов
Алексей Кузнецов
Системный администратор6 февраля 2026 г.

Установка Rust

Установите Rust с помощью rustup, если он не установлен. После установки перезапустите терминал и проверьте версию Rust и Cargo.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustc --version
cargo --version

Клонирование репозитория Estrella

Создайте рабочую директорию и клонируйте репозиторий проекта с GitHub.

git clone https://github.com/example/estrella.git
cd estrella

Установка зависимостей

Установите необходимые системные зависимости для вашей операционной системы. Для Ubuntu/Debian это build-essential, libudev-dev, libssl-dev и pkg-config.

sudo apt-get update
sudo apt-get install build-essential libudev-dev libssl-dev pkg-config

Сборка проекта

Соберите проект с помощью Cargo в release-режиме для оптимальной производительности. Бинарный файл будет находиться в target/release/estrella.

cargo build --release
./target/release/estrella --help

Создание конфигурационного файла

Создайте файл config.toml с настройками сервера, аутентификации, принтера, логирования и очереди печати.

[server]
host = "0.0.0.0"
port = 8080
workers = 4

[printer]
type = "usb"
path = "/dev/usb/lp0"
width = 48
encoding = "cp866"
model = "epson"
autocut = true

[auth]
enabled = true
method = "bearer"
api_key = "your-secret-api-key"

Настройка прав доступа к USB-принтеру

Для Linux добавьте пользователя в группы lp и dialout для доступа к USB-устройству принтера.

sudo usermod -a -G lp,dialout $USER
ls -l /dev/usb/lp*

Запуск сервера

Запустите сервер с указанием конфигурационного файла. По умолчанию сервер будет доступен на порту 8080.

./target/release/estrella --config config.toml

Проверка работы сервера

Проверьте статус принтера и отправьте тестовый запрос на печать через API.

curl -X GET http://localhost:8080/status

curl -X POST http://localhost:8080/print \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{"text": "Тестовый чек", "cut": true}'

Настройка systemd сервиса (опционально)

Для автоматического запуска сервера создайте systemd unit файл и включите сервис.

[Unit]
Description=Estrella Thermal Printer Server
After=network.target

[Service]
Type=simple
User=estrella
WorkingDirectory=/opt/estrella
ExecStart=/opt/estrella/target/release/estrella --config /etc/estrella/config.toml
Restart=always

[Install]
WantedBy=multi-user.target

Запуск с Docker (опционально)

Альтернативно можно запустить сервер в Docker контейнере с пробросом USB-устройства и конфигурации.

docker run -d \
  --name estrella \
  -p 8080:8080 \
  --device=/dev/usb/lp0 \
  -v /path/to/config.toml:/etc/estrella/config.toml \
  ghcr.io/example/estrella:latest

Rust-сервер Estrella для термопринтера чеков: полное руководство по установке и настройке

Введение

Estrella — это высокопроизводительный сервер, написанный на Rust, предназначенный для управления термопринтерами чеков. Он обеспечивает быструю и надежную печать чеков с поддержкой различных протоколов подключения. В этом руководстве мы рассмотрим процесс установки, настройки, использования и интеграции сервера Estrella с различными системами.

Этот сервер особенно востребован в торговых точках, ресторанах и других бизнесах, где требуется быстрая и надежная печать чеков. Благодаря высокому уровню абстракции, Estrella поддерживает множество моделей принтеров и предоставляет удобный API для интеграции с POS-системами. Использование Rust в качестве языка разработки обеспечивает безопасность памяти, производительность и надежность, что критически важно для систем, работающих с оборудованием в реальном времени.

Требования системы

Перед установкой убедитесь, что ваша система соответствует следующим требованиям:

  • Операционная система: Linux (Ubuntu 18.04+), macOS 10.14+ или Windows 10+
  • Rust 1.65 или выше
  • Термопринтер, поддерживаемый через ESC/POS, CUPS или сетевой интерфейс
    • Поддерживаемые модели: Epson TM-T20II, Star TSP100, CITIZEN CT-S651, ZJ-58, BIXOLON SRP-350plus, Honeywell V600, Godex EZ-3200, NCR RealPOS 80
    • Для принтеров с USB-интерфейсом
    • Для сетевых принтеров (Ethernet, Wi-Fi)
  • USB-порт (для локального подключения) или доступная сеть (для сетевых принтеров)
  • Для Linux: права доступа к устройствам (/dev/usb/lp*)
  • Для Windows: доступ к принтеру через API (установлены драйверы принтера)
  • Для macOS: доступ через CUPS (Common Unix Printing System)

Установка

Шаг 1: Установка Rust

Если у вас не установлен Rust, установите его с помощью rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

После установки перезапустите терминал или выполните:

source $HOME/.cargo/env

Проверьте версию Rust:

rustc --version

Убедитесь, что установлен Cargo - менеджер пакетов Rust:

cargo --version

Шаг 2: Клонирование репозитория Estrella

Создайте рабочую директорию и клонируйте репозиторий:

git clone https://github.com/example/estrella.git
cd estrella

Шаг 3: Установка зависимостей

Установите необходимые зависимости для вашей операционной системы:

Для Ubuntu/Debian:

sudo apt-get update
sudo apt-get install build-essential libudev-dev libssl-dev pkg-config

Для macOS:

brew install pkg-config openssl

Для Windows: Установите Visual Studio Build Tools и OpenSSL.

Шаг 4: Сборка проекта

Соберите проект с помощью Cargo:

cargo build --release

После сборки бинарный файл будет находиться в target/release/estrella.

Шаг 5: Проверка установки

Проверьте, что бинарный файл был создан корректно:

./target/release/estrella --help

Если вы видите справку по командам, установка прошла успешно.

Внутренние механизмы работы Estrella

Estrella построен на основе асинхронного рантайма Tokio, что обеспечивает высокую производительность и эффективное использование системных ресурсов. Сервер использует неблокирующие I/O операции для обработки множества одновременных подключений.

Архитектура сервера

  1. HTTP-сервер: На базе библиотеки Hyper, обеспечивает обработку входящих HTTP-запросов с поддержкой HTTP/2.
  2. Принтерный драйвер: Абстрактный слой, поддерживающий различные типы принтеров (USB, сеть, COM-порт) с поддержкой горячей замены.
  3. Кэширование: Внутренний кэш для часто используемых конфигураций и шаблонов на базе библиотеки DashMap.
  4. Обработка ошибок: Система обработки ошибок с детальным логированием и автоматическим восстановлением после сбоев.
  5. Очередь задач: Неблокирующая очередь для обработки запросов печати, обеспечивающая доставку сообщений даже при временной недоступности принтера.
  6. Пул соединений: Пул для эффективного управления соединениями с сетевыми принтерами.

Поддерживаемые протоколы

  • ESC/POS: Протокол Epson для управления термопринтерами, включая расширенные команды.
  • CPCL: Протокол Citizen для термопринтеров с поддержкой графики.
  • StarPRNT: Протокол Star Micronics с поддержкой современных функций.
  • ZPL: Протокол Zebra для принтеров этикеток.
  • TSPL: Протокол TSC для этикеточных принтеров.
  • EPL2: Протокол Zebra для старых моделей принтеров.

Потоки обработки запросов

  1. Приходит HTTP-запрос на печать чека.
  2. Сервер валидирует запрос и проверяет аутентификацию.
  3. Запрос помещается в очередь обработки с приоритизацией.
  4. Модуль форматирования преобразует данные в команды принтера.
  5. Пул драйверов выбирает доступный принтер для обработки запроса.
  6. Команда отправляется на принтер через соответствующий драйвер.
  7. Сервер ожидает ответа от принтера с настраиваемым таймаутом.
  8. После успешной обработки ответ формируется клиенту.

Производительность и оптимизация

Estrella спроектирован для высокой производительности:

  • Асинхронная обработка запросов с минимальными задержками.
  • Эффективное управление памятью за счет системы владения Rust.
  • Оптимизированный парсинг данных для минимального потребления CPU.
  • Кэширование шаблонов чеков для повторяющихся сценариев.
  • Пул соединений с сетевыми принтерами для снижения накладных расходов.

Бенчмарки показывают, что Estrella способен обрабатывать до 200 запросов в секунду на стандартном железе с задержкой менее 50 мс.

Настройка

Шаг 1: Конфигурационный файл

Создайте конфигурационный файл config.toml в директории проекта:

[server]
host = "0.0.0.0"
port = 8080
workers = 4  # количество worker-потоков
timeout = 30  # таймаут в секундах
keep_alive = 75  # время поддержания соединения в секундах
max_connections = 1000  # максимальное количество одновременных соединений

[auth]
enabled = true
method = "bearer"  # bearer, basic, api_key
api_key = "your-secret-api-key"
token_expiry = 3600  # срок действия токена в секундах
refresh_token = true  # включить рефреш токены

[security]
enable_cors = true
allowed_origins = ["http://localhost:3000", "https://yourdomain.com"]
max_request_size = "10 MB"
enable_rate_limit = true
rate_limit = 1000  # запросов в минуту на один IP
ssl = false
ssl_cert = "/path/to/cert.pem"
ssl_key = "/path/to/key.pem"
ip_whitelist = ["192.168.1.0/24", "10.0.0.0/8"]

[printer]
type = "usb"  # или "network", "serial", "bluetooth"
path = "/dev/usb/lp0"  # для USB или сетевой адрес
width = 48  # ширина чека в символах
encoding = "cp866"  # кодировка
model = "epson"  # epson, star, citizen, zebra, bixolon, honeywell, godex
density = 15  # плотность печати (0-15)
speed = 4  # скорость печати (1-5)
thermal_mode = true  # термический режим
cut_after = 3  # количество чеков после которых отрезать
paper_sensor = true  # использование датчика бумаги
autocut = true  # автоматическая обрезка

[printer.barcode]
type = "CODE128"  # CODE128, EAN13, UPC-A, QR-Code, DataMatrix, PDF417
width = 2  # ширина штрих-кода
height = 100  # высота штрих-кода
text_position = "below"  # below, above, none
barcode_width = 2
barcode_height = 100

[printer.qr]
size = 10  # размер QR-кода (1-16)
error_correction = "M"  # L, M, Q, H
margin = 4  # отступы в точках
model = "2"  # модель QR-кода (1-2)

[printer.image]
max_width = 512  # максимальная ширина изображения в пикселях
max_height = 1024  # максимальная высота изображения в пикселях
dithering = true  # дизеринг для черно-белых изображений

[logging]
level = "info"  # trace, debug, info, warn, error
file = "estrella.log"
max_size = "100 MB"
backups = 5
enable_json = false  # форматировать логи в JSON
enable_syslog = false  # отправлять логи в syslog

[metrics]
enabled = true
path = "/metrics"  # эндпоинт для метрик Prometheus
interval = 30  # интервал сбора метрик в секундах

[cache]
enabled = true
max_size = 1000  # максимальное количество элементов в кэше
ttl = 3600  # время жизни кэша в секундах

[queue]
enabled = true
max_size = 1000  # максимальное количество элементов в очереди
retry_attempts = 3  # количество попыток повторной отправки
retry_delay = 5  # задержка между попытками в секундах
persistent = true  # сохранять очередь между перезапусками (requires SQLite)

[persistent_queue]
path = "/var/lib/estrella/queue.db"
max_size = "1 GB"  # максимальный размер базы данных
checkpoint_interval = 100  # количество операций между точками сохранения

Шаг 2: Настройка аутентификации

Для включения аутентификации установите [auth].enabled = true в конфигурационном файле. Estrella поддерживает три метода аутентификации:

  1. Bearer токен: Включите method = "bearer" и укажите api_key.
  2. HTTP Basic: Включите method = "basic" и укажите логин и пароль.
  3. API ключ: Включите method = "api_key" и укажите api_key.

Пример запроса с аутентификацией:

curl -X POST http://localhost:8080/print \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "text": "Пример текста для печати"
  }'

Для получения токена используйте эндпоинт /auth:

curl -X POST http://localhost:8080/auth \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "your-secret-api-key"
  }'

Шаг 3: Настройка безопасности

Шифрование соединений

Для настройки HTTPS:

[server]
port = 8443
ssl = true
ssl_cert = "/path/to/fullchain.pem"
ssl_key = "/path/to/privkey.pem

Защита от атак

Estrella включает встроенные механизмы защиты:

  1. Защита от DDoS: Ограничение количества запросов с одного IP-адреса.
  2. Защита от брутфорса: Ограничение попыток аутентификации.
  3. CORS: Ограничение доменов, с которых разрешены запросы.
  4. Ограничение размера запросов: Предотвращение атак типа "загрузка данных".

Настройка IP-белого списка

[security]
ip_whitelist = ["192.168.1.0/24", "10.0.0.0/8", "127.0.0.1"]
ip_blacklist = ["192.168.2.100"]  # конкретные IP-адреса, которые будут заблокированы

Настройка CORS

[security]
enable_cors = true
allowed_origins = ["http://localhost:3000", "https://yourdomain.com"]
allowed_methods = ["GET", "POST", "PUT", "DELETE"]
allowed_headers = ["Content-Type", "Authorization"]

Шаг 4: Настройка принтера

Для USB-принтера:

Убедитесь, что ваш пользователь имеет доступ к USB-устройству:

sudo usermod -a -G lp $USER
sudo usermod -a -G dialout $USER

Перезагрузите систему или выйдите и войдите снова, чтобы изменения вступили в силу.

Для определения правильного пути к принтеру используйте команду:

ls -l /dev/usb/lp*

Для принтеров, которые определяются как serial устройства:

ls -l /dev/ttyUSB*
ls -l /dev/ttyACM*

Для сетевого принтера:

Укажите IP-адрес принтера в параметре path конфигурационного файла:

[printer]
type = "network"
path = "192.168.1.100:9100"

Для сетевых принтеров также можно указать таймаут подключения:

[printer.network]
timeout = 10  # таймаут в секундах
retries = 3  # количество попыток подключения
connection_pool_size = 5  # размер пула соединений

Для принтера через порт COM (Serial):

[printer]
type = "serial"
path = "/dev/ttyS0"
baud_rate = 9600
data_bits = 8
parity = "none"
stop_bits = 1
flow_control = "none"

Для Bluetooth-принтера:

[printer]
type = "bluetooth"
mac_address = "00:11:22:33:44:55"
channel = 1
timeout = 30

Шаг 5: Настройка мониторинга

Настройка метрик

Для сбора метрик Prometheus:

[metrics]
enabled = true
path = "/metrics"
interval = 15
include_labels = ["printer", "request_type"]

Настройка логирования

Для продвинутого логирования:

[logging]
level = "info"
file = "/var/log/estrella/estrella.log"
max_size = "100 MB"
backups = 5
enable_json = true
enable_syslog = true
syslog_facility = "local0"

Использование сервера

Запуск сервера

Запустите сервер с помощью следующей команды:

./target/release/estrella --config config.toml

Для запуска в режиме разработки с перезагрузкой при изменениях:

cargo watch -x "run -- --config config.toml"

По умолчанию сервер будет доступен на порту 8080. Для запуска в качестве systemd сервиса создайте файл /etc/systemd/system/estrella.service:

[Unit]
Description=Estrella Thermal Printer Server
After=network.target

[Service]
Type=simple
User=estrella
WorkingDirectory=/opt/estrella
ExecStart=/opt/estrella/target/release/estrella --config /etc/estrella/config.toml
Restart=always
RestartSec=5
Environment=RUST_LOG=info

[Install]
WantedBy=multi-user.target

Затем включите и запустите сервис:

sudo systemctl daemon-reload
sudo systemctl enable estrella
sudo systemctl start estrella

Для запуска с Docker:

docker run -d \
  --name estrella \
  -p 8080:8080 \
  -v /path/to/config.toml:/etc/estrella/config.toml \
  -v /var/log/estrella:/var/log/estrella \
  ghcr.io/example/estrella:latest

Мониторинг и логирование

Estrella предоставляет подробное логирование и метрики для мониторинга работы сервера.

Логи сохраняются в указанный в конфигурационном файле. Для просмотра логов в реальном времени используйте:

tail -f estrella.log

Для просмотра системных логов:

journalctl -u estrella -f

Для мониторинга метрик используйте эндпоинт /metrics (если включен в конфигурации):

curl http://localhost:8080/metrics

API-интерфейс

Estrella предоставляет REST API для управления печатью. Все эндпоинты защищены аутентификацией, если она включена в настройках.

Аутентификация

curl -X POST http://localhost:8080/auth \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "your-secret-api-key"
  }'

Ответ:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600
}

Печать чека

curl -X POST http://localhost:8080/print \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-access-token" \
  -d '{
    "text": "Пример текста для печати",
    "cut": true,
    "beep": true,
    "barcode": "123456789012",
    "qr_code": "https://example.com",
    "encoding": "cp866",
    "width": 48,
    "align": "left"
  }'

Печать изображения

curl -X POST http://localhost:8080/print-image \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-access-token" \
  -d '{
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAA...",
    "width": 300,
    "alignment": "center",
    "dithering": true
  }'

Печать таблицы

curl -X POST http://localhost:8080/print-table \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-access-token" \
  -d '{
    "headers": ["Товар", "Кол-во", "Цена"],
    "rows": [
      ["Товар 1", "2", "100.00"],
      ["Товар 2", "1", "200.00"]
    ],
    "alignment": ["left", "center", "right"],
    "borders": true,
    "spacing": 1
  }'

Получение статуса принтера

curl -X GET http://localhost:8080/status \
  -H "Authorization: Bearer your-access-token"

Ответ:

{
  "status": "ready",
  "temperature": 42.5,
  "paper_status": "present",
  "error_code": 0,
  "model": "epson-tm-t20ii",
  "serial_number": "12345678"
}

Отправка команды на принтер

curl -X POST http://localhost:8080/command \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-access-token" \
  -d '{
    "command": "\\x1B\\x40",  # ESC @ - сброс принтера
    "raw": true
  }'

Получение списка поддерживаемых принтеров

curl -X GET http://localhost:8080/printers \
  -H "Authorization: Bearer your-access-token"

Управление очередью печати

# Получение статуса очереди
curl -X GET http://localhost:8080/queue/status \
  -H "Authorization: Bearer your-access-token"

# Очистка очереди
curl -X POST http://localhost:8080/queue/clear \
  -H "Authorization: Bearer your-access-token"

# Приостановка обработки очереди
curl -X POST http://localhost:8080/queue/pause \
  -H "Authorization: Bearer your-access-token"

# Возобновление обработки очереди
curl -X POST http://localhost:8080/queue/resume \
  -H "Authorization: Bearer your-access-token"

Интеграция с POS-системами

Обзор интеграции

Estrella предоставляет гибкие возможности для интеграции с различными POS-системами. В этом разделе рассмотрим примеры интеграции на разных языках программирования.

Интеграция с Python

Установите необходимые библиотеки:

pip install requests aiohttp

Пример интеграции:

import requests
import json
import asyncio
import aiohttp

class EstrellaClient:
    def __init__(self, base_url, api_key):
        self.base_url = base_url
        self.api_key = api_key
        self.access_token = None
        self.token_expiry = 0
    
    def get_headers(self):
        if self.access_token:
            return {
                "Content-Type": "application/json",
                "Authorization": f"Bearer {self.access_token}"
            }
        return {"Content-Type": "application/json"}
    
    def authenticate(self):
        """Получение токена аутентификации"""
        url = f"{self.base_url}/auth"
        payload = {"api_key": self.api_key}
        
        response = requests.post(url, json=payload)
        response.raise_for_status()
        
        data = response.json()
        self.access_token = data["access_token"]
        self.token_expiry = data["expires_in"]
        return self.access_token
    
    def ensure_authenticated(self):
        """Проверка и обновление токена при необходимости"""
        current_time = time.time()
        if not self.access_token or current_time > self.token_expiry:
            self.authenticate()
    
    def print_receipt(self, text, cut=True, beep=True, barcode=None, qr_code=None, **kwargs):
        """Печать чека"""
        self.ensure_authenticated()
        
        url = f"{self.base_url}/print"
        payload = {
            "text": text,
            "cut": cut,
            "beep": beep,
            **kwargs
        }
        
        if barcode:
            payload["barcode"] = barcode
        if qr_code:
            payload["qr_code"] = qr_code
        
        response = requests.post(url, headers=self.get_headers(), json=payload)
        response.raise_for_status()
        return response.json()
    
    def print_image(self, image_path=None, image_base64=None, **kwargs):
        """Печать изображения"""
        self.ensure_authenticated()
        
        url = f"{self.base_url}/print-image"
        payload = kwargs
        
        if image_path:
            with open(image_path, 'rb') as f:
                image_data = f.read()
                payload["image_base64"] = base64.b64encode(image_data).decode('utf-8')
        elif image_base64:
            payload["image_base64"] = image_base64
        
        response = requests.post(url, headers=self.get_headers(), json=payload)
        response.raise_for_status()
        return response.json()
    
    def print_table(self, headers, rows, **kwargs):
        """Печать таблицы"""
        self.ensure_authenticated()
        
        url = f"{self.base_url}/print-table"
        payload = {
            "headers": headers,
            "rows": rows,
            **kwargs
        }
        
        response = requests.post(url, headers=self.get_headers(), json=payload)
        response.raise_for_status()
        return response.json()
    
    def get_status(self):
        """Получение статуса принтера"""
        self.ensure_authenticated()
        
        url = f"{self.base_url}/status"
        response = requests.get(url, headers=self.get_headers())
        response.raise_for_status()
        return response.json()

# Асинхронный клиент
class AsyncEstrellaClient:
    def __init__(self, base_url, api_key):
        self.base_url = base_url
        self.api_key = api_key
        self.access_token = None
        self.token_expiry = 0
        self.session = None
    
    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        return self
    
    async def __aexit__(self, exc_type, exc, tb):
        if self.session:
            await self.session.close()
    
    async def authenticate(self):
        """Получение токена аутентификации"""
        url = f"{self.base_url}/auth"
        payload = {"api_key": self.api_key}
        
        async with self.session.post(url, json=payload) as response:
            response.raise_for_status()
            
            data = await response.json()
            self.access_token = data["access_token"]
            self.token_expiry = data["expires_in"]
            return self.access_token
    
    async def ensure_authenticated(self):
        """Проверка и обновление токена при необходимости"""
        current_time = time.time()
        if not self.access_token or current_time > self.token_expiry:
            await self.authenticate()
    
    async def print_receipt(self, text, cut=True, beep=True, barcode=None, qr_code=None, **kwargs):
        """Асинхронная печать чека"""
        await self.ensure_authenticated()
        
        url = f"{self.base_url}/print"
        payload = {
            "text": text,
            "cut": cut,
            "beep": beep,
            **kwargs
        }
        
        if barcode:
            payload["barcode"] = barcode
        if qr_code:
            payload["qr_code"] = qr_code
        
        async with self.session.post(url, headers=self.get_headers(), json=payload) as response:
            response.raise_for_status()
            return await response.json()
    
    def get_headers(self):
        if self.access_token:
            return {
                "Content-Type": "application/json",
                "Authorization": f"Bearer {self.access_token}"
            }
        return {"Content-Type": "application/json"}

# Пример использования
client = EstrellaClient("http://localhost:8080", "your-api-key")

receipt_text = """
КАССОВЫЙ ЧЕК
=====================
Товар 1: 100.00 руб.
Товар 2: 200.00 руб.
---------------------
ИТОГО: 300.00 руб.
=====================
Спасибо за покупку!
"""

client.print_receipt(
    text=receipt_text,
    barcode="123456789012",
    qr_code="https://example.com/receipt/123"
)

# Получение статуса принтера
status = client.get_status()
print("Статус принтера:", status)

# Асинхронное использование
async def async_example():
    async with AsyncEstrellaClient("http://localhost:8080", "your-api-key") as client:
        await client.print_receipt(
            text="Асинхронный чек",
            cut=True
        )

asyncio.run(async_example())

Интеграция с Node.js

Установите необходимые пакеты:

npm install axios axios-retry

Пример интеграции:

const axios = require('axios');
const axiosRetry = require('axios-retry');

class EstrellaClient {
    constructor(baseUrl, apiKey) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
        this.accessToken = null;
        this.tokenExpiry = 0;
        
        const axiosInstance = axios.create();
        axiosRetry(axiosInstance, {
            retries: 3,
            retryDelay: (retryCount) => {
                console.log(`Попытка ${retryCount}`);
                return 1000;
            }
        });
        this.axios = axiosInstance;
    }
    
    get headers() {
        return {
            'Content-Type': 'application/json',
            ...(this.accessToken && { 'Authorization': `Bearer ${this.accessToken}` })
        };
    }
    
    async authenticate() {
        const response = await this.axios.post(`${this.baseUrl}/auth`, {
            api_key: this.apiKey
        });
        
        const { access_token, expires_in } = response.data;
        this.accessToken = access_token;
        this.tokenExpiry = Date.now() + (expires_in * 1000);
        return access_token;
    }
    
    async ensureAuthenticated() {
        if (!this.accessToken || Date.now() > this.tokenExpiry) {
            await this.authenticate();
        }
    }
    
    async printReceipt(text, options = {}) {
        await this.ensureAuthenticated();
        
        const payload = {
            text,
            cut: true,
            beep: true,
            ...options
        };
        
        try {
            const response = await this.axios.post(`${this.baseUrl}/print`, payload, {
                headers: this.headers
            });
            return response.data;
        } catch (error) {
            console.error('Ошибка печати:', error.response?.data || error.message);
            throw error;
        }
    }
    
    async printImage(imageBase64, options = {}) {
        await this.ensureAuthenticated();
        
        const payload = {
            image_base64: imageBase64,
            ...options
        };
        
        try {
            const response = await this.axios.post(`${this.baseUrl}/print-image`, payload, {
                headers: this.headers
            });
            return response.data;
        } catch (error) {
            console.error('Ошибка печати изображения:', error.response?.data || error.message);
            throw error;
        }
    }
    
    async getStatus() {
        await this.ensureAuthenticated();
        
        try {
            const response = await this.axios.get(`${this.baseUrl}/status`, {
                headers: this.headers
            });
            return response.data;
        } catch (error) {
            console.error('Ошибка получения статуса:', error.response?.data || error.message);
            throw error;
        }
    }
    
    async addToQueue(data, options = {}) {
        await this.ensureAuthenticated();
        
        try {
            const response = await this.axios.post(`${this.baseUrl}/queue/add`, {
                data,
                ...options
            }, {
                headers: this.headers
            });
            return response.data;
        } catch (error) {
            console.error('Ошибка добавления в очередь:', error.response?.data || error.message);
            throw error;
        }
    }
}

// Пример использования
const client = new EstrellaClient('http://localhost:8080', 'your-api-key');

const receiptText = `
КАССОВЫЙ ЧЕК
=====================
Товар 1: 100.00 руб.
Товар 2: 200.00 руб.
---------------------
ИТОГО: 300.00 руб.
=====================
Спасибо за покупку!
`;

client.printReceipt(receiptText, {
    barcode: '123456789012',
    qr_code: 'https://example.com/receipt/123'
})
.then(() => console.log('Чек успешно отправлен на печать'))
.catch(err => console.error('Ошибка:', err));

// Получение статуса принтера
client.getStatus()
.then(status => console.log('Статус принтера:', status))
.catch(err => console.error('Ошибка:', err));

// Пример с очередью
async function printWithQueue() {
    try {
        const queueId = await client.addToQueue({
            type: 'receipt',
            text: 'Чек из очереди'
        });
        
        console.log('Задача добавлена в очередь с ID:', queueId);
        
        // Проверка статуса задачи
        setTimeout(() => {
            client.getStatus()
            .then(status => {
                console.log('Статус принтера после добавления в очередь:', status);
            });
        }, 5000);
    } catch (error) {
        console.error('Ошибка:', error);
    }
}

printWithQueue();

Интеграция с Java

Сначала добавьте зависимость в ваш проект (Maven):

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.1</version>
</dependency>

Пример интеграции:

import okhttp3.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.Base64;
import java.util.concurrent.TimeUnit;

public class EstrellaClient {
    private final OkHttpClient client;
    private final String baseUrl;
    private final String apiKey;
    private String accessToken;
    private long tokenExpiry;
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    public EstrellaClient(String baseUrl, String apiKey) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
        this.client = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(new RetryInterceptor(3))
            .build();
    }
    
    public void authenticate() throws IOException {
        RequestBody body = RequestBody.create(
            MediaType.parse("application/json"),
            "{\"api_key\":\"" + apiKey + "\"}"
        );
        
        Request request = new Request.Builder()
            .url(baseUrl + "/auth")
            .post(body)
            .build();
        
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response + ", body: " + response.body().string());
            }
            
            String json = response.body().string();
            AuthResponse authResponse = objectMapper.readValue(json, AuthResponse.class);
            
            this.accessToken = authResponse.getAccessToken();
            this.tokenExpiry = System.currentTimeMillis() + (authResponse.getExpiresIn() * 1000L);
        }
    }
    
    public void ensureAuthenticated() throws IOException {
        if (accessToken == null || System.currentTimeMillis() > tokenExpiry) {
            authenticate();
        }
    }
    
    public PrintResponse printReceipt(String text, PrintOptions options) throws IOException {
        ensureAuthenticated();
        
        PrintRequest request = new PrintRequest();
        request.setText(text);
        request.setCut(options.isCut());
        request.setBeep(options.isBeep());
        request.setBarcode(options.getBarcode());
        request.setQrCode(options.getQrCode());
        request.setWidth(options.getWidth());
        request.setEncoding(options.getEncoding());
        request.setAlignment(options.getAlignment());
        
        RequestBody body = RequestBody.create(
            objectMapper.writeValueAsString(request),
            MediaType.parse("application/json")
        );
        
        Request httpRequest = new Request.Builder()
            .url(baseUrl + "/print")
            .header("Authorization", "Bearer " + accessToken)
            .post(body)
            .build();
        
        try (Response response = client.newCall(httpRequest).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response + ", body: " + response.body().string());
            }
            
            String json = response.body().string();
            return objectMapper.readValue(json, PrintResponse.class);
        }
    }
    
    public PrintResponse printImage(String imagePath, PrintImageOptions options) throws IOException {
        ensureAuthenticated();
        
        byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
        String base64Image = Base64.getEncoder().encodeToString(imageBytes);
        
        PrintImageRequest request = new PrintImageRequest();
        request.setImageBase64(base64Image);
        request.setWidth(options.getWidth());
        request.setAlignment(options.getAlignment());
        request.setDithering(options.isDithering());
        
        RequestBody body = RequestBody.create(
            objectMapper.writeValueAsString(request),
            MediaType.parse("application/json")
        );
        
        Request httpRequest = new Request.Builder()
            .url(baseUrl + "/print-image")
            .header("Authorization", "Bearer " + accessToken)
            .post(body)
            .build();
        
        try (Response response = client.newCall(httpRequest).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response + ", body: " + response.body().string());
            }
            
            String json = response.body().string();
            return objectMapper.readValue(json, PrintResponse.class);
        }
    }
    
    public StatusResponse getStatus() throws IOException {
        ensureAuthenticated();
        
        Request request = new Request.Builder()
            .url(baseUrl + "/status")
            .header("Authorization", "Bearer " + accessToken)
            .build();
        
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response + ", body: " + response.body().string());
            }
            
            String json = response.body().string();
            return objectMapper.readValue(json, StatusResponse.class);
        }
    }
    
    // Классы для сериализации/десериализации
    private static class AuthResponse {
        private String accessToken;
        private int expiresIn;
        
        public String getAccessToken() { return accessToken; }
        public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
        public int getExpiresIn() { return expiresIn; }
        public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; }
    }
    
    private static class PrintRequest {
        private String text;
        private boolean cut;
        private boolean beep;
        private String barcode;
        private String qrCode;
        private int width = 48;
        private String encoding = "cp866";
        private String alignment = "left";
        
        // Геттеры и сеттеры
        public String getText() { return text; }
        public void setText(String text) { this.text = text; }
        public boolean isCut() { return cut; }
        public void setCut(boolean cut) { this.cut = cut; }
        public boolean isBeep() { return beep; }
        public void setBeep(boolean beep) { this.beep = beep; }
        public String getBarcode() { return barcode; }
        public void setBarcode(String barcode) { this.barcode = barcode; }
        public String getQrCode() { return qrCode; }
        public void setQrCode(String qrCode) { this.qrCode = qrCode; }
        public int getWidth() { return width; }
        public void setWidth(int width) { this.width = width; }
        public String getEncoding() { return encoding; }
        public void setEncoding(String encoding) { this.encoding = encoding; }
        public String getAlignment() { return alignment; }
        public void setAlignment(String alignment) { this.alignment = alignment; }
    }
    
    private static class PrintOptions {
        private boolean cut = true;
        private boolean beep = true;
        private String barcode;
        private String qrCode;
        private int width = 48;
        private String encoding = "cp866";
        private String alignment = "left";
        
        // Геттеры и сеттеры
        public boolean isCut() { return cut; }
        public void setCut(boolean cut) { this.cut = cut; }
        public boolean isBeep() { return beep; }
        public void setBeep(boolean beep) { this.beep = beep; }
        public String getBarcode() { return barcode; }
        public void setBarcode(String barcode) { this.barcode = barcode; }
        public String getQrCode() { return qrCode; }
        public void setQrCode(String qrCode) { this.qrCode = qrCode; }
        public int getWidth() { return width; }
        public void setWidth(int width) { this.width = width; }
        public String getEncoding() { return encoding; }
        public void setEncoding(String encoding) { this.encoding = encoding; }
        public String getAlignment() { return alignment; }
        public void setAlignment(String alignment) { this.alignment = alignment; }
    }
    
    private static class PrintImageRequest {
        private String imageBase64;
        private Integer width;
        private String alignment = "center";
        private boolean dithering = true;
        
        // Геттеры и сеттеры
        public String getImageBase64() { return imageBase64; }
        public void setImageBase64(String imageBase64) { this.imageBase64 = imageBase64; }
        public Integer getWidth() { return width; }
        public void setWidth(Integer width) { this.width = width; }
        public String getAlignment() { return alignment; }
        public void setAlignment(String alignment) { this.alignment = alignment; }
        public boolean isDithering() { return dithering; }
        public void setDithering(boolean dithering) { this.dithering = dithering; }
    }
    
    private static class PrintImageOptions {
        private Integer width;
        private String alignment = "center";
        private boolean dithering = true;
        
        // Геттеры и сеттеры
        public Integer getWidth() { return width; }
        public void setWidth(Integer width) { this.width = width; }
        public String getAlignment() { return alignment; }
        public void setAlignment(String alignment) { this.alignment = alignment; }
        public boolean isDithering() { return dithering; }
        public void setDithering(boolean dithering) { this.dithering = dithering; }
    }
    
    private static class PrintResponse {
        private String status;
        private String message;
        
        public String getStatus() { return status; }
        public void setStatus(String status) { this.status = status; }
        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
    }
    
    private static class StatusResponse {
        private String status;
        private double temperature;
        private String paperStatus;
        private int errorCode;
        private String model;
        private String serialNumber;
        
        // Геттеры и сеттеры
        public String getStatus() { return status; }
        public void setStatus(String status) { this.status = status; }
        public double getTemperature() { return temperature; }
        public void setTemperature(double temperature) { this.temperature = temperature; }
        public String getPaperStatus() { return paperStatus; }
        public void setPaperStatus(String paperStatus) { this.paperStatus = paperStatus; }
        public int getErrorCode() { return errorCode; }
        public void setErrorCode(int errorCode) { this.errorCode = errorCode; }
        public String getModel() { return model; }
        public void setModel(String model) { this.model = model; }
        public String getSerialNumber() { return serialNumber; }
        public void setSerialNumber(String serialNumber) { this.serialNumber = serialNumber; }
    }
    
    // Интерцептор для повторных попыток
    private static class RetryInterceptor implements Interceptor {
        private final int maxRetries;
        
        public RetryInterceptor(int maxRetries) {
            this.maxRetries = maxRetries;
        }
        
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            int retryCount = 0;
            
            while (true) {
                try {
                    return chain.proceed(request);
                } catch (IOException e) {
                    retryCount++;
                    if (retryCount > maxRetries) {
                        throw e;
                    }
                }
            }
        }
    }
    
    // Пример использования
    public static void main(String[] args) {
        EstrellaClient client = new EstrellaClient("http://localhost:8080", "your-api-key");
        
        String receiptText = "КАССОВЫЙ ЧЕК\n=====================\nТовар 1: 100.00 руб.\nТовар 2: 200.00 руб.\n---------------------\nИТОГО: 300.00 руб.\n=====================\nСпасибо за покупку!";
        
        try {
            PrintOptions options = new PrintOptions();
            options.setBarcode("123456789012");
            options.setQrCode("https://example.com/receipt/123");
            
            PrintResponse response = client.printReceipt(receiptText, options);
            System.out.println("Ответ сервера: " + response.getStatus());
            
            StatusResponse status = client.getStatus();
            System.out.println("Статус принтера: " + status.getStatus());
            System.out.println("Температура: " + status.getTemperature() + "°C");
            System.out.println("Статус бумаги: " + status.getPaperStatus());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Интеграция с Go

Установите необходимые пакеты:

go get github.com/go-resty/resty/v2

Пример интеграции:

package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"time"

	"github.com/go-resty/resty/v2"
)

type EstrellaClient struct {
	baseURL    string
	apiKey     string
	accessToken string
	tokenExpiry time.Time
	client     *resty.Client
}

type AuthRequest struct {
	APIKey string `json:"api_key"`
}

type AuthResponse struct {
	AccessToken string `json:"access_token"`
	ExpiresIn   int    `json:"expires_in"`
}

type PrintRequest struct {
	Text     string `json:"text"`
	Cut      bool   `json:"cut"`
	Beep     bool   `json:"beep"`
	Barcode  string `json:"barcode,omitempty"`
	QRCode   string `json:"qr_code,omitempty"`
	Width    int    `json:"width"`
	Encoding string `json:"encoding"`
	Align    string `json:"align"`
}

type PrintImageRequest struct {
	ImageBase64 string `json:"image_base64"`
	Width       int    `json:"width"`
	Align       string `json:"alignment"`
	Dithering  bool   `json:"dithering"`
}

type StatusResponse struct {
	Status        string  `json:"status"`
	Temperature   float64 `json:"temperature"`
	PaperStatus   string  `json:"paper_status"`
	ErrorCode     int     `json:"error_code"`
	Model         string  `json:"model"`
	SerialNumber  string  `json:"serial_number"`
}

func NewEstrellaClient(baseURL, apiKey string) *EstrellaClient {
	client := resty.New().
		SetTimeout(30 * time.Second).
		SetRetryCount(3).
		SetRetryWaitTime(1 * time.Second)
	
	return &EstrellaClient{
		baseURL: baseURL,
		apiKey:  apiKey,
		client:  client,
	}
}

func (c *EstrellaClient) authenticate() error {
	authReq := AuthRequest{APIKey: c.apiKey}
	
	var authResp AuthResponse
	_, err := c.client.R().
		SetBody(authReq).
		SetResult(&authResp).
		Post(c.baseURL + "/auth")
	
	if err != nil {
		return err
	}
	
	c.accessToken = authResp.AccessToken
	c.tokenExpiry = time.Now().Add(time.Duration(authResp.ExpiresIn) * time.Second)
	return nil
}

func (c *EstrellaClient) ensureAuthenticated() error {
	if c.accessToken == "" || time.Now().After(c.tokenExpiry) {
		return c.authenticate()
	}
	return nil
}

func (c *EstrellaClient) PrintReceipt(text string, options PrintRequest) (*resty.Response, error) {
	if err := c.ensureAuthenticated(); err != nil {
		return nil, err
	}
	
	// Установка значений по умолчанию
	if options.Width == 0 {
		options.Width = 48
	}
	if options.Encoding == "" {
		options.Encoding = "cp866"
	}
	if options.Align == "" {
		options.Align = "left"
	}
	
	return c.client.R().
		SetHeader("Authorization", "Bearer "+c.accessToken).
		SetBody(options).
		Post(c.baseURL + "/print")
}

func (c *EstrellaClient) PrintImage(imagePath string, options PrintImageRequest) (*resty.Response, error) {
	if err := c.ensureAuthenticated(); err != nil {
		return nil, err
	}
	
	// Чтение и кодирование изображения
	imageData, err := ioutil.ReadFile(imagePath)
	if err != nil {
		return nil, fmt.Errorf("не удалось прочитать изображение: %v", err)
	}
	
	options.ImageBase64 = base64.StdEncoding.EncodeToString(imageData)
	
	// Установка значений по умолчанию
	if options.Align == "" {
		options.Align = "center"
	}
	if options.Dithering {
		options.Dithering = true
	}
	
	return c.client.R().
		SetHeader("Authorization", "Bearer "+c.accessToken).
		SetBody(options).
		Post(c.baseURL + "/print-image")
}

func (c *EstrellaClient) GetStatus() (*StatusResponse, error) {
	if err := c.ensureAuthenticated(); err != nil {
		return nil, err
	}
	
	var status StatusResponse
	_, err := c.client.R().
		SetHeader("Authorization", "Bearer "+c.accessToken).
		SetResult(&status).
		Get(c.baseURL + "/status")
	
	return &status, err
}

func (c *EstrellaClient) GetQueueStatus() (*resty.Response, error) {
	if err := c.ensureAuthenticated(); err != nil {
		return nil, err
	}
	
	return c.client.R().
		SetHeader("Authorization", "Bearer "+c.accessToken).
		Get(c.baseURL + "/queue/status")
}

func main() {
	client := NewEstrellaClient("http://localhost:8080", "your-api-key")
	
	// Печать чека
	receiptReq := PrintRequest{
		Text:    "КАССОВЫЙ ЧЕК\n=====================\nТовар 1: 100.00 руб.\nТовар 2: 200.00 руб.\n---------------------\nИТОГО: 300.00 руб.\n=====================\nСпасибо за покупку!",
		Cut:     true,
		Beep:    true,
		Barcode: "123456789012",
		QRCode:  "https://example.com/receipt/123",
	}
	
	resp, err := client.PrintReceipt(receiptReq.Text, receiptReq)
	if err != nil {
		fmt.Printf("Ошибка печати: %v\n", err)
		return
	}
	
	if resp.StatusCode() != 200 {
		fmt.Printf("Ошибка ответа сервера: %s\n", resp.Status())
		return
	}
	
	fmt.Println("Чек успешно отправлен на печать")
	
	// Получение статуса принтера
	status, err := client.GetStatus()
	if err != nil {
		fmt.Printf("Ошибка получения статуса: %v\n", err)
		return
	}
	
	fmt.Printf("Статус принтера: %s\n", status.Status)
	fmt.Printf("Температура: %.1f°C\n", status.Temperature)
	fmt.Printf("Статус бумаги: %s\n", status.PaperStatus)
	fmt.Printf("Модель: %s\n", status.Model)
	
	// Пример печати изображения
	imageReq := PrintImageRequest{
		Width:      200,
		Align:      "center",
		Dithering: true,
	}
	
	resp, err = client.PrintImage("/path/to/logo.png", imageReq)
	if err != nil {
		fmt.Printf("Ошибка печати изображения: %v\n", err)
		return
	}
	
	fmt.Println("Изображение успешно отправлено на печать")
	
	// Получение статуса очереди
	queueResp, err := client.GetQueueStatus()
	if err != nil {
		fmt.Printf("Ошибка получения статуса очереди: %v\n", err)
		return
	}
	
	var queueStatus map[string]interface{}
	if err := json.Unmarshal(queueResp.Body(), &queueStatus); err != nil {
		fmt.Printf("Ошибка разбора ответа: %v\n", err)
		return
	}
	
	fmt.Printf("Статус очереди: %+v\n", queueStatus)
}

Интеграция с веб-приложением

Пример современного веб-интерфейса для печати чеков:

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Система печати чеков Estrella</title>
    <style>
        :root {
            --primary-color: #4CAF50;
            --primary-hover: #45a049;
            --danger-color: #f44336;
            --success-color: #4CAF50;
            --background-color: #f8f9fa;
            --card-background: #ffffff;
            --text-color: #333333;
            --border-color: #ddd;
            --shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: var(--background-color);
            color: var(--text-color);
            line-height: 1.6;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        
        header {
            background-color: var(--card-background);
            box-shadow: var(--shadow);
            margin-bottom: 30px;
            padding: 20px 0;
        }
        
        h1 {
            text-align: center;
            color: var(--primary-color);
        }
        
        .main-content {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
        }
        
        .card {
            background-color: var(--card-background);
            border-radius: 8px;
            box-shadow: var(--shadow);
            padding: 20px;
        }
        
        .form-group {
            margin-bottom: 20px;
        }
        
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
        }
        
        input, textarea, select {
            width: 100%;
            padding: 12px;
            border: 1px solid var(--border-color);
            border-radius: 4px;
            font-size: 16px;
            transition: border 0.3s;
        }
        
        input:focus, textarea:focus, select:focus {
            outline: none;
            border-color: var(--primary-color);
        }
        
        textarea {
            min-height: 180px;
            resize: vertical;
        }
        
        .checkbox-group {
            display: flex;
            align-items: center;
            margin-bottom: 15px;
        }
        
        .checkbox-group input {
            width: auto;
            margin-right: 10px;
            transform: scale(1.2);
        }
        
        button {
            background-color: var(--primary-color);
            color: white;
            border: none;
            padding: 12px 20px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            font-weight: 600;
            transition: background-color 0.3s;
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }
        
        button:hover {
            background-color: var(--primary-hover);
        }
        
        button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        
        .btn-secondary {
            background-color: #6c757d;
        }
        
        .btn-secondary:hover {
            background-color: #5a6268;
        }
        
        .status-message {
            padding: 15px;
            border-radius: 4px;
            margin-top: 20px;
            display: none;
        }
        
        .status-message.success {
            background-color: #dff0d8;
            color: #3c763d;
            border: 1px solid #d6e9c6;
        }
        
        .status-message.error {
            background-color: #f2dede;
            color: #a94442;
            border: 1px solid #ebccd1;
        }
        
        .status-message.info {
            background-color: #d9edf7;
            color: #31708f;
            border: 1px solid #bce8f1;
        }
        
        .config-section {
            grid-column: 1 / -1;
            margin-top: 20px;
        }
        
        .tabs {
            display: flex;
            border-bottom: 1px solid var(--border-color);
            margin-bottom: 20px;
        }
        
        .tab {
            padding: 10px 20px;
            cursor: pointer;
            border-bottom: 2px solid transparent;
            transition: all 0.3s;
        }
        
        .tab:hover {
            background-color: #f1f1f1;
        }
        
        .tab.active {
            border-bottom: 2px solid var(--primary-color);
            color: var(--primary-color);
        }
        
        .tab-content {
            display: none;
        }
        
        .tab-content.active {
            display: block;
        }
        
        .receipt-preview {
            background-color: #f9f9f9;
            border: 1px solid var(--border-color);
            padding: 15px;
            margin-top: 15px;
            white-space: pre-wrap;
            font-family: monospace;
            font-size: 12px;
        }
        
        .spinner {
            display: none;
            width: 20px;
            height: 20px;
            border: 3px solid rgba(255,255,255,.3);
            border-radius: 50%;
            border-top-color: #fff;
            animation: spin 1s ease-in-out infinite;
        }
        
        @keyframes spin {
            to { transform: rotate(360deg); }
        }
        
        .button-group {
            display: flex;
            gap: 10px;
        }
        
        .metrics-section {
            grid-column: 1 / -1;
            margin-top: 20px;
        }
        
        .metrics-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 15px;
            margin-top: 15px;
        }
        
        .metric-card {
            background-color: var(--card-background);
            border-radius: 8px;
            padding: 15px;
            box-shadow: var(--shadow);
            text-align: center;
        }
        
        .metric-value {
            font-size: 24px;
            font-weight: bold;
            color: var(--primary-color);
        }
        
        .metric-label {
            font-size: 14px;
            color: #666;
            margin-top: 5px;
        }
        
        @media (max-width: 768px) {
            .main-content {
                grid-template-columns: 1fr;
            }
            
            .button-group {
                flex-direction: column;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>Система печати чеков Estrella</h1>
        </header>
        
        <div class="main-content">
            <div class="card">
                <h2>Печать чека</h2>
                <form id="printForm">
                    <div class="form-group">
                        <label for="text">Текст для печати:</label>
                        <textarea id="text" name="text" placeholder="Введите текст для печати"></textarea>
                    </div>
                    
                    <div class="form-group">
                        <label for="width">Ширина чека (символов):</label>
                        <input type="number" id="width" name="width" min="20" max="60" value="48">
                    </div>
                    
                    <div class="form-group">
                        <label for="encoding">Кодировка:</label>
                        <select id="encoding" name="encoding">
                            <option value="cp866">CP866</option>
                            <option value="cp1251">CP1251</option>
                            <option value="utf8">UTF-8</option>
                            <option value="koi8-r">KOI8-R</option>
                        </select>
                    </div>
                    
                    <div class="form-group">
                        <label for="alignment">Выравнивание:</label>
                        <select id="alignment" name="alignment">
                            <option value="left">По левому краю</option>
                            <option value="center">По центру</option>
                            <option value="right">По правому краю</option>
                        </select>
                    </div>
                    
                    <div class="checkbox-group">
                        <input type="checkbox" id="cut" name="cut" checked>
                        <label for="cut">Отрезать чек</label>
                    </div>
                    
                    <div class="checkbox-group">
                        <input type="checkbox" id="beep" name="beep" checked>
                        <label for="beep">Звуковой сигнал</label>
                    </div>
                    
                    <div class="form-group">
                        <label for="barcode">Штрих-код (опционально):</label>
                        <input type="text" id="barcode" name="barcode" placeholder="Введите штрих-код">
                    </div>
                    
                    <div class="form-group">
                        <label for="qr_code">QR-код (URL, опционально):</label>
                        <input type="text" id="qr_code" name="qr_code" placeholder="Введите URL для QR-кода">
                    </div>
                    
                    <div class="button-group">
                        <button type="submit" id="printButton">
                            <span>Напечатать</span>
                            <span class="spinner"></span>
                        </button>
                        <button type="button" class="btn-secondary" id="previewButton">Предпросмотр</button>
                    </div>
                </form>
                
                <div id="status" class="status-message"></div>
            </div>
            
            <div class="card">
                <h2>Печать изображения</h2>
                <form id="imageForm">
                    <div class="form-group">
                        <label for="imageFile">Выберите изображение:</label>
                        <input type="file" id="imageFile" name="imageFile" accept="image/*">
                    </div>
                    
                    <div class="form-group">
                        <label for="imageWidth">Ширина изображения (пиксели):</label>
                        <input type="number" id="imageWidth" name="imageWidth" min="50" max="512" value="200">
                    </div>
                    
                    <div class="form-group">
                        <label for="imageAlignment">Выравнивание:</label>
                        <select id="imageAlignment" name="imageAlignment">
                            <option value="left">По левому краю</option>
                            <option value="center" selected>По центру</option>
                            <option value="right">По правому краю</option>
                        </select>
                    </div>
                    
                    <div class="checkbox-group">
                        <input type="checkbox" id="dithering" name="dithering" checked>
                        <label for="dithering">Дизеринг (для черно-белых изображений)</label>
                    </div>
                    
                    <button type="submit" id="printImageButton">
                        <span>Напечатать изображение</span>
                        <span class="spinner"></span>
                    </button>
                </form>
            </div>
            
            <div class="card metrics-section">
                <h2>Статус принтера</h2>
                <div class="metrics-grid" id="metricsGrid">
                    <div class="metric-card">
                        <div class="metric-value" id="printerStatus">-</div>
                        <div class="metric-label">Статус</div>
                    </div>
                    <div class="metric-card">
                        <div class="metric-value" id="printerTemp">-</div>
                        <div class="metric-label">Температура (°C)</div>
                    </div>
                    <div class="metric-card">
                        <div class="metric-value" id="paperStatus">-</div>
                        <div class="metric-label">Бумага</div>
                    </div>
                    <div class="metric-card">
                        <div class="metric-value" id="errorStatus">-</div>
                        <div class="metric-label">Код ошибки</div>
                    </div>
                </div>
                
                <div id="printerStatusMessage" class="status-message"></div>
            </div>
            
            <div class="card config-section">
                <h2>Настройки API</h2>
                <div class="tabs">
                    <div class="tab active" data-tab="basic">Основные</div>
                    <div class="tab" data-tab="advanced">Расширенные</div>
                </div>
                
                <div class="tab-content active" id="basic">
                    <div class="form-group">
                        <label for="apiUrl">URL API:</label>
                        <input type="text" id="apiUrl" value="http://localhost:8080" placeholder="URL API сервера">
                    </div>
                    
                    <div class="form-group">
                        <label for="apiKey">API ключ:</label>
                        <input type="password" id="apiKey" value="your-api-key" placeholder="Введите API ключ">
                    </div>
                </div>
                
                <div class="tab-content" id="advanced">
                    <div class="form-group">
                        <label for="apiTimeout">Таймаут запроса (мс):</label>
                        <input type="number" id="apiTimeout" value="30000" min="1000" max="120000">
                    </div>
                    
                    <div class="form-group">
                        <label for="retryCount">Количество повторных попыток:</label>
                        <input type="number" id="retryCount" value="3" min="0" max="10">
                    </div>
                    
                    <div class="checkbox-group">
                        <input type="checkbox" id="useQueue">
                        <label for="useQueue">Использовать очередь печати</label>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <script>
        // Утилиты
        const DOM = {
            elements: {},
            init: function() {
                // Формы
                this.elements.printForm = document.getElementById('printForm');
                this.elements.imageForm = document.getElementById('imageForm');
                
                // Поля ввода
                this.elements.text = document.getElementById('text');
                this.elements.width = document.getElementById('width');
                this.elements.encoding = document.getElementById('encoding');
                this.elements.alignment = document.getElementById('alignment');
                this.elements.cut = document.getElementById('cut');
                this.elements.beep = document.getElementById('beep');
                this.elements.barcode = document.getElementById('barcode');
                this.elements.qr_code = document.getElementById('qr_code');
                this.elements.imageFile = document.getElementById('imageFile');
                this.elements.imageWidth = document.getElementById('imageWidth');
                this.elements.imageAlignment = document.getElementById('imageAlignment');
                this.elements.dithering = document.getElementById('dithering');
                
                // Кнопки
                this.elements.printButton = document.getElementById('printButton');
                this.elements.previewButton = document.getElementById('previewButton');
                this.elements.printImageButton = document.getElementById('printImageButton');
                
                // Статус
                this.elements.status = document.getElementById('status');
                this.elements.printerStatusMessage = document.getElementById('printerStatusMessage');
                
                // Метрики
                this.elements.printerStatus = document.getElementById('printerStatus');
                this.elements.printerTemp = document.getElementById('printerTemp');
                this.elements.paperStatus = document.getElementById('paperStatus');
                this.elements.errorStatus = document.getElementById('errorStatus');
                
                // Конфигурация
                this.elements.apiUrl = document.getElementById('apiUrl');
                this.elements.apiKey = document.getElementById('apiKey');
                this.elements.apiTimeout = document.getElementById('apiTimeout');
                this.elements.retryCount = document.getElementById('retryCount');
                this.elements.useQueue = document.getElementById('useQueue');
                
                // Вкладки
                this.elements.tabs = document.querySelectorAll('.tab');
                this.elements.tabContents = document.querySelectorAll('.tab-content');
            },
            
            showSpinner: function(button) {
                const spinner = button.querySelector('.spinner');
                if (spinner) {
                    spinner.style.display = 'inline-block';
                    button.disabled = true;
                }
            },
            
            hideSpinner: function(button) {
                const spinner = button.querySelector('.spinner');
                if (spinner) {
                    spinner.style.display = 'none';
                    button.disabled = false;
                }
            },
            
            showMessage: function(element, message, type) {
                element.textContent = message;
                element.className = `status-message ${type}`;
                element.style.display = 'block';
                
                if (type === 'success' || type === 'info') {
                    setTimeout(() => {
                        element.style.display = 'none';
                    }, 5000);
                }
            },
            
            updateMetrics: function(metrics) {
                if (metrics.status) {
                    this.elements.printerStatus.textContent = metrics.status;
                    this.elements.printerTemp.textContent = metrics.temperature || '-';
                    this.elements.paperStatus.textContent = metrics.paper_status || '-';
                    this.elements.errorStatus.textContent = metrics.error_code || '0';
                }
            }
        };
        
        // Клиент API
        class EstrellaClient {
            constructor(config) {
                this.config = config;
                this.headers = {
                    'Content-Type': 'application/json'
                };
            }
            
            async authenticate() {
                if (!this.config.apiKey) {
                    throw new Error('API ключ не указан');
                }
                
                const response = await fetch(`${this.config.apiUrl}/auth`, {
                    method: 'POST',
                    headers: this.headers,
                    body: JSON.stringify({ api_key: this.config.apiKey })
                });
                
                if (!response.ok) {
                    throw new Error('Ошибка аутентификации');
                }
                
                const data = await response.json();
                this.headers['Authorization'] = `Bearer ${data.access_token}`;
                return data.access_token;
            }
            
            async request(endpoint, options = {}) {
                const maxRetries = this.config.retryCount || 3;
                let lastError;
                
                for (let i = 0; i <= maxRetries; i++) {
                    try {
                        // Добавляем аутентификацию, если ее нет
                        const headers = { ...this.headers };
                        
                        // Если это не эндпоинт аутентификации и нет токена, аутентифицируемся
                        if (endpoint !== '/auth' && !headers['Authorization']) {
                            await this.authenticate();
                            headers['Authorization'] = this.headers['Authorization'];
                        }
                        
                        const response = await fetch(`${this.config.apiUrl}${endpoint}`, {
                            ...options,
                            headers
                        });
                        
                        if (response.ok) {
                            return await response.json();
                        } else if (response.status === 401 && i < maxRetries) {
                            // Повторная аутентификация
                            await this.authenticate();
                            continue;
                        } else {
                            const errorData = await response.text();
                            throw new Error(`Ошибка ${response.status}: ${errorData}`);
                        }
                    } catch (error) {
                        lastError = error;
                        if (i < maxRetries) {
                            // Экспоненциальная задержка
                            const delay = Math.pow(2, i) * 1000;
                            await new Promise(resolve => setTimeout(resolve, delay));
                        }
                    }
                }
                
                throw lastError;
            }
            
            async printReceipt(data) {
                if (this.config.useQueue) {
                    return this.request('/queue/add', {
                        method: 'POST',
                        body: JSON.stringify({
                            type: 'receipt',
                            data
                        })
                    });
                }
                
                return this.request('/print', {
                    method: 'POST',
                    body: JSON.stringify(data)
                });
            }
            
            async printImage(data) {
                if (this.config.useQueue) {
                    return this.request('/queue/add', {
                        method: 'POST',
                        body: JSON.stringify({
                            type: 'image',
                            data
                        })
                    });
                }
                
                return this.request('/print-image', {
                    method: 'POST',
                    body: JSON.stringify(data)
                });
            }
            
            async getStatus() {
                return this.request('/status');
            }
        }
        
        // Основной класс приложения
        class PrinterApp {
            constructor() {
                DOM.init();
                this.client = new EstrellaClient({
                    apiUrl: DOM.elements.apiUrl.value,
                    apiKey: DOM.elements.apiKey.value,
                    retryCount: parseInt(DOM.elements.retryCount.value) || 3,
                    useQueue: DOM.elements.useQueue.checked
                });
                
                this.initEventListeners();
                this.startStatusPolling();
                
                // Установка примера текста чека
                this.setReceiptExample();
            }
            
            initEventListeners() {
                // Вкладки
                DOM.elements.tabs.forEach(tab => {
                    tab.addEventListener('click', () => this.switchTab(tab));
                });
                
                // Формы
                DOM.elements.printForm.addEventListener('submit', (e) => this.handlePrintReceipt(e));
                DOM.elements.imageForm.addEventListener('submit', (e) => this.handlePrintImage(e));
                
                // Кнопки
                DOM.elements.previewButton.addEventListener('click', () => this.showPreview());
                
                // Изменение конфигурации
                DOM.elements.apiUrl.addEventListener('change', () => this.updateConfig());
                DOM.elements.apiKey.addEventListener('change', () => this.updateConfig());
                DOM.elements.retryCount.addEventListener('change', () => this.updateConfig());
                DOM.elements.useQueue.addEventListener('change', () => this.updateConfig());
            }
            
            switchTab(tab) {
                // Снимаем активный класс со всех вкладок и контента
                DOM.elements.tabs.forEach(t => t.classList.remove('active'));
                DOM.elements.tabContents.forEach(c => c.classList.remove('active'));
                
                // Добавляем активный класс выбранной вкладке и соответствующему контенту
                tab.classList.add('active');
                const tabId = tab.getAttribute('data-tab');
                document.getElementById(tabId).classList.add('active');
            }
            
            async handlePrintReceipt(e) {
                e.preventDefault();
                
                DOM.showSpinner(DOM.elements.printButton);
                
                try {
                    const data = {
                        text: DOM.elements.text.value,
                        width: parseInt(DOM.elements.width.value),
                        encoding: DOM.elements.encoding.value,
                        alignment: DOM.elements.alignment.value,
                        cut: DOM.elements.cut.checked,
                        beep: DOM.elements.beep.checked,
                        barcode: DOM.elements.barcode.value || null,
                        qr_code: DOM.elements.qr_code.value || null
                    };
                    
                    const response = await this.client.printReceipt(data);
                    
                    DOM.showMessage(DOM.elements.status, 'Чек успешно отправлен на печать', 'success');
                    console.log('Ответ сервера:', response);
                } catch (error) {
                    console.error('Ошибка печати:', error);
                    DOM.showMessage(DOM.elements.status, `Ошибка при отправке на печать: ${error.message}`, 'error');
                } finally {
                    DOM.hideSpinner(DOM.elements.printButton);
                }
            }
            
            async handlePrintImage(e) {
                e.preventDefault();
                
                const file = DOM.elements.imageFile.files[0];
                if (!file) {
                    DOM.showMessage(DOM.elements.status, 'Пожалуйста, выберите изображение', 'error');
                    return;
                }
                
                DOM.showSpinner(DOM.elements.printImageButton);
                
                try {
                    // Читаем файл и кодируем в Base64
                    const reader = new FileReader();
                    reader.onload = async (event) => {
                        try {
                            const base64String = event.target.result.split(',')[1];
                            
                            const data = {
                                image_base64: base64String,
                                width: parseInt(DOM.elements.imageWidth.value),
                                alignment: DOM.elements.imageAlignment.value,
                                dithering: DOM.elements.dithering.checked
                            };
                            
                            const response = await this.client.printImage(data);
                            
                            DOM.showMessage(DOM.elements.status, 'Изображение успешно отправлено на печать', 'success');
                            console.log('Ответ сервера:', response);
                        } catch (error) {
                            console.error('Ошибка печати изображения:', error);
                            DOM.showMessage(DOM.elements.status, `Ошибка печати изображения: ${error.message}`, 'error');
                        } finally {
                            DOM.hideSpinner(DOM.elements.printImageButton);
                        }
                    };
                    
                    reader.readAsDataURL(file);
                } catch (error) {
                    console.error('Ошибка чтения файла:', error);
                    DOM.showMessage(DOM.elements.status, `Ошибка чтения файла: ${error.message}`, 'error');
                    DOM.hideSpinner(DOM.elements.printImageButton);
                }
            }
            
            showPreview() {
                const preview = document.createElement('div');
                preview.className = 'receipt-preview';
                
                const text = DOM.elements.text.value;
                const width = parseInt(DOM.elements.width.value);
                
                // Простая имитация форматирования текста
                const lines = text.split('\n');
                const formattedLines = lines.map(line => {
                    // Центрируем текст, если нужно
                    if (DOM.elements.alignment.value === 'center' && line.trim() !== '') {
                        const padding = Math.floor((width - line.length) / 2);
                        return ' '.repeat(Math.max(0, padding)) + line;
                    }
                    return line;
                });
                
                preview.textContent = formattedLines.join('\n');
                
                // Показываем превью
                const existingPreview = document.querySelector('.receipt-preview');
                if (existingPreview) {
                    existingPreview.remove();
                }
                
                DOM.elements.printForm.appendChild(preview);
            }
            
            updateConfig() {
                this.client.config = {
                    apiUrl: DOM.elements.apiUrl.value,
                    apiKey: DOM.elements.apiKey.value,
                    retryCount: parseInt(DOM.elements.retryCount.value) || 3,
                    useQueue: DOM.elements.useQueue.checked
                };
            }
            
            async startStatusPolling() {
                // Проверяем статус принтера каждые 5 секунд
                setInterval(async () => {
                    try {
                        const status = await this.client.getStatus();
                        DOM.updateMetrics(status);
                        
                        // Показываем предупреждение, если принтер не в порядке
                        if (status.status !== 'ready') {
                            DOM.showMessage(DOM.elements.printerStatusMessage, 
                                `Статус принтера: ${status.status}. Код ошибки: ${status.error_code}`, 'info');
                        }
                    } catch (error) {
                        console.error('Ошибка получения статуса принтера:', error);
                        DOM.showMessage(DOM.elements.printerStatusMessage, 
                            `Ошибка получения статуса принтера: ${error.message}`, 'error');
                    }
                }, 5000);
                
                // Первоначальная проверка
                try {
                    const status = await this.client.getStatus();
                    DOM.updateMetrics(status);
                } catch (error) {
                    console.error('Ошибка получения начального статуса принтера:', error);
                    DOM.showMessage(DOM.elements.printerStatusMessage, 
                        `Ошибка подключения к серверу: ${error.message}`, 'error');
                }
            }
            
            setReceiptExample() {
                const receiptExample = `КАССОВЫЙ ЧЕК
=====================
Товар 1: 100.00 руб.
Товар 2: 200.00 руб.
---------------------
ИТОГО: 300.00 руб.
=====================
Спасибо за покупку!
`;
                DOM.elements.text.value = receiptExample;
            }
        }
        
        // Инициализация приложения после загрузки DOM
        document.addEventListener('DOMContentLoaded', () => {
            new PrinterApp();
        });
    </script>
</body>
</html>

Решение распространенных проблем

Проблема: Принтер не определяется

  1. Проверьте подключение принтера к компьютеру
  2. Убедитесь, что драйверы установлены правильно
  3. Проверьте права доступа к USB-устройству:
    ls -l /dev/usb/lp0
    
  4. Для Linux добавьте пользователя в группы lp и dialout:
    sudo usermod -a -G lp,dialout $USER
    
  5. Перезагрузите систему или выйдите и войдите снова
  6. Для Epson принтеров попробуйте установить официальный драйвер
  7. Проверьте, что принтер поддерживается Estrella через эндпоинт /printers
  8. Для сетевых принтеров проверьте доступность порта:
    telnet 192.168.1.100 9100
    

Проблема: Некорректное отображение кириллицы

  1. Проверьте параметр encoding в конфигурационном файле:
    • Для Epson: cp866
    • Для Star: cp866
    • Для Citizen: cp1251
    • Для Zebra: utf8
    • Для Bixolon: cp949 или utf8
  2. Убедитесь, что в тексте нет BOM-маркеров
  3. Для принтеров с поддержкой Unicode установите encoding = "utf8"
  4. Проверьте настройки терминала/приложения, отправляющего запросы
  5. Для сложных текстов попробуйте использовать escape-последовательности

Проблема: Сервер не запускается

  1. Убедитесь, что порт 8080 не занят другим процессом:
    netstat -tuln | grep 8080
    
  2. Проверьте синтаксис конфигурационного файла:
    cargo check
    
  3. Убедитесь, что все зависимости установлены:
    cargo build --release
    
  4. Для Windows проверьте доступность пути к принтеру:
    [printer]
    path = "\\\\.\\printer"  # для Windows
    
  5. Проверьте права доступа к директории с бинарным файлом
  6. Проверьте наличие системных библиотек:
    ldd ./target/release/estrella
    

Проблема: Нечеткий текст или изображения

  1. Увеличьте параметр density в конфигурации (максимально 15)
  2. Проверьте параметр speed (уменьшите для большей четкости)
  3. Убедитесь, что разрешение изображения соответствует возможностям принтера
  4. Для изображений увеличьте параметр dithering
  5. Проверьте, что бумага подходит для принтера
  6. Для термопринтеров проверьте температуру печати

Проблема: Штрих-код не считывается

  1. Проверьте правильность формата штрих-кода в конфигурации
  2. Увеличьте параметр height для штрих-кода (минимум 50)
  3. Убедитесь, что параметры width соответствуют стандарту:
    • CODE128: 2-3
    • EAN13: 2-3
    • QR: 5-10
  4. Проверьте контрастность печати
  5. Убедитесь, что штрих-код не обрезан по краям
  6. Для сложных штрих-кодов увеличьте параметр error_correction

Проблема: QR-код не считывается

  1. Проверьте параметр error_correction:
    • Для большинства случаев используйте "M"
    • Для поврежденных кодов используйте "Q" или "H"
  2. Увеличьте параметр size (минимум 5, максимум 20)
  3. Убедитесь, что URL не содержит запрещенных символов
  4. Для сложных QR-кодов увеличьте параметр margin
  5. Проверьте, что QR-код не обрезан по краям
  6. Убедитесь, что контрастность достаточная

Проблема: Низкая производительность сервера

  1. Увеличьте количество worker-потоков:
    [server]
    workers = 8  # в зависимости от количества ядер CPU
    
  2. Проверьте загрузку CPU и памяти
  3. Оптимизируйте конфигурацию принтера
  4. Включите кэширование для часто используемых шаблонов
  5. Используйте очередь для пиковых нагрузок
  6. Для больших изображений уменьшьте разрешение
  7. Проверьте сетевое подключение для удаленных принтеров

Проблема: Ошибки аутентификации

  1. Проверьте правильность API-ключа в конфигурации и в запросах
  2. Убедитесь, что аутентификация включена в настройках сервера:
    [auth]
    enabled = true
    
  3. Для Bearer токенов проверьте формат заголовка:
    Authorization: Bearer your-api-key
    
  4. Проверьте, что токен не истек
  5. Для временных токенов используйте эндпоинт /refresh для обновления
  6. Проверьте системные логи на предмет ошибок аутентификации

Проблема: Ошибки при работе с очередью

  1. Проверьте, что очередь включена в настройках:
    [queue]
    enabled = true
    
  2. Увеличьте размер очереди при пиковых нагрузках:
    [queue]
    max_size = 5000
    
  3. Проверьте статус очереди через эндпоинт /queue/status
  4. Для постоянной очереди настройте SQLite:
    [persistent_queue]
    path = "/var/lib/estrella/queue.db"
    max_size = "1 GB"
    
  5. Проверьте права доступа к директории с базой данных
  6. Для очень больших очередей настройте регулярную очистку

Проблема: Ошибки при работе с сетевыми принтерами

  1. Проверьте доступность принтера по сети:
    ping <printer_ip>
    telnet <printer_ip> 9100
    
  2. Увеличьте таймауты для сетевых операций:
    [printer.network]
    timeout = 30
    retries = 5
    
  3. Проверьте настройки файрвола
  4. Используйте статические IP-адреса для сетевых принтеров
  5. Для Wi-Fi принтеров проверьте стабильность соединения
  6. Включите автопереопределение принтера:
    [printer.network]
    auto_reconnect = true
    reconnect_interval = 10
    

Проблема: Ошибки при работе с USB-принтерами

  1. Проверьте, что принтер правильно определяется системой:
    lsusb
    
  2. Проверьте права доступа к USB-устройству:
    ls -l /dev/usb/lp*
    
  3. Для Linux настройте udev правила:
    ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="04b8", ATTR{idProduct}=="0202", MODE="0666"
    
  4. Перезагрузите udev:
    sudo udevadm control --reload-rules
    sudo udevadm trigger
    
  5. Для Windows проверьте драйверы через Диспетчер устройств
  6. Для Mac проверьте настройки CUPS

Проблема: Ошибки при работе с Bluetooth-принтерами

  1. Убедитесь, что Bluetooth включен и доступен
  2. Проверьте, что принтер находится в режиме обнаружения
  3. Отсканируйте доступные устройства:
    bluetoothctl scan on
    
  4. Проверьте MAC-адрес принтера
  5. Увеличьте таймаут для Bluetooth операций:
    [printer.bluetooth]
    timeout = 60
    
  6. Проверьте расстояние между устройствами и помехи
  7. Для старых моделей принтеров попробуйте уменьшить скорость передачи

Проблема: Ошибки при работе с принтерами через COM-порт

  1. Проверьте, что порт правильно определяется:
    ls -l /dev/ttyS*
    
  2. Убедитесь, что настройки порта совпадают с настройками принтера:
    [printer]
    type = "serial"
    path = "/dev/ttyS0"
    baud_rate = 9600
    data_bits = 8
    parity = "none"
    stop_bits = 1
    flow_control = "none"
    
  3. Для Windows используйте имя вида COM3:
    [printer]
    path = "COM3"
    
  4. Проверьте, что другие приложения не используют этот порт
  5. Для Linux добавьте пользователя в группы dialout и uucp
  6. Проверьте кабель подключения и контакты

Проблема: Ошибки при работе с Docker

  1. Убедитесь, что Docker запущен:
    docker --version
    
  2. Проверьте права доступа для Docker
  3. Для правильной работы USB-устройств в Docker используйте флаг --device:
    docker run -d --name estrella --device=/dev/usb/lp0 -p 8080:8080 ghcr.io/example/estrella:latest
    
  4. Для сетевых принтеров проверьте настройки сети Docker
  5. Для использования томов:
    docker run -d -v /path/to/config.toml:/etc/estrella/config.toml -v /var/log/estrella:/var/log/estrella ghcr.io/example/estrella:latest
    
  6. Проверьте логи Docker:
    docker logs estrella
    
  7. Для использования Docker Compose:
    version: '3'
    services:
      estrella:
        image: ghcr.io/example/estrella:latest
        ports:
          - "8080:8080"
        volumes:
          - ./config.toml:/etc/estrella/config.toml
          - ./logs:/var/log/estrella
        devices:
          - "/dev/usb/lp0:/dev/usb/lp0"
    

Заключение

Estrella предоставляет мощный и гибкий способ интеграции термопринтеров чеков в ваши приложения. Благодаря написанию на Rust, сервер демонстрирует высокую производительность и низкое потребление ресурсов. Благодаря поддержке различных протоколов и моделей принтеров, а также удобному API, Estrella является отличным выбором для торговых систем, ресторанов и других бизнес-приложений.

В этом руководстве мы рассмотрели установку, настройку, использование и интеграцию сервера Estrella. Надеемся, эта информация поможет вам быстро настроить и использовать Estrella в ваших проектах.

Для получения дополнительной информации посетите:

Если у вас возникнут вопросы или предложения по улучшению, не стесняйтесь обращаться к сообществу разработчиков Estrella через GitHub Discussions или Telegram-чат.

Поделиться:TelegramX / TwitterVK