332 lines
10 KiB
C#
332 lines
10 KiB
C#
//Рефакторинг и перевод на асинхронку 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
|
||
}
|