360 lines
43 KiB
Markdown
360 lines
43 KiB
Markdown
# Exchanger
|
||
|
||
Exchanger — полноэкранное тач-приложение для напольного разменника, продающего и выдающего жетоны за наличную или безналичную оплату. Проект разрабатывается на Godot 4.7 Mono/C# для Windows x64 с расчётом на будущий перенос аппаратного транспорта на Android ARM64.
|
||
|
||
Целевой аппарат использует портретный тач-экран, показывает цену и бинарное состояние запаса жетонов («много»/«мало»), принимает наличные через купюро- и монетоприёмник либо запускает оплату на отдельном банковском терминале, после чего безопасно инициирует выдачу. События оплаты, баланса, запаса и выдачи должны поступать от внешнего контроллера автомата.
|
||
|
||
## Текущее состояние
|
||
|
||
Завершены Этапы 0–3, программная часть Этапа 4 и весь программный пакет Этапа 6; приложение проходит полный пользовательский сценарий на типизированном mock-контроллере и документированный wire-профиль 2.3.5 на детерминированном симуляторе. После обновления авторизации физическая плата на COM3 прошла отдельный строгий auth-probe 1/1 и безопасную автоматическую приёмку 8/9: startup-баннер, AES-handshake, точный `READY!`, цифровые входы, четыре status-запроса и три цикла переподключения работают; отдельный запрос состояния Вендиста `160` дважды остался без ответа. Интерактивные платежи, выдача и отключение USB остаются отдельными явно подтверждаемыми проверками.
|
||
|
||
Указанные результаты COM3 относятся к предыдущему состоянию прошивки. После текущего обновления протокола плата отключена, поэтому новая разводка и команды пока проверены только симулятором; аппаратная приёмка отложена до её подключения.
|
||
|
||
- главная сцена `src/scenes/App/App.tscn`;
|
||
- портретный `HomeScreen` с ценой, запасом, двумя способами оплаты, промо-fallback и поддержкой;
|
||
- рабочие ветки наличной и карточной оплаты, включая другую сумму, ожидание терминала, успех и отказ;
|
||
- `ScreenManager` Autoload с централизованным реестром, fade-переходами и блокировкой повторного ввода;
|
||
- единственная встроенная программная fallback-тема;
|
||
- безопасная замена fallback-темы одним внешним DLC-пакетом `.pck` при запуске;
|
||
- статический фон DLC-темы с отдельным слоем анимированных спрайтов;
|
||
- облегчённый режим, отключающий необязательные фоновые анимации;
|
||
- типизированная внешняя конфигурация;
|
||
- файловый журнал с ротацией;
|
||
- фильтрация вторичных касаний;
|
||
- аппаратный интерфейс `IHardwarePort`;
|
||
- рабочие `MockMachineController`, `PeripheryControllerSimulator`, Windows COM-транспорт и безопасная Android-заглушка;
|
||
- конфигурируемые тарифы, бонусы, карточные пресеты, способы оплаты, пользовательские таймауты, брендинг, промо и оформление;
|
||
- актуальная привязка цифровых входов платы: `IN8` — сервисная кнопка, `IN1` — сигнал «мало жетонов» первого хоппера, `IN24` — его датчик выдачи, `IN4` — сигнал «мало жетонов» второго хоппера; вход датчика выдачи второго хоппера пока не определён;
|
||
- компонент состояния запаса жетонов `Enough/Low/Unknown` без фиктивного точного количества;
|
||
- отдельный подтверждаемый владельцем учётный остаток жетонов: ручное пополнение, списание подтверждённой выдачи и автоматический пересчёт активного хоппера;
|
||
- безопасное состояние соединения `Disconnected/Authenticating/Ready/Faulted` и ненавязчивый сервисный индикатор на домашнем экране;
|
||
- `SessionController` с явными состояниями, таймаутами и защитой от повторной выдачи;
|
||
- чистый `SessionFlow`, уникальный локальный контекст операции и управляемый монотонный источник времени для тестов;
|
||
- типизированные безопасные ошибки и отдельный JSONL-журнал начала/итога операций;
|
||
- `AdvertisementPlayer` с локальным плейлистом, постером/текстовым fallback и остановкой вне HomeScreen.
|
||
- production kiosk-профиль с borderless fullscreen, скрытым курсором и восстановлением окна после
|
||
потери фокуса либо изменения дисплея;
|
||
- сервисный вход по аппаратному `SERVICE`: защита включается в самом приложении, первый защищённый вход предлагает дважды задать единый сервисный PIN, последующие входы требуют его проверки; допустимые изменения сохраняются автоматически, повторное нажатие сервисной кнопки возвращает на главный экран;
|
||
- шестираздельный редактор настроек владельца автомата: общие данные, звук, промо, тарифы, оплата и сервисный учёт жетонов;
|
||
технические параметры, диагностика и тестовая выдача в пользовательском UI отсутствуют;
|
||
- Windows watchdog, задача автозапуска и process soak-runner.
|
||
|
||
По документации контроллера 2.3.5 реализованы Windows COM-транспорт, framing, строгий startup-handshake и платёжные команды. Порядок авторизации сверен с `plate_config_app`: RTS-reset, баннер `periphery controller ver_<version>`, пауза 2000 мс, `07 → CHAL → 08 → RAES` и допуск к работе только после точной строки `READY!`. Оба хоппера включены в целевой `DualHopperAuto`: первый использует `309N/3090` и `301/300`, второй — `409N/4090` и `401/400`; фактическая выдача подтверждается `HOPPER1/2 OK`, пересчёт — `HOPPER1/2 PULSE=1`. Учётные остатки хранятся отдельно, а выдача списывается с фактически сработавшего канала. Универсальная конфигурация репозитория сохраняет безопасный `Unsupported`, пока профиль конкретного автомата не выбран явно. Подробности: [docs/hardware/PERIPHERY_CONTROLLER_2_3_5.md](docs/hardware/PERIPHERY_CONTROLLER_2_3_5.md). Актуальный прогресс перечислен в [ROADMAP.md](ROADMAP.md).
|
||
|
||
После завершения Этапа 1 были изучены визуальные референсы реального корпуса и интерфейса. Они уточнили целевую портретную ориентацию и две разные платёжные ветки. Ландшафтный `StartScreen` удалён и заменён портретным домашним экраном. Подробные выводы находятся в [docs/REFERENCE_ANALYSIS.md](docs/REFERENCE_ANALYSIS.md).
|
||
|
||
Этап 0 завершён. Базовые договорённости, включая универсальность до появления целевого железа и отложенную интеграцию контроллера, зафиксированы в [docs/architecture/decisions.md](docs/architecture/decisions.md).
|
||
|
||
Команды `309N` и `409N` ограничены 99 жетонами: `309100` и `409100` возвращают ошибки групп 30 и 40. Приложение автоматически разбивает крупную выдачу на последовательные пакеты максимум по 99 жетонов и маршрутизирует их между двумя учётными остатками.
|
||
|
||
## Целевой интерфейс по референсам
|
||
|
||
- Портретный формат 9:16; нативная сетка интерфейса и preview — 720×1280.
|
||
- Пиксельная космическая тема, согласованная с оформлением корпуса: сам фон статичен, а НЛО, персонажи и другие движущиеся элементы реализуются отдельными анимированными спрайтами.
|
||
- Домашний экран без дополнительного шага «Начать»: цена жетона, запас, способы оплаты, кнопка выдачи и промо-ролик.
|
||
- Наличная ветка: текущий баланс, расчёт жетонов/бонуса, инструкции для купюр и монет.
|
||
- Карточная ветка: предустановленные суммы, ввод другой суммы, ожидание прикладывания карты и результат.
|
||
- Постоянно видимые брендовые элементы и телефон техподдержки.
|
||
- Маскот и фоновые анимации, не перекрывающие функциональные панели.
|
||
|
||
Промо-ролик, показанный в референсах, является отдельным ограниченным блоком домашнего экрана и не используется как фон приложения.
|
||
|
||
Конкретные числа и графические материалы из референсов пока считаются демонстрационными. Перед production необходимо подтвердить тарифы, бонусы, бренд и права на ассеты.
|
||
|
||
## Технологии
|
||
|
||
| Компонент | Значение |
|
||
| --- | --- |
|
||
| Движок | Godot 4.7.1 Mono |
|
||
| Язык | C# |
|
||
| .NET | .NET 8; Android-конфигурация — .NET 9 |
|
||
| Рендерер | Mobile (Vulkan) |
|
||
| Основная платформа | Windows x64 |
|
||
| Перспективная платформа | Android ARM64 |
|
||
| Целевой профиль железа | Пока не определён; приложение проектируется аппаратно-независимым и конфигурируемым |
|
||
| Логический viewport | 720×1280, портретный 9:16, `canvas_items/expand` |
|
||
| Debug-window | 720×1280 |
|
||
| Аппаратный транспорт MVP | COM/Serial на Windows |
|
||
|
||
## Требования для разработки
|
||
|
||
- Godot 4.7.1 Mono или совместимая версия Godot 4.7 Mono.
|
||
- .NET SDK 8.
|
||
- Windows x64 для текущей целевой сборки.
|
||
|
||
Путь к редактору, используемый локальной конфигурацией VS Code, хранится в `.vscode/settings.json`. На другой машине его необходимо изменить на фактический путь к Godot Mono.
|
||
|
||
## Запуск
|
||
|
||
### Через Godot Editor
|
||
|
||
1. Открыть `project.godot` в Godot 4.7.1 Mono.
|
||
2. Дождаться импорта ресурсов и сборки C#-проекта.
|
||
3. Запустить главную сцену клавишей `F6` либо весь проект клавишей `F5`.
|
||
|
||
В debug- и production-сборках интерфейс использует нативную сетку 720×1280. В полноэкранном режиме Godot масштабирует её на портретную панель через `canvas_items/expand`.
|
||
|
||
### Проверка C#-сборки
|
||
|
||
```powershell
|
||
dotnet build Exchanger.sln
|
||
```
|
||
|
||
### Автоматические тесты
|
||
|
||
```powershell
|
||
dotnet test Exchanger.sln
|
||
```
|
||
|
||
Отдельный проект `tests/Exchanger.Tests` использует NUnit 4. Обычный набор тестов выполняется без запуска окна Godot и проверяет конфигурацию, миграции, атомарное сохранение и восстановление настроек, сервисный доступ, учётный остаток и автоматический пересчёт жетонов, безопасный audit, ротацию журналов, настройки оборудования, штатную отмену реального COM-чтения, аудиопараметры, брендинг, промо, DLC-тему, полносценовый binding-контракт, owner-only состав настроек, production export, тарифы, бонусы, карточные суммы, способы оплаты и таймауты, конечный автомат, framing COM-протокола, команды и полные cash/card-сценарии контроллера, обязательные `ASK:*`, взаимоисключение приёмников, строгий startup-handshake `banner → 2000 ms → 07/CHAL/08/RAES → READY!`, AES и восстановление ключа, wire-симулятор, оба формата `INPUT`, актуальную разводку `IN8/IN1/IN4/IN24`, keep-alive Вендиста, at-most-once, раздельный учёт и выдачу обоими хопперами через `309N/409N → APPEND/OK` либо `ERROR/CREDIT`, строковые импульсы раздельного сервисного пересчёта, malformed-кадры, ошибки соединения, JSONL-восстановление и отсутствие накопления подписок. Ускоренный soak моделирует 1440 минут и 480 выдач, а отдельный стресс выполняет 10 000 жизненных циклов `SessionFlow`. Загрузка сцен и ресурсов дополнительно проверяется в Godot Editor через разрешённый проектом MCP-сервер `godot-ai`.
|
||
|
||
Для физической платы подготовлен отдельный `Exchanger.HardwareTests` с 18 безопасными и подтверждаемыми приёмочными проверками, включая изолированный handshake probe. Без явного флага они пропускаются и никогда не открывают COM-порт. Порядок проверки handshake, status-команд, SERVICE, приёмников оплаты, Вендиста, датчика, хоппера и USB-reconnect описан в [docs/hardware/BOARD_ACCEPTANCE_TESTS.md](docs/hardware/BOARD_ACCEPTANCE_TESTS.md).
|
||
|
||
### Headless smoke-тест
|
||
|
||
```powershell
|
||
Godot_v4.7.1-stable_mono_win64.exe --headless --path . --quit-after 10
|
||
```
|
||
|
||
### Windows x64 export
|
||
|
||
```powershell
|
||
.\tools\export-windows.ps1 -Configuration Release
|
||
.\tools\export-windows.ps1 -Configuration Debug
|
||
```
|
||
|
||
Пресеты `Windows x64 Release` и `Windows x64 Debug` разведены также в редакторе. Скрипт использует
|
||
очищенную staging-копию и жёстко выбирает `--export-release` либо `--export-debug`; без параметра
|
||
собирается Release. Готовый self-contained каталог создаётся в `artifacts/windows/`. Подробности и
|
||
smoke-проверка: [docs/operations/WINDOWS_EXPORT.md](docs/operations/WINDOWS_EXPORT.md).
|
||
|
||
## Конфигурация
|
||
|
||
Настройки, доступные владельцу автомата, по умолчанию находятся в:
|
||
|
||
```text
|
||
res://config/default_settings.json
|
||
```
|
||
|
||
При первом запуске приложение создаёт пользовательскую копию:
|
||
|
||
```text
|
||
user://config/appsettings.json
|
||
```
|
||
|
||
В пользовательской конфигурации задаются:
|
||
|
||
- fullscreen/debug-window режим;
|
||
- целевой FPS;
|
||
- разрешение и путь к внешнему DLC-пакету темы;
|
||
- облегчённый режим эффектов;
|
||
- таймауты сессии;
|
||
- общая громкость, master mute и отдельная громкость промо-ролика;
|
||
- доступность наличной и карточной оплаты, карточные пресеты, максимальная сумма и шаг ручного ввода;
|
||
- безопасные таймауты бездействия, ожидания оплаты и показа результата;
|
||
- включение промо, звук промо-роликов, fallback-текст, локальный постер и плейлист OGV/MP4/MOV/M4V;
|
||
- ограничения файлового журнала.
|
||
|
||
Монтажные параметры оборудования вынесены в отдельный TOML-файл:
|
||
|
||
```text
|
||
res://config/technical_settings.toml
|
||
user://config/technical_settings.toml
|
||
```
|
||
|
||
Пользовательская копия создаётся при первом запуске и не показывается в интерфейсе приложения.
|
||
Она задаёт основной последовательный порт (`COM*` в Windows, `/dev/ttyUSB*`, `/dev/ttyACM*`
|
||
или `/dev/serial/by-id/*` в Linux), mock-режим, параметры связи, наличие обоих хопперов, индексы и
|
||
полярность их датчиков, сервисную кнопку и интервалы опроса. TOML применяется поверх устаревшего
|
||
раздела `Hardware` в JSON; аргумент `--mock-hardware` остаётся последним безопасным переопределением.
|
||
Изменения вступают в силу только после полного перезапуска. Схема, диапазоны и порядок проверки:
|
||
[docs/operations/TECHNICAL_CONFIGURATION.md](docs/operations/TECHNICAL_CONFIGURATION.md).
|
||
|
||
Параметр `[controller].reconnect_interval_seconds` задаёт паузу между повторными открытиями порта.
|
||
Штатно это 30 секунд: приложение повторяет подключение, если платы не было при запуске, и начинает
|
||
новый полный отсчёт после отключения работавшей платы. Платёжные и выдающие команды при reconnect
|
||
никогда не повторяются автоматически.
|
||
|
||
Модель настроек содержит цену жетона, бонусные правила, карточные суммы, телефон поддержки и плейлист промо-роликов. Датчики обоих хопперов сообщают только «достаточно» или «мало». Точные подтверждаемые остатки хранятся раздельно в `user://data/token_inventory.json`, начинаются с нуля, уменьшаются после подтверждённой выдачи и меняются отдельными действиями для каждого хоппера в разделе «Сервис». Два независимых переключателя управляют поведением: «Учитывать остаток жетонов» включает ведение и показ точного остатка, а «Не продавать больше остатка» отдельно запрещает покупку сверх сохранённого количества с учётом бонуса. Если ограничение выключено, остаток продолжает отображаться и списываться, но не блокирует оплату. Если выключен сам учёт, скрываются пересчёт, пополнение и приблизительное количество Home, а файл остатка не изменяется. Перед повторным включением учёта после реальных выдач требуется пересчёт.
|
||
|
||
Поддерживаются аргументы запуска:
|
||
|
||
```text
|
||
--windowed
|
||
--fullscreen
|
||
--theme-dlc=D:\ExchangerThemes\active_theme.pck
|
||
--no-theme-dlc
|
||
--mock-hardware=true
|
||
--reduced-effects
|
||
```
|
||
|
||
Конфигурация версии 19 сохраняется через `SettingsService` атомарной заменой файла. Перед изменением
|
||
создаётся `appsettings.json.bak`; повреждённый пользовательский JSON восстанавливается из последней
|
||
корректной копии либо безопасных значений по умолчанию и не прерывает запуск.
|
||
|
||
В debug-сборке с домашнего экрана `F10` переключает тот же сервисный вход; если защита PIN выключена,
|
||
редактор открывается напрямую, а при включённой защите сначала используется общая PIN-клавиатура. В редакторе владельца доступны свой текст
|
||
для речевого bubble и контакт техподдержки без отдельного предпросмотра раздела, общая громкость,
|
||
master mute, отдельная громкость промо-ролика, короткий проверочный сигнал, стоимость жетона, бонусные
|
||
правила, карточные пресеты, предел и шаг ручной суммы, доступность наличной/карточной оплаты,
|
||
пользовательские таймауты, промо-poster/fallback/плейлист, защита PIN, предварительный расчёт
|
||
и автоматическое сохранение. Из всех подходящих бонусных правил применяется один наибольший бонус; значения разных строк не
|
||
суммируются. Нулевая цена, значения вне диапазона, повторяющиеся суммы/пороги и отключение обоих
|
||
способов оплаты блокируют сохранение и показываются рядом с соответствующим полем. Пустой набор
|
||
карточных пресетов допустим: пользователь сохраняет доступ к пункту «Другая сумма». Обычная
|
||
release-сборка игнорирует действие `F10`. В production экран PIN открывается только аппаратным
|
||
событием `SERVICE`, только из `Home` и только при отсутствии операции. Единый сервисный PIN
|
||
включается в разделе «Общие» и дважды задаётся на экране приложения при следующем входе.
|
||
Открытый PIN нигде не сохраняется: в JSON находятся только случайная соль, PBKDF2-SHA256-хеш и число итераций;
|
||
PIN и производные значения не записываются в журналы. Тема/DLC, COM-порт, mock-режим, параметры
|
||
окна/FPS, журналирование, диагностика и тестовая выдача не показываются владельцу. Аппаратные
|
||
параметры изменяются только через отдельный TOML при техническом развёртывании. Полная матрица:
|
||
[docs/operations/SETTINGS_ACCESS_MATRIX.md](docs/operations/SETTINGS_ACCESS_MATRIX.md).
|
||
При тапе на текстовое поле открывается встроенная в тему экранная клавиатура с русской и английской
|
||
раскладками, Shift и отдельной страницей спецсимволов; для числового поля открывается цифровая
|
||
клавиатура. Обе панели можно скрыть кнопкой справа сверху, а поле автоматически прокручивается выше
|
||
клавиатуры. Системная виртуальная клавиатура для этих полей отключена. Подробности:
|
||
[docs/operations/ON_SCREEN_KEYBOARD.md](docs/operations/ON_SCREEN_KEYBOARD.md).
|
||
Master и промо разведены по Godot
|
||
audio buses `Master` и `Advertisement`, поэтому громкость промо-ролика не влияет на остальные звуки.
|
||
|
||
Внешняя тема остаётся частью deployment-конфигурации: отсутствующий или не прошедший проверку
|
||
пакет оставит встроенную fallback-тему. Путь к `.pck`, включение DLC и `ReducedEffects` намеренно
|
||
не показываются в настройках владельца.
|
||
|
||
Промо-ресурсы принимаются только из `res://`, `user://` либо по абсолютному локальному пути.
|
||
Постеры поддерживают PNG/JPG/JPEG/WEBP, видео — OGV (Ogg Theora), MP4, MOV и M4V. MP4/MOV/M4V
|
||
воспроизводятся GDExtension `addons/native_video` через тот же `VideoStreamPlayer`; для них нужен
|
||
Mobile или Forward+ (Vulkan), Compatibility не поддерживается. При выборе внешнего MP4/MOV/M4V
|
||
ролик остаётся на месте и передаётся Native Video напрямую по исходному пути, включая Unicode и
|
||
пробелы; копии, служебные ссылки и ASCII-псевдонимы не создаются. Существование файла проверяется
|
||
до сохранения. Отключённое промо может временно хранить недоступный плейлист, но не читает файлы,
|
||
не запускает декодирование и показывает на Home безопасный текст «ПРОМО ОТКЛЮЧЕНО».
|
||
|
||
Смена плейлиста использует штатный lifecycle Godot: активный player получает `Stop()`, его
|
||
`Stream` очищается, а новый источник начинается с позиции 0. То же завершение выполняется при
|
||
скрытии HomeScreen и освобождении узла; старый decoder и его аудио не сохраняются в runtime-кэше.
|
||
Регрессионная сцена `tests/runtime/NativeVideoLifecycleSmoke.tscn` проверяет 50 смен двух внешних
|
||
MP4, скрытие/возврат и многократное удаление активного `AdvertisementPlayer`.
|
||
|
||
## Журналы
|
||
|
||
Операции приложения записываются в:
|
||
|
||
```text
|
||
user://logs/exchanger.log
|
||
user://logs/operations.jsonl
|
||
user://logs/settings-audit.jsonl
|
||
```
|
||
|
||
`exchanger.log` содержит диагностические сообщения с уровнем и категорией. `operations.jsonl` хранит структурированные события начала и единственного итога по локальному `OperationId`. `settings-audit.jsonl` фиксирует только время, роль, названия изменённых разделов и необходимость перезапуска. При перезапуске незавершённые операции безопасно закрываются как `UnknownAfterRestart`, без автоматического повтора оплаты или выдачи. Журналы автоматически очищаются по сроку хранения и общему лимиту; платёжные секреты, значения настроек, полные пути и сырые ответы оборудования не записываются. Подробные правила и mock-чек-лист: [docs/operations/RECOVERY_AND_MOCK_QA.md](docs/operations/RECOVERY_AND_MOCK_QA.md).
|
||
|
||
## Структура проекта
|
||
|
||
```text
|
||
res://
|
||
├── docs/ # Архитектурные решения и анализ референсов
|
||
├── config/ # Встроенная конфигурация
|
||
├── tests/Exchanger.Tests/ # NUnit-тесты чистого ядра и smoke-проверки файлов
|
||
├── src/
|
||
│ ├── core/
|
||
│ │ ├── Configuration/ # AppSettings и загрузка JSON
|
||
│ │ ├── TechnicalConfiguration/ # Монтажный TOML и его строгая валидация
|
||
│ │ ├── Logging/ # Файловый журнал
|
||
│ │ ├── Navigation/ # Реестр и менеджер экранов
|
||
│ │ ├── Session/ # Конечный автомат операции
|
||
│ │ └── ThemeManager/ # Runtime-контракт, fallback и загрузчик DLC
|
||
│ ├── hardware/
|
||
│ │ ├── Abstractions/ # IHardwarePort и IMachineController
|
||
│ │ ├── MockHardware/ # Имитация сырого транспорта
|
||
│ │ ├── MockMachine/ # Типизированный симулятор автомата
|
||
│ │ ├── SerialPortHardware/ # Desktop-порт Windows/Linux
|
||
│ │ └── UsbSerialHardware/ # Android-заглушка
|
||
│ ├── scenes/
|
||
│ │ ├── App/ # Композиционный корень
|
||
│ │ ├── ServiceAccessScreen/ # Защищённый PIN-вход
|
||
│ │ ├── SettingsScreen/ # Настройки владельца и сервисный учёт
|
||
│ │ ├── HomeScreen/ # Домашний экран
|
||
│ │ ├── CashPaymentScreen/ # Оплата наличными
|
||
│ │ ├── CardAmountScreen/ # Выбор карточной суммы
|
||
│ │ ├── CardCustomAmountScreen/ # Цифровая клавиатура
|
||
│ │ ├── CardTerminalScreen/ # Ожидание банковского терминала
|
||
│ │ ├── SuccessScreen/ # Выдача и успех
|
||
│ │ └── ErrorScreen/ # Отказ и ошибки
|
||
│ └── ui/
|
||
│ ├── OnScreenKeyboard/ # Логика текстовой и цифровой клавиатур
|
||
│ ├── StaticBackground/ # Статический фон и слой анимированных спрайтов
|
||
│ ├── AdvertisementPlayer/ # Локальное видео и fallback
|
||
│ ├── TokenStockStatus/ # Состояние запаса жетонов
|
||
│ └── TouchInputGuard/ # Защита от мультитача
|
||
└── addons/ # Сторонние плагины
|
||
```
|
||
|
||
Сцены, их C#-скрипты и специфичные ресурсы хранятся атомарно в одной тематической папке.
|
||
|
||
## Темы и DLC
|
||
|
||
В Exchanger нет каталога разработки тем и встроенных `.tres`-тем. Единственная минимальная fallback-тема создаётся кодом, поэтому приложение сохраняет рабочий интерфейс без внешних файлов. При старте `ThemeManager` ищет один `.pck` по пути из пользовательского `appsettings.json`; успешно проверенный DLC полностью заменяет fallback до создания экранов. Manifest v2 предоставляет полные визуальные сцены всех девяти экранов: app-owned C#-контроллеры связываются с ними через metadata `exchanger_binding_id`, не передавая теме бизнес-логику.
|
||
|
||
Исходники тем, manifest, фоновые изображения и сцены находятся только в соседнем Godot-проекте `../ExchangerThemeDlcBuilder`: `themes/example/` служит готовым шаблоном, `themes/space-pixel/` — текущей оформленной темой, остальные темы лежат в `themes/<theme-id>/`. Основное приложение содержит runtime-контракт имён сцен, binding-resolver и безопасный загрузчик. Для manifest v2 приложение сверяет закреплённый SHA-256 машиночитаемого каталога и проверяет все 481 обязательный биндинг девяти сцен по ID и Godot-типу. Горячего переключения и списка тем нет; после замены `.pck` приложение нужно перезапустить.
|
||
|
||
Современная production-сцена темы самодостаточна: её корень содержит metadata `exchanger_self_contained_visuals = true`, собственный `Theme`, прямой `TextureRect` `ThemeBackground` и прямой слой `BackgroundDecoration`. Все девять сцен пакета обязаны использовать один и тот же режим. Для такого пакета глобальные `BackgroundTexturePath` и `BackgroundDecorationPath` оставляют пустыми; приложение отклоняет смешанную или дублирующую визуальную композицию.
|
||
|
||
Runtime не переоформляет самодостаточную сцену: он не назначает ей другой `Theme`, type variations,
|
||
размеры или theme overrides. Приложение находит готовые элементы по binding-id, подключает события
|
||
и обновляет только функциональные значения — текст, видимость, доступность и состояние полей.
|
||
|
||
Тематические PCK считаются доверенными. Внутри production-сцен разрешены GDScript, сигналы,
|
||
`AnimatedSprite2D`, `AnimationPlayer`, шейдеры, частицы, аудио, видео и другие runtime-возможности
|
||
Godot. Формат, binding-id, пользовательские пути и сборка пакета описаны в
|
||
[docs/THEME_DLC.md](docs/THEME_DLC.md).
|
||
|
||
## Аппаратный слой
|
||
|
||
UI не обращается к порту напрямую. `SessionController` работает через типизированный `IMachineController`, а будущий протокольный адаптер будет использовать низкоуровневый `IHardwarePort`; поэтому экраны не зависят от COM-порта, Android USB API или строк wire-протокола.
|
||
|
||
Сейчас доступны:
|
||
|
||
- `MockHardwarePort` — минимальная служебная заглушка транспорта для UI mock-режима;
|
||
- `PeripheryControllerSimulator` — детерминированный wire-симулятор handshake, `INPUT`, платежей и обоих хопперов для интеграционных тестов без COM-порта;
|
||
- `MockMachineController` — детерминированно имитирует наличный баланс, карточный результат, бинарный уровень запаса и подтверждение выдачи;
|
||
- `SerialPortHardware` — реальный Windows/Linux-транспорт с отдельным worker, очередями и reconnect;
|
||
- `PeripheryMachineController` — типизированный адаптер протокола универсального контроллера 2.3.5;
|
||
- `UsbSerialHardware` — явно неподдерживаемая Android-заглушка.
|
||
|
||
COM-порт использует `System.IO.Ports` 8.0.0 и параметры 115200 8N1/None. Авторизация и платежи реализованы по спецификации 2.3.5. Обфусцированное представление ключа встроено в `PeripheryControllerSecurity` и восстанавливается XOR `0x37`. Целевой режим — `DualHopperAuto`: `309N` обслуживает первый хоппер, `409N` — второй, пакеты ограничены 99 жетонами, а подтверждения `OK/ERROR/CREDIT` принимаются только от активного канала. Шаблон TOML включает оба хоппера, но остаётся безопасным благодаря `use_mock = true`; реальный профиль активируется только техническим специалистом. Команда выдачи автоматически не повторяется.
|
||
|
||
В актуальной разводке `IN8` active-low переключает сервисный вход, `IN1` сообщает уровень первого хоппера, `IN4` — второго. Обычная выдача использует `309N` и `409N` и считает только соответствующие кадры `HOPPER1/2 OK`. Сервисный пересчёт запускается отдельно командами `301/300` или `401/400`, считает только `HOPPER1/2 PULSE=1` выбранного канала и завершается после 10 секунд тишины. Физические импульсные входы не участвуют в программном счёте. Общая полоска объединяет точный учёт и бинарные датчики в состояния «нет жетонов», «мало», «достаточно» и «много».
|
||
|
||
`IMachineController` также публикует безопасное сервисное состояние соединения. На главном экране штатный статус скрыт в production и виден в debug; авторизация, обрыв или аппаратная ошибка отображаются всегда, но без имени COM-порта, сырых кадров и деталей handshake. При потере связи запас становится `Unknown`, поэтому запуск новой оплаты блокируется.
|
||
|
||
## Универсальность оборудования
|
||
|
||
Пока целевой ПК и тач-панель отсутствуют, проект не оптимизируется под одну модель устройства. UI проверяется на нескольких портретных разрешениях, параметры оборудования и сценариев выносятся в конфигурацию, а платформенные транспорты остаются за `IHardwarePort`. Фактические DPI, GPU, производительность, системный киоск-режим и работа в перчатках будут проверены на реальном аппарате без изменения границ UI и бизнес-логики.
|
||
|
||
## Киоск-режим
|
||
|
||
Release-сборка настраивает borderless fullscreen, скрывает курсор и повторно применяет оконный
|
||
профиль после потери фокуса либо изменения состава/разрешения дисплеев. Godot не может надёжно
|
||
заблокировать Alt+Tab, Ctrl+Alt+Del и системные жесты, поэтому полный киоск дополнительно настраивается
|
||
средствами Windows. Скрипты автозапуска/watchdog и безопасная процедура установки находятся в
|
||
`tools/windows`; инструкция — [docs/operations/WINDOWS_KIOSK_SETUP.md](docs/operations/WINDOWS_KIOSK_SETUP.md).
|
||
Ускоренный и процессный soak описаны в [docs/operations/SOAK_TEST.md](docs/operations/SOAK_TEST.md).
|
||
|
||
## Сторонние компоненты
|
||
|
||
В проекте подключены:
|
||
|
||
- Godot AI;
|
||
- GodotX Toast;
|
||
- Log.gd;
|
||
- Reload Current Scene.
|
||
|
||
Эти editor/development addons исключаются из production export; приложение не зависит от них.
|
||
|
||
Лицензии сторонних компонентов находятся рядом с ними в `addons/` либо в отдельных сопроводительных файлах, например `godot-ai-LICENSE.txt`.
|
||
|
||
## Лицензия проекта
|
||
|
||
Собственная лицензия Exchanger в настоящий момент не объявлена. Ошибочный корневой `LICENSE`, автоматически скопированный из GodotX Toast, удалён. Это не затрагивает лицензии сторонних компонентов.
|