332 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//Рефакторинг и перевод на асинхронку ComWrapper'a
//Надо потом еще логирование нормальное прикрутить и логгер сделать
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;
public static class ComWrapper
{
//Настраиваем из инспектора
//То, что нужно вынести в конфиг
//[SerializeField] bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
//[SerializeField] bool keepAlive = true;
static bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true. Да и что то не вижу реализации. Возможно оно уже где-то в ComInput
static bool keepAlive = true;
public static UnityEvent PaymentReceived;
public static event Action<string> InputRecieved;
private const string REQUEST_INPUT_COMMAND = "91";
private static int h;//идентификатор хэндла
private static byte[] buf = new byte[1024]; //Байт-буфер куда будут приходить данные с порта
private static ILogger logger;
private static CancellationTokenSource poll_cts, keepAlive_cts;
public static void Initialize()//Добавить логгер в конструктор
{
//Тут надо с конфига подтянуть настройки!
PaymentReceived = new UnityEvent();
OpenPort();
SetupBoardAsync();
}
public static void Send(string message)
{
try
{
int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1); //w-количество записаных символов в порт. можно обработать
}
catch { logger?.Log("Ошибка отправки строкового сигнала."); }
}
private static async Task SetupBoardAsync()
{
bool auth = await BoardAuthAsync();
if(!auth) (new GameObject()).AddComponent(typeof(VfxPrefab));//Запускаем отвал
poll_cts = new CancellationTokenSource();
keepAlive_cts = new CancellationTokenSource();
StartPollingAsync();
StartKeepAliveProcessAsync(keepAlive_cts.Token);
}
private static void OpenPort()
{
Log("Инициализация порта");
//ComNativeWrapper.Serial_CloseAll();//или все таки h?, Пока не удаляем, посмотрим как сработает
var ports = ComNativeWrapper.ListPorts();
if (ports.Length == 0) { Log("Нет портов, завершаю"); return; }
string port = ports[^1];
Log("Открываю порт: " + port);
h = ComNativeWrapper.Serial_Open(port, 115200, 8, 0, 1, 4, 10);
Log($"handle: {h}");
if (h <= 0)
{
Log("Open failed: " + ComNativeWrapper.GetLastError());
return;
}
else
{
Log($"Открыл порт: {port}");
}
}
//прописать нормальные логи
private static async Task<bool> BoardAuthAsync()
{
bool boardEncrypted = true; //предполагаем что зашифрована
float timeout = 1;
var sw = new Stopwatch();
//Проверяем зашифрована или нет
sw.Start();
RequestInputs();
while (sw.Elapsed.TotalSeconds < timeout)
{
string entry = await ReadBufferLineAsync();
if (entry.StartsWith("INPUT"))
{
boardEncrypted = false;
break;
}
}
sw.Stop();
//Не шифрованная -> На выход, пользуемся.
if (!boardEncrypted)
return true;
//А тут возможно шифрованная
Log("Шифрованная плата");
//Запрашиваем шифрованную строку
string encryptedString = string.Empty;
Send("666");
sw.Start();
while (sw.Elapsed.Seconds < timeout)
{
string entry = await ReadBufferLineAsync();
if (entry.StartsWith("ENC:"))
{
//пишем сразу без суффикса
encryptedString = entry.Substring(4);
break;
}
}
sw.Stop();
//Шифрованная строка не пришла -> идем нафиг
if (string.IsNullOrEmpty(encryptedString))
{
Log($"Зашифрованная строка не пришла");
return false;
}
//Раз пришла, надо расшифровывать
Log($"Расшифровываю {encryptedString}");
string decryptedString = BoardUtility.DecryptSerial(encryptedString);
//ждем подтверждение от платы
string confirmationString = string.Empty;
sw.Start();
Send("777" + decryptedString);
while (sw.Elapsed.Seconds < timeout)
{
string entry = await ReadBufferLineAsync();
if (entry.IndexOf("OK") != -1)
{
//Успех, плату активировали
sw.Stop();
return true;
}
}
//А вот тут мы не дождались подтверждения. Плохо, будем ломать игру.
Application.Quit();
return false;
}
private static void OnApplicationQuit()
{
StopAsyncPolling();
StopKeepAlive();
ClosePort();
}
private static void StopAsyncPolling()
{
poll_cts?.Cancel();
poll_cts?.Dispose();
poll_cts = null;
}
private static void StopKeepAlive()
{
keepAlive_cts?.Cancel();
keepAlive_cts?.Dispose();
keepAlive_cts = null;
}
//прописать норм логи
private static async void StartPollingAsync()
{
try
{
Log("Запуск асинхронного опроса порта");
while (poll_cts != null && !poll_cts.Token.IsCancellationRequested)
{
await Task.Delay(10, poll_cts.Token);
string comInput = await ReadBufferLineAsync(poll_cts.Token);
if (!string.IsNullOrEmpty(comInput))
{
// Прямой вызов - мы уже в главном потоке после await
ProcessComInputImmediate(comInput);
}
}
}
catch (OperationCanceledException)
{
Log("Асинхронный опрос порта остановлен");
}
catch (Exception e)
{
Log($"Ошибка асинхронного опроса: {e.Message}");
}
}
//Логи
private static void ProcessComInputImmediate(string comInput)
{
Log($"COM. h={h}: {comInput}");
if (comInput == "COIN")
PaymentReceived?.Invoke();
else if (comInput.StartsWith("INPUT"))
InputRecieved?.Invoke(comInput.Substring(5));
}
#region Private Methods
private static void ClosePort()
{
Log($"Отпускаю порт handle:{h}");
if (h > 0)
{
ComNativeWrapper.Serial_Close(h);
h = 0;
}
}
private static async Task<string> ReadBufferLineAsync(CancellationToken cancellationToken = default)
{
try
{
return await Task.Run(() =>
{
// Проверяем отмену внутри Task.Run
if (cancellationToken.IsCancellationRequested)
return string.Empty;
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buf, buf.Length);
if (read > 0)
{
return Encoding.UTF8.GetString(buf, 0, read);
}
return string.Empty;
}, cancellationToken);
}
catch (OperationCanceledException)
{
Log("Чтение порта отменено");
return string.Empty;
}
catch (Exception e)
{
Log($"Ошибка асинхронного чтения: {e.Message}");
return string.Empty;
}
}
private static void RequestInputs()
=> Send(REQUEST_INPUT_COMMAND);
//Сделать ожидание простоя около минуты на проверку и переподелючение.
private static async Task StartKeepAliveProcessAsync(CancellationToken cancellationToken)
{
try
{
Log("Запуск асинхронной проверки порта");
while (!cancellationToken.IsCancellationRequested)
{
if (!await CheckPortConnectionAsync())
{
Log("Плата отвалилась. Запускаю переподключение.");
await ReinitializePortAsync(cancellationToken);
}
await Task.Delay(10000, cancellationToken); //Задержка
}
}
catch (OperationCanceledException)
{
Log("Асинхронная проверка порта остановлена");
}
catch (Exception e)
{
Log($"Ошибка проверки порта: {e.Message}");
}
}
private static async Task<bool> CheckPortConnectionAsync()
{
var sw = Stopwatch.StartNew();
float timeout = 1;
RequestInputs();
while (sw.Elapsed.Seconds < timeout)
{
string entry = await ReadBufferLineAsync();
if(!string.IsNullOrEmpty(entry) && entry.StartsWith("INPUT"))
{
return true;
}
}
sw.Stop();
return false;
}
//реализовать
private static async Task ReinitializePortAsync(CancellationToken cancellationToken)
{
ClosePort();
await SetupBoardAsync();
}
private static void Log(string message) => logger?.Log($"CW{message}");
#endregion
}