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