Перейти до вмісту

Фіскальний реєстратор МІНІ-ФП54.01

Матеріал з K2 ERP Wiki Ukraine — База знань з автоматизації та санкцій в Україні


МІНІ-ФП54.01 — портативний фіскальний реєстратор виробництва Юнісістем. Пристрій має акумулятор, контрольну стрічку в електронній формі, інтерфейси підключення до ПК через USB або RS-232, а також канали передачі даних через Ethernet та GSM/GPRS.

Модель підходить для роздрібної торгівлі, виїзної торгівлі, інтернет-магазинів, аптек, кафе, барів, ресторанів та сфери послуг.

Короткий опис

Параметр Значення
Назва МІНІ-ФП54.01
Тип пристрою Фіскальний реєстратор
Виробник Юнісістем
Версія ПЗ 5401F3
Підключення до ПК USB або RS-232
Передача даних Ethernet, GSM/GPRS
Bluetooth Опція
Основний офіційний спосіб інтеграції DLL / OLE-сервер для Windows
Альтернативна інтеграція Пряма робота через протокол обміну

Важливі висновки для інтеграції

Статус Висновок Коментар
Рекомендовано Для Windows використовувати офіційний DLL / OLE-сервер Це найшвидший шлях інтеграції з POS-системою або Python-застосунком через COM/OLE.
Можливо Для Linux/macOS можна реалізувати власний драйвер через serial-порт Потрібна реалізація протоколу обміну з фіскальним реєстратором.
Обережно Офіційних Python-прикладів не знайдено Python можна використовувати через pywin32 або pyserial, але готового SDK саме для Python на сторінці підтримки немає.
Не робити Не підключати одночасно USB і RS-232 до одного ПК Для роботи з ПК потрібно використовувати один інтерфейс підключення.
Не плутати Ethernet/GSM не є основним інтерфейсом POS-команд Ethernet і GSM/GPRS використовуються переважно для передачі даних до ДПС.

Основні характеристики

Характеристика Значення
Кількість товарів 16 384
Кількість відділів 64
Кількість касирів 32
Кількість символів у рядку 32
Кількість символів у назві товару 48
Початкове повідомлення 12 рядків
Заключне повідомлення 2 рядки
Дисплей покупця Вбудований, 2×16 символів
Друк Термодрук
Ширина стрічки 58 мм
Швидкість друку 8 рядків/с
Живлення Вбудований Li-Pol акумулятор
Акумулятор 7,4 В; 2 А·год, опційно 3,6 А·год
RS-232 1 порт
USB 1 порт
Bluetooth Опція
Грошова скринька micro-jack 2,5 мм
Ethernet Є
GSM/GPRS Вбудований
Габарити 181 × 123 × 102 мм
Маса 0,73 кг

Підключення до компʼютера

МІНІ-ФП54.01 може підключатися до компʼютера через:

  • USB;
  • RS-232 / COM-порт.

Типова схема підключення:

POS-система / касова програма
        ↓
Драйвер або власна інтеграція
        ↓
USB або RS-232
        ↓
МІНІ-ФП54.01

USB-підключення

USB-підключення зручне для сучасних ПК і ноутбуків. Після встановлення USB-драйвера пристрій зазвичай працює як віртуальний COM-порт.

Приклад:

Windows:
COM3, COM4, COM5 ...

Linux:
 /dev/ttyUSB0
 /dev/ttyACM0

macOS:
 /dev/tty.usbserial-*
 /dev/tty.usbmodem-*
Статус Примітка
Добре USB простіше використовувати на сучасних робочих місцях.
Перевірити Потрібно встановити USB-драйвер виробника.
Ризик У різних ОС назва порту буде різною, тому її не можна жорстко зашивати в код.

RS-232 / COM-порт

RS-232 підходить для класичних POS-систем, касових терміналів і embedded-рішень.

Статус Примітка
Добре Serial-протокол простіше реалізувати кросплатформно.
Перевірити Потрібен фізичний COM-порт або USB-RS232 адаптер.
Ризик Дешеві USB-RS232 адаптери можуть давати нестабільний звʼязок.

Ethernet, GSM/GPRS і Bluetooth

Пристрій має Ethernet-порт і GSM/GPRS-модем для передачі даних.

Інтерфейс Призначення Коментар
USB Підключення до ПК Основний варіант для POS-інтеграції.
RS-232 Підключення до ПК Зручний для serial-інтеграції.
Ethernet Передача даних через інтернет Використовується для звʼязку з ДПС.
GSM/GPRS Передача даних через мобільну мережу Дає змогу працювати без дротового інтернету.
Bluetooth Опційне підключення Може використовуватися зі смартфоном або планшетом за наявності сумісного ПЗ.

Операційні системи

Windows

Для Windows виробник надає:

  • USB-драйвер;
  • DLL-бібліотеку / OLE-сервер;
  • програму UNI-PROGress для налаштування РРО;
  • Uniq Commander для налаштування комунікаційного блоку;
  • обробку для «1С:Підприємство».

Рекомендована схема для Windows:

POS-система
    ↓
Python / C# / 1C / інше ПЗ
    ↓
OLE/DLL Юнісістем
    ↓
USB або COM
    ↓
МІНІ-ФП54.01
Статус Висновок
Рекомендовано Windows — найпростіша ОС для інтеграції через офіційний OLE/DLL.
Плюс Можна використовувати Python через pywin32.
Уточнити Потрібно знати реальний ProgID OLE-сервера і точні назви методів.

Linux

Linux не є основною цільовою платформою для офіційного OLE/DLL-драйвера. Але інтеграція можлива через serial-порт, якщо реалізувати протокол обміну.

Схема:

POS-система
    ↓
Python-сервіс
    ↓
pyserial
    ↓
/dev/ttyUSB0 або /dev/ttyS0
    ↓
МІНІ-ФП54.01
Статус Висновок
Можливо Linux може працювати з пристроєм через serial-порт.
Потрібно Реалізувати протокол обміну.
Немає Готового офіційного Python SDK для Linux не знайдено.

macOS

Для macOS ситуація аналогічна Linux: готового офіційного OLE/DLL-драйвера немає, але можна працювати через serial-порт, якщо система бачить пристрій як `/dev/tty.*`.

Статус Висновок
Можливо Потрібна власна serial-інтеграція.
Не готово з коробки Офіційного macOS SDK не знайдено.

Варіанти інтеграції

Варіант 1. Через OLE/DLL у Windows

Це найпростіший варіант для Windows.

Python → pywin32 → OLE-сервер → USB/COM → МІНІ-ФП54.01

Переваги:

  • не потрібно вручну реалізовувати низькорівневий протокол;
  • простіше стартувати;
  • можна викликати готові методи драйвера;
  • підходить для POS-систем на Windows.

Недоліки:

  • залежність від Windows;
  • залежність від конкретної версії OLE-сервера;
  • потрібно встановити та зареєструвати COM/OLE-компонент;
  • потрібно знати ProgID і назви методів.

Варіант 2. Через serial-порт напряму

Цей варіант підходить для Windows, Linux, macOS та embedded-систем.

Python → pyserial → протокол обміну → USB/COM або RS-232 → МІНІ-ФП54.01

Переваги:

  • кросплатформність;
  • незалежність від OLE;
  • можна запускати як локальний сервіс;
  • зручно для Docker, Linux POS, Raspberry Pi, embedded-терміналів.

Недоліки:

  • потрібно реалізувати протокол обміну;
  • потрібно обробляти контрольні суми, ACK/NAK, таймаути, повтори;
  • складніше тестувати;
  • потрібна офіційна документація протоколу.

Python-інтеграція

Офіційних прикладів саме для Python на сторінці підтримки не знайдено. Але Python можна використовувати у двох режимах:

Варіант Пакет Python ОС Коментар
OLE/DLL pywin32 Windows Найпростіший варіант, якщо встановлено OLE-сервер.
Serial pyserial Windows / Linux / macOS Потрібна реалізація протоколу обміну.
Mock-драйвер pytest / unittest Будь-яка Для тестування без фізичного пристрою.

Python через OLE/DLL у Windows

Встановлення залежності

 pip install pywin32

Пошук ProgID OLE-сервера

Після встановлення OLE-сервера потрібно знайти його ProgID.

Приклад через PowerShell:

 Get-ChildItem "Registry::HKEY_CLASSES_ROOT" |
  Where-Object { $_.Name -match "Ecr|T400|MINI|FP|Unisystem|Uni" } |
  Select-Object -First 50

Приклад через Python:

 import winreg

keywords = ["Ecr", "T400", "MINI", "FP", "Unisystem", "Uni"]

with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, "") as root:
    total = winreg.QueryInfoKey(root)[0]

    for index in range(total):
        try:
            name = winreg.EnumKey(root, index)
            if any(keyword.lower() in name.lower() for keyword in keywords):
                print(name)
        except OSError:
            pass
Статус Коментар
Важливо Назва ProgID у прикладах нижче умовна. Її треба уточнити після встановлення реального OLE-сервера.

Базова структура Python-обгортки

 import win32com.client

class MiniFP54OleDriver:
    """
    Python-обгортка над OLE-сервером Юнісістем.

    Увага:
    - ProgID потрібно взяти з реальної інсталяції OLE-сервера.
    - Назви методів потрібно звірити з документацією до конкретної версії OLE.
    """

    def __init__(self, prog_id: str):
        self.prog_id = prog_id
        self.driver = None

    def connect_to_ole(self):
        self.driver = win32com.client.Dispatch(self.prog_id)

    def open_port(self, port: int, baudrate: int = 115200):
        """
        Приклад.
        Реальна назва методу може бути OpenPort, openPort або іншою.
        """
        return self.driver.OpenPort(port, baudrate)

    def close_port(self):
        return self.driver.ClosePort()

    def get_status(self):
        return self.driver.GetStatus()

    def get_version(self):
        return self.driver.GetSoftVersion()

    def open_shift(self):
        return self.driver.OpenShift()

    def close_shift(self):
        return self.driver.ZReport()

    def open_receipt(self):
        return self.driver.OpenReceipt()

    def sell_item(self, name: str, price: float, quantity: float, tax_group: int = 1):
        return self.driver.Sale(name, quantity, price, tax_group)

    def pay_cash(self, amount: float):
        payment_type = 0
        return self.driver.Pay(payment_type, amount)

    def close_receipt(self):
        return self.driver.CloseReceipt()

Приклад smoke-тесту через OLE

 from mini_fp54_ole import MiniFP54OleDriver

PROG_ID = "PUT_REAL_PROG_ID_HERE"

driver = MiniFP54OleDriver(PROG_ID)

driver.connect_to_ole()

try:
    print("Open port:", driver.open_port(port=3, baudrate=115200))
    print("Version:", driver.get_version())
    print("Status:", driver.get_status())
finally:
    print("Close port:", driver.close_port())

Приклад продажу через OLE

 from mini_fp54_ole import MiniFP54OleDriver

driver = MiniFP54OleDriver("PUT_REAL_PROG_ID_HERE") driver.connect_to_ole()

try:
    driver.open_port(port=3, baudrate=115200)

    driver.open_shift()
    driver.open_receipt()

    driver.sell_item(
        name="Кава",
        price=50.00,
        quantity=1,
        tax_group=1,
    )

    driver.pay_cash(50.00)
    driver.close_receipt()
finally:
    driver.close_port()
Статус Що треба перевірити перед запуском
Обовʼязково Чи правильний ProgID OLE-сервера.
Обовʼязково Чи правильні назви методів.
Обовʼязково Чи правильний COM-порт.
Не запускати в production Поки не перевірено на тестовому РРО або у нефіскальному режимі.

Python через serial-порт

Встановлення залежності

 pip install pyserial

Базовий serial-клієнт

 import serial from dataclasses import dataclass

@dataclass class MiniFP54SerialConfig:
    port: str
    baudrate: int = 115200
    timeout: float = 3.0
    encoding: str = "cp1251"
class MiniFP54SerialClient:
    """
    Базовий serial-клієнт для МІНІ-ФП54.01.

    Це не повний драйвер.
    Для реальної роботи потрібно реалізувати офіційний протокол:
    - формат пакета;
    - службові байти;
    - довжину;
    - контрольну суму;
    - ACK/NAK;
    - повтори;
    - таймаути;
    - парсинг статусів і помилок.
    """

    def __init__(self, config: MiniFP54SerialConfig):
        self.config = config
        self.connection: serial.Serial | None = None

    def connect(self):
        self.connection = serial.Serial(
            port=self.config.port,
            baudrate=self.config.baudrate,
            timeout=self.config.timeout,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
        )

    def disconnect(self):
        if self.connection and self.connection.is_open:
            self.connection.close()

    def send_raw(self, payload: bytes, read_size: int = 1024) -> bytes:
        if not self.connection or not self.connection.is_open:
            raise RuntimeError("Serial port is not open")

        self.connection.write(payload)
        self.connection.flush()

        return self.connection.read(read_size)

    def send_text_command(self, command: str) -> str:
        """
        Умовний приклад текстової команди.
        Реальний формат треба взяти з протоколу обміну.
        """
        payload = command.encode(self.config.encoding)
        response = self.send_raw(payload)

        return response.decode(self.config.encoding, errors="replace")

Приклад smoke-тесту через serial

 from mini_fp54_serial import MiniFP54SerialClient, MiniFP54SerialConfig

client = MiniFP54SerialClient(
    MiniFP54SerialConfig(
        port="COM3",       # Linux: "/dev/ttyUSB0"
        baudrate=115200,
        timeout=3.0,
    )
)

client.connect()

try:
    response = client.send_text_command("GET_STATUS")
    print(response)
finally:
    client.disconnect()
Статус Коментар
Умовний приклад Команда GET_STATUS наведена як приклад. Реальну команду потрібно брати з протоколу обміну.
Корисно Така структура підходить для побудови власного драйвера.
Не достатньо Без реалізації контрольної суми та службових байтів це не production-драйвер.

Тестування драйвера без пристрою

Без фізичного МІНІ-ФП54.01 можна тестувати не сам фіскальний реєстратор, а логіку інтеграції.

Мета тестування без пристрою:

  • перевірити бізнес-логіку POS-системи;
  • перевірити порядок команд;
  • перевірити обробку помилок;
  • перевірити логування;
  • перевірити поведінку при timeout;
  • перевірити поведінку при відсутності паперу;
  • перевірити поведінку при розриві зʼєднання.

Архітектура тестування без пристрою

POS-система
    ↓
FiscalDriverInterface
    ↓
FakeMiniFP54Driver
    ↓
Unit / integration tests

Інтерфейс драйвера

 from abc import ABC, abstractmethod

class FiscalDriverInterface(ABC):
    @abstractmethod
    def open_shift(self) -> str:
        pass

    @abstractmethod
    def close_shift(self) -> str:
        pass

    @abstractmethod
    def open_receipt(self) -> str:
        pass

    @abstractmethod
    def sell_item(self, name: str, price: float, quantity: float) -> str:
        pass

    @abstractmethod
    def pay_cash(self, amount: float) -> str:
        pass

    @abstractmethod
    def close_receipt(self) -> str:
        pass

    @abstractmethod
    def get_status(self) -> str:
        pass

Fake-драйвер

 class FakeMiniFP54Driver(FiscalDriverInterface):
    def __init__(self):
        self.shift_opened = False
        self.receipt_opened = False
        self.items = []
        self.total = 0.0

    def open_shift(self) -> str:
        if self.shift_opened:
            return "ERROR;SHIFT_ALREADY_OPEN"

        self.shift_opened = True
        return "OK;SHIFT_OPENED"

    def close_shift(self) -> str:
        if not self.shift_opened:
            return "ERROR;SHIFT_NOT_OPEN"

        if self.receipt_opened:
            return "ERROR;RECEIPT_IS_OPEN"

        self.shift_opened = False
        return "OK;Z_REPORT_PRINTED"

    def open_receipt(self) -> str:
        if not self.shift_opened:
            return "ERROR;SHIFT_NOT_OPEN"

        if self.receipt_opened:
            return "ERROR;RECEIPT_ALREADY_OPEN"

        self.receipt_opened = True
        self.items = []
        self.total = 0.0

        return "OK;RECEIPT_OPENED"

    def sell_item(self, name: str, price: float, quantity: float) -> str:
        if not self.receipt_opened:
            return "ERROR;RECEIPT_NOT_OPEN"

        if price <= 0:
            return "ERROR;INVALID_PRICE"

        if quantity <= 0:
            return "ERROR;INVALID_QUANTITY"

        amount = round(price * quantity, 2)
        self.items.append(
            {
                "name": name,
                "price": price,
                "quantity": quantity,
                "amount": amount,
            }
        )
        self.total = round(self.total + amount, 2)

        return f"OK;ITEM_ADDED;{amount:.2f}"

    def pay_cash(self, amount: float) -> str:
        if not self.receipt_opened:
            return "ERROR;RECEIPT_NOT_OPEN"

        if amount < self.total:
            return "ERROR;PAYMENT_LESS_THAN_TOTAL"

        change = round(amount - self.total, 2)

        return f"OK;PAYMENT_ACCEPTED;CHANGE={change:.2f}"

    def close_receipt(self) -> str:
        if not self.receipt_opened:
            return "ERROR;RECEIPT_NOT_OPEN"

        if not self.items:
            return "ERROR;EMPTY_RECEIPT"

        self.receipt_opened = False

        return f"OK;RECEIPT_CLOSED;TOTAL={self.total:.2f}"

    def get_status(self) -> str:
        return (
            "OK;"
            f"SHIFT_OPENED={self.shift_opened};"
            f"RECEIPT_OPENED={self.receipt_opened}"
        )

Unit-тест успішного продажу

 def test_success_sale_flow():
    driver = FakeMiniFP54Driver()

    assert driver.open_shift() == "OK;SHIFT_OPENED"
    assert driver.open_receipt() == "OK;RECEIPT_OPENED"

    result = driver.sell_item("Кава", price=50.00, quantity=2)
    assert result == "OK;ITEM_ADDED;100.00"

    result = driver.pay_cash(100.00)
    assert result == "OK;PAYMENT_ACCEPTED;CHANGE=0.00"

    result = driver.close_receipt()
    assert result == "OK;RECEIPT_CLOSED;TOTAL=100.00"

    result = driver.close_shift()
    assert result == "OK;Z_REPORT_PRINTED"

Unit-тест помилки: чек без відкритої зміни

 def test_open_receipt_without_shift():
    driver = FakeMiniFP54Driver()

    result = driver.open_receipt()

    assert result == "ERROR;SHIFT_NOT_OPEN"

Unit-тест помилки: оплата менша за суму чека

 def test_payment_less_than_total():
    driver = FakeMiniFP54Driver()

    driver.open_shift()
    driver.open_receipt()
    driver.sell_item("Кава", price=50.00, quantity=2)

    result = driver.pay_cash(50.00)

    assert result == "ERROR;PAYMENT_LESS_THAN_TOTAL"

Емуляція serial-пристрою без фізичного РРО

Для тестування serial-драйвера без пристрою можна зробити локальний емулятор.

Варіант для Linux/macOS

Можна створити пару віртуальних serial-портів через `socat`.

 socat -d -d pty,raw,echo=0 pty,raw,echo=0

Після запуску `socat` покаже два порти, наприклад:

/dev/pts/3
/dev/pts/4

Один порт використовує тестовий POS-драйвер, інший — емулятор пристрою.

Простий Python-емулятор

 import serial

PORT = "/dev/pts/4" BAUDRATE = 115200

def handle_command(command: str) -> str:
    command = command.strip()

    if command == "GET_STATUS":
        return "OK;READY\n"

    if command == "GET_VERSION":
        return "OK;MINI-FP54.01;5401F3\n"

    if command.startswith("SALE"):
        return "OK;ITEM_ADDED\n"

    if command.startswith("PAY"):
        return "OK;PAYMENT_ACCEPTED\n"

    if command == "CLOSE_RECEIPT":
        return "OK;RECEIPT_CLOSED\n"

    return "ERROR;UNKNOWN_COMMAND\n"
with serial.Serial(PORT, BAUDRATE, timeout=1) as ser:
    print(f"Fake MINI-FP54.01 emulator started on {PORT}")

    while True:
        raw = ser.readline()

        if not raw:
            continue

        command = raw.decode("cp1251", errors="replace")
        response = handle_command(command)

        ser.write(response.encode("cp1251"))
        ser.flush()
Статус Коментар
Добре для розробки Емулятор дозволяє тестувати POS-сценарії без фізичного пристрою.
Не замінює РРО Емулятор не перевіряє реальний фіскальний протокол.
Не для production Не можна вважати інтеграцію готовою без тестів із реальним пристроєм.

Тестування з реальним пристроєм

Підготовка

  1. Встановити USB-драйвер виробника.
  2. Встановити DLL/OLE-сервер, якщо використовується Windows.
  3. Підключити пристрій через USB або RS-232.
  4. Переконатися, що використовується тільки один інтерфейс.
  5. Перевірити, який COM-порт отримав пристрій.
  6. Перевірити наявність паперу.
  7. Перевірити заряд акумулятора або живлення.
  8. Перевірити Ethernet або GSM/GPRS для передачі даних.
  9. Виконати команду отримання статусу.
  10. Виконати тестовий друк або службову операцію.

Мінімальний чек-лист smoke-тесту

Тест Очікуваний результат
1 Відкрити порт Драйвер не повертає помилку
2 Отримати статус Пристрій відповідає
3 Отримати версію ПЗ Повертається версія пристрою
4 Перевірити папір Немає помилки паперу
5 Відкрити зміну Зміна відкрита
6 Відкрити чек Чек відкритий
7 Додати товар Рядок товару надруковано або прийнято
8 Провести оплату Оплата прийнята
9 Закрити чек Чек закритий
10 Надрукувати X-звіт Звіт друкується без закриття зміни
11 Надрукувати Z-звіт Зміна закрита
12 Перевірити передачу даних Дані передаються через Ethernet або GSM/GPRS

Робота в реальному режимі

Реальний режим — це робота з фіскалізованим пристроєм, який використовується для реєстрації розрахункових операцій.

Перед запуском

Перед production-запуском потрібно:

  1. Зареєструвати РРО.
  2. Виконати фіскалізацію.
  3. Виконати персоналізацію.
  4. Налаштувати податкові групи.
  5. Налаштувати типи оплат.
  6. Налаштувати касирів.
  7. Налаштувати заголовок і підвал чека.
  8. Перевірити передачу даних.
  9. Перевірити роботу POS-системи з реальним драйвером.
  10. Провести тестовий день роботи.
Статус Правило
Обовʼязково Усі операції продажу мають проходити через фіскальний реєстратор.
Обовʼязково Наприкінці зміни потрібно виконувати Z-звіт.
Контроль Потрібно перевіряти статус передачі даних.
Небезпечно Не можна ігнорувати помилки фіскального реєстратора.

Типовий робочий цикл

1. Увімкнути фіскальний реєстратор.
2. Перевірити звʼязок із POS-системою.
3. Перевірити папір.
4. Відкрити зміну.
5. Пробивати чеки продажу.
6. За потреби виконувати повернення.
7. За потреби друкувати X-звіт.
8. Наприкінці дня друкувати Z-звіт.
9. Перевірити передачу даних.

Обробка помилок

Типові помилки інтеграції

Помилка Причина Що робити
Немає звʼязку з пристроєм Неправильний COM-порт або кабель Перевірити порт, кабель, драйвер, живлення
Timeout Пристрій не відповідає Повторити команду, перевірити стан РРО
Port busy Порт зайнятий іншою програмою Закрити інші програми, які використовують COM-порт
Немає паперу Закінчилась чекова стрічка Замінити папір і повторити операцію
Зміна не відкрита POS намагається пробити чек без відкритої зміни Спочатку виконати відкриття зміни
Чек уже відкритий Попередній чек не закритий Закрити або анулювати чек
Помилка передачі даних Немає Ethernet/GSM-звʼязку Перевірити інтернет, SIM-карту, налаштування

Рекомендований retry-механізм

 import time

class FiscalCommandError(Exception):
    pass
def execute_with_retry(command, retries: int = 3, delay: float = 1.0):
    last_error = None

    for attempt in range(1, retries + 1):
        try:
            result = command()

            if isinstance(result, str) and result.startswith("OK"):
                return result

            last_error = result

        except Exception as exc:
            last_error = exc

        time.sleep(delay)

    raise FiscalCommandError(f"Command failed after {retries} retries: {last_error}")

Логування

Для production-інтеграції потрібно логувати:

  • дату й час команди;
  • тип команди;
  • параметри команди;
  • відповідь пристрою;
  • код помилки;
  • COM-порт;
  • тривалість виконання;
  • номер чека;
  • номер зміни.

Приклад логування в Python

 import logging import time

logging.basicConfig(
    filename="mini_fp54.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)

def log_fiscal_command(command_name: str, callback):
    started_at = time.time()

    try:
        result = callback()
        duration = round(time.time() - started_at, 3)

        logging.info(
            "command=%s duration=%s result=%s",
            command_name,
            duration,
            result,
        )

        return result

    except Exception as exc:
        duration = round(time.time() - started_at, 3)

        logging.exception(
            "command=%s duration=%s error=%s",
            command_name,
            duration,
            exc,
        )

        raise

Рекомендована структура Python-проєкту

mini_fp54/
├── __init__.py
├── interfaces.py
├── ole_driver.py
├── serial_driver.py
├── fake_driver.py
├── errors.py
├── logger.py
├── config.py
└── tests/
    ├── test_fake_driver.py
    ├── test_sale_flow.py
    └── test_errors.py

Призначення файлів

Файл Призначення
interfaces.py Спільний інтерфейс драйвера
ole_driver.py Робота через OLE/DLL у Windows
serial_driver.py Робота через serial-порт
fake_driver.py Mock-драйвер для тестів
errors.py Власні класи помилок
logger.py Логування команд і відповідей
config.py Налаштування порту, швидкості, режиму роботи
tests/ Unit та integration-тести

Production checklist

Статус Перевірка
USB-драйвер встановлено
OLE/DLL встановлено, якщо використовується Windows
COM-порт визначено
Отримання статусу працює
Відкриття зміни працює
Продаж товару працює
Оплата готівкою працює
Оплата карткою працює
Закриття чека працює
X-звіт працює
Z-звіт працює
Передача даних працює
Перевірено поведінку при timeout
Перевірено поведінку при відсутності паперу
Перевірено поведінку при втраті інтернету
Немає hardcoded COM-порту в production-коді
Немає ігнорування помилок РРО

Висновок

МІНІ-ФП54.01 найпростіше інтегрувати у Windows через офіційний DLL/OLE-сервер. Python можна використовувати через пакет `pywin32`, якщо встановлено та зареєстровано OLE-сервер.

Для Linux, macOS або embedded-систем можлива інтеграція через `pyserial`, але для цього потрібно реалізувати протокол обміну з пристроєм. Такий варіант гнучкіший, але складніший і потребує повного тестування з реальним фіскальним реєстратором.

Для розробки без фізичного пристрою варто використовувати mock-драйвер або serial-емулятор. Це дозволяє перевірити POS-логіку, але не замінює тестування з реальним РРО.

Джерела