refactor: Refactor work with COM to ComSystemManager
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Добавлен ComSystemManager как новый публичный компонент COM-системы для поэтапной замены ComWrapper - Реализовано получение роли контроллера только после успешного handshake в ComDeviceClient - Добавлено публичное поле ControllerRole в ComDeviceClient - Добавлен запрос роли по команде 97 и разбор ответа формата ROLE : <value> (с пробелами) - В ComMultiManager добавлена логика выбора DefaultDeviceId по роли main_controller - Добавлен доступ к роли устройства через GetDeviceRole(deviceId) в менеджере и фасаде - В ComWrapper добавлены настройки роли: roleRequestCommand, roleResponsePrefix, roleRequestTimeoutMs, mainControllerRole - Дефолтная команда запроса роли изменена на 97 - Выполнена кодовая миграция зависимостей с ComWrapper на ComSystemManager в GameManager, GlobalsManager, PaymentManager, BalanceManager, InactivityManager, COMInput, LightController, PrizeSystem - В GlobalsManager исправлена инициализация ссылки после AddComponent<ComSystemManager>(), чтобы исключить NullReference
This commit is contained in:
parent
6b02c76ef3
commit
4a079dd46b
314
Assets/Scripts/Controls/Inputs/ComSystem/PrizeStandController.cs
Normal file
314
Assets/Scripts/Controls/Inputs/ComSystem/PrizeStandController.cs
Normal file
@ -0,0 +1,314 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
//Зависит от ComNativeWrapper и одноименного плагина
|
||||
//рекомендуется соединение с таймаутом не меньше 10мс. Иначе вероятно busy loop и много алокаций.
|
||||
public static class PrizeStandController
|
||||
{
|
||||
/// <summary>
|
||||
/// По завершении инициализации.
|
||||
/// bool - успех/неуспех.
|
||||
/// string - сообщение в случае неуспеха
|
||||
/// </summary>
|
||||
public static event Action<bool, string> InitializationComplete;
|
||||
/// <summary>
|
||||
/// По завершении операции открытия.
|
||||
/// bool - успех/неуспех.
|
||||
/// string - сообщение в случае неуспеха
|
||||
/// </summary>
|
||||
public static event Action<bool, string> OpeningFinished;
|
||||
|
||||
private const string REQUEST_STAND_STATUS = "25";
|
||||
private const string OPEN_BOX_PREFIX = "2501";
|
||||
private const float STATUS_REQUEST_TIMEOUT = 3;
|
||||
private const float WAIT_OPEN_STATUS_TIMEOUT = 5;
|
||||
|
||||
private static readonly IReadOnlyList<BoxStatus> READY_TO_OPEN_STATUSES = new[] { BoxStatus.OK, BoxStatus.OKplusCard };
|
||||
|
||||
private static int handle = -1;
|
||||
private static int boxesCount;
|
||||
private static readonly SemaphoreSlim _lock = new(1, 1);
|
||||
private static bool initialized = false;
|
||||
|
||||
private static ILogger _logger;
|
||||
|
||||
#region PUBLIC API
|
||||
/// <summary>
|
||||
/// Инициализировать модуль витрины.
|
||||
/// </summary>
|
||||
/// <param name="handleId">Id хэндла полученное после ComNativeWrapper.Serial_Open</param>
|
||||
/// <param name="countOfBoxes">Количество физических коробок витрины</param>
|
||||
/// <param name="logger">(опционально) Логгер</param>
|
||||
public static void Initialize(int handleId, int countOfBoxes, ILogger logger)
|
||||
{
|
||||
bool hasError = false;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
//Проверка хэндла. h<=0 - ошибка открытия порта
|
||||
if (handleId <= 0)
|
||||
{
|
||||
hasError = true;
|
||||
sb.AppendLine($"[Модуль витрины] Переданный handle={handleId}. Ожидалось значение>0.");
|
||||
}
|
||||
else handle = handleId;
|
||||
|
||||
//Проверка количества коробок
|
||||
if (countOfBoxes <= 0)
|
||||
{
|
||||
hasError = true;
|
||||
sb.AppendLine($"[Модуль витрины] Попытка задать количество коробок={countOfBoxes}. Ожидалось значение>0.");
|
||||
}
|
||||
else boxesCount = countOfBoxes;
|
||||
|
||||
//Установка логгера
|
||||
_logger = logger;
|
||||
if (_logger == null)
|
||||
sb.AppendLine($"[Модуль витрины] Получен логгер NULL. Если нужно логгирование, передайте дествующий логгер.");
|
||||
|
||||
//Завершение
|
||||
if (hasError)
|
||||
{
|
||||
initialized = false;
|
||||
handle = -1;
|
||||
boxesCount = 0;
|
||||
|
||||
InitializationComplete?.Invoke(false, sb.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
initialized = true;
|
||||
InitializationComplete?.Invoke(true, sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Открыть коробку по номеру
|
||||
/// </summary>
|
||||
/// <param name="boxNumber">Номер коробки (считаем с 1)</param>
|
||||
public static async Task OpenBoxAsync(int boxNumber)
|
||||
{
|
||||
if(!await _lock.WaitAsync(0))
|
||||
{
|
||||
OpeningFinished?.Invoke(false, "Выдача уже в процессе");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//Валидация
|
||||
var validationResult = await CanExecuteOpenAsync(boxNumber);
|
||||
if (!validationResult.OK)
|
||||
{
|
||||
OpeningFinished?.Invoke(false, validationResult.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
SendOpenCommand(boxNumber);
|
||||
|
||||
//Обработка результатов
|
||||
BoxStatus openStatus = await ReadBoxStatus(boxNumber, 1, WAIT_OPEN_STATUS_TIMEOUT);
|
||||
if (openStatus == BoxStatus.OPENED)
|
||||
{
|
||||
OpeningFinished?.Invoke(true, $"Ячейка {boxNumber} успешно открыта");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.Log($"[Модуль витрины] Ошибка при открытии ячейки {boxNumber}");
|
||||
OpeningFinished?.Invoke(false, $"Ошибка при открытии ячейки номер {boxNumber}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[Модуль витрины] Ошибка при открытии ячейки {boxNumber}: {ex.Message}");
|
||||
OpeningFinished?.Invoke(false, ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.Release();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HELPERS
|
||||
private static void SendOpenCommand(int boxNumber)
|
||||
=> ComNativeWrapper.Serial_WriteStringUTF8(handleId: handle,
|
||||
utf8: $"{OPEN_BOX_PREFIX}{boxNumber:D2}", //D2 приводит в формат XX (2 знака, ноль впереди)
|
||||
appendNewline: 1);
|
||||
private static void SendRequestStatusCommand()
|
||||
=> ComNativeWrapper.Serial_WriteStringUTF8(handle, REQUEST_STAND_STATUS, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Проверка может ли модуль произвести выдачу
|
||||
/// </summary>
|
||||
private static async Task<(bool OK, string Message)> CanExecuteOpenAsync(int boxNumber)
|
||||
{
|
||||
bool isOk = true;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine($"[Модуль витрины. Проверка перед открытием]");
|
||||
|
||||
//Проверка инициализации
|
||||
if (!initialized)
|
||||
{
|
||||
isOk = false;
|
||||
sb.AppendLine("- Модуль не инициализирован.");
|
||||
}
|
||||
|
||||
bool boxNumberIsValid = boxNumber > 0 && boxNumber <= boxesCount;
|
||||
if (!boxNumberIsValid)
|
||||
{
|
||||
isOk = false;
|
||||
sb.AppendLine($"- Номер коробки={boxNumber} невалидный. Требуется от 1 до {boxesCount}.");
|
||||
}
|
||||
|
||||
//Не лезем в порт с запросом статуса коробки, если то, что выше дало НеОК
|
||||
if (isOk)
|
||||
{
|
||||
SendRequestStatusCommand();
|
||||
BoxStatus boxStatus = await ReadBoxStatus(boxNumber, boxesCount, STATUS_REQUEST_TIMEOUT);
|
||||
|
||||
if (!READY_TO_OPEN_STATUSES.Contains(boxStatus))
|
||||
{
|
||||
isOk = false;
|
||||
sb.AppendLine($"- Статус коробки {boxNumber}: {boxStatus} не позволяет провести открытие.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"- Невозможно проверить статус коробки {boxNumber} из-за ошибок выше.");
|
||||
}
|
||||
|
||||
return (isOk, sb.ToString());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Читать статус заданной ячейки витрины. Выход когда прочитали заданное количество вхождений, или по таймауту.
|
||||
/// </summary>
|
||||
/// <param name="expectedBoxNumber">Номер ячейки которую мы ждем</param>
|
||||
/// <param name="expectedEntriesCount">Сколько ячеек мы ждем</param>
|
||||
/// <param name="timeout">Таймаут операции(сек.)</param>
|
||||
/// <returns></returns>
|
||||
private static async Task<BoxStatus> ReadBoxStatus(int expectedBoxNumber, int expectedEntriesCount, float timeout)
|
||||
{
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
//Изначально предполагаем что статус нужной коробки не пришел
|
||||
BoxStatus status = BoxStatus.NotFound;
|
||||
|
||||
int boxesRecieved = 0;
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
while (sw.Elapsed.TotalSeconds < timeout && boxesRecieved < expectedEntriesCount)
|
||||
{
|
||||
//ждать пока всё не считаем
|
||||
int entryLength = await Task.Run(() => ComNativeWrapper.Serial_ReadLineUTF8_bytes(handle, buffer, buffer.Length));
|
||||
|
||||
if (entryLength == 0) //ничего не пришло
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (entryLength < 0) //ошибка
|
||||
{
|
||||
_logger?.Log($"Ошибка при считывании статуса ячеек={entryLength} :: {ComNativeWrapper.GetLastError()}");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
string entry = Encoding.UTF8.GetString(buffer, 0, entryLength).Trim();
|
||||
var ParseResult = ParseBoxStatus(entry);
|
||||
|
||||
if(ParseResult.BoxNumber == -1)
|
||||
{
|
||||
_logger?.Log($"[Модуль витрины. Ошибка получения статуса ячейки]\n{ParseResult.Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
boxesRecieved++;
|
||||
if (ParseResult.BoxNumber == expectedBoxNumber)
|
||||
status = ParseResult.Status;
|
||||
}
|
||||
}
|
||||
|
||||
if (status == BoxStatus.NotFound)
|
||||
_logger?.Log($"[Модуль витрины. Не дождался ответа по статусу ячейки]\n" +
|
||||
$"Ожидаемый номер: {expectedBoxNumber}\n" +
|
||||
$"Получено статусов ячеек: {boxesRecieved}/{expectedEntriesCount}");
|
||||
return status;
|
||||
}
|
||||
|
||||
private static (int BoxNumber, BoxStatus Status, string Message) ParseBoxStatus(string entryString)
|
||||
{
|
||||
int number = -1;
|
||||
BoxStatus status = BoxStatus.ParsingError;
|
||||
string message = "";
|
||||
|
||||
|
||||
|
||||
if (entryString.StartsWith("BOX"))
|
||||
{
|
||||
var noPrefixEntry = entryString.Substring(3); //Убираем "BOX"
|
||||
string[] parts = noPrefixEntry.Split('_'); //ожидаем массив [<номер_коробки>, <статус_коробки>]
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
//Парсим номер коробки
|
||||
bool boxNumberParsed = (int.TryParse(parts[0], out int parsedNumber));
|
||||
|
||||
if (boxNumberParsed)
|
||||
{
|
||||
number = parsedNumber;
|
||||
status = MapStatusFromString(parts[1]);
|
||||
}
|
||||
else message = $"Не удалось спарсить номер коробки: {entryString}";
|
||||
}
|
||||
else
|
||||
{
|
||||
message = $"Ошибка парсинга. Часть строки не пришла: {entryString}";
|
||||
}
|
||||
}
|
||||
else message = $"Неожиданный префикс при парсинге ячеек: {entryString}";
|
||||
|
||||
|
||||
return (number, status, message);
|
||||
|
||||
}
|
||||
private static BoxStatus MapStatusFromString(string statusString) => statusString switch
|
||||
{
|
||||
"OK" => BoxStatus.OK,
|
||||
"OK+CARD" => BoxStatus.OKplusCard,
|
||||
"EMPTY" => BoxStatus.Empty,
|
||||
"OPEN" => BoxStatus.Open,
|
||||
|
||||
"OPENED" => BoxStatus.OPENED,
|
||||
"OPEN_ERROR" => BoxStatus.OPEN_ERROR,
|
||||
|
||||
"ERROR" => BoxStatus.Error,
|
||||
|
||||
_ => BoxStatus.ParsingError
|
||||
};
|
||||
#endregion
|
||||
}
|
||||
|
||||
enum BoxStatus
|
||||
{
|
||||
//Статусы ДО открытия
|
||||
OK, //Готов к выдаче
|
||||
OKplusCard, //Готов к выдаче + готов выдать по карте
|
||||
Empty, //Пусто, уже выдали
|
||||
Open, //Дверка УЖЕ открыта. Приз предположительно пустой
|
||||
|
||||
//Статусы ПОСЛЕ открытия
|
||||
OPENED, //Всё хорошо, открылся
|
||||
OPEN_ERROR, //Ошибка открытия
|
||||
|
||||
//Служебные статусы
|
||||
ParsingError, //При парсинге что то пошло не так, или пришла непредусмотренная фигня
|
||||
NotFound, //Коробка с требуемым номером не найдена
|
||||
Error //Общая ошибка (например дали команду на открытие пустого ящика или сломан замок)
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce33b35c8b4042a5b85c0263727d588b
|
||||
timeCreated: 1771575375
|
||||
Loading…
x
Reference in New Issue
Block a user