using System; using System.Collections.Generic; using System.Text; using UnityEngine; public static class ComPortScanner { // Параметры соединения private const int _baudRate = 115200; private const int _dataBits = 8; private const int _parity = 0; // 0 = None private const int _stopBits = 1; // 1 = One private const int _readTimeoutMs = 5; private const int _writeTimeoutMs = 10; // Команды устройств private const string _cmdPrize = "97"; private const string _cmdControllerReset = "00"; private const string _cmdControllerAuth = "07"; private const int _deviceResponseTimeoutMs = 1000; private const int _controllerRestartTimeoutMs = 20000; private const int _authenticationResponseTimeoutMs = 3000; // Типы устройств public enum DeviceType { None, Controller, PrizeShowcase } // Префиксы ответов private const string _rolePrefix = "ROLE :"; private const string _prizeRoleValue = "vitrina"; /// /// Сканирует все доступные COM-порты и возвращает словарь {порт = тип устройства} /// public static Dictionary ScanPorts() { var result = new Dictionary(); string[] ports = ComNativeWrapper.ListPorts(); foreach (string port in ports) { if (port == "COM1") continue; DeviceType type = CheckPort(port); if (type != DeviceType.None) { result[port] = type; } } return result; } /// /// Проверяет один порт на наличие известных устройств /// private static DeviceType CheckPort(string port) { Debug.Log($"Проверка порта {port}"); string response97 = CheckDevice( port, _cmdPrize, line => line.StartsWith(_rolePrefix, StringComparison.Ordinal)); if (response97 != null && response97.StartsWith(_rolePrefix)) { Debug.Log($"Role: {response97}"); // Извлекаем значение после "ROLE : " string roleValue = response97.Substring(_rolePrefix.Length).Trim(); if (string.Equals(roleValue, _prizeRoleValue, StringComparison.OrdinalIgnoreCase)) return DeviceType.PrizeShowcase; return DeviceType.Controller; } if (CheckControllerDevice(port)) return DeviceType.Controller; return DeviceType.None; } /// /// Очищает входной буфер порта /// private static void ClearPortBuffer(int handle) { byte[] tempBuffer = new byte[1024]; const float idleTimeout = 0.5f; const int maxreads = 50; int reads = 0; float startTime = Time.unscaledTime; while (reads < maxreads) { int bytesRead = ComNativeWrapper.Serial_ReadLineUTF8_bytes(handle, tempBuffer, tempBuffer.Length); reads++; if (bytesRead > 0) { startTime = Time.unscaledTime; continue; } else if (bytesRead == 0) { float elapsed = Time.unscaledTime - startTime; if (elapsed >= idleTimeout) { break; } } else { break; } } } /// /// Проверяет устройство на порту и возвращает ответ /// private static string CheckDevice(string port, string command, Func isExpectedResponse) { int handle = ComNativeWrapper.Serial_Open(port, _baudRate, _dataBits, _parity, _stopBits, 10, _writeTimeoutMs); if (handle <= 0) { return null; } try { ClearPortBuffer(handle); int writeResult = ComNativeWrapper.Serial_WriteStringUTF8(handle, command, 1); if (writeResult < 0) { return null; } byte[] buffer = new byte[1024]; long deadline = DateTime.UtcNow.Ticks + _deviceResponseTimeoutMs * TimeSpan.TicksPerMillisecond; while (DateTime.UtcNow.Ticks < deadline) { int bytesRead = ComNativeWrapper.Serial_ReadLineUTF8_bytes(handle, buffer, buffer.Length); if (bytesRead <= 0) continue; string line = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim(); if (!string.IsNullOrEmpty(line) && isExpectedResponse(line)) return line; } return null; } finally { ComNativeWrapper.Serial_Close(handle); } } private static bool CheckControllerDevice(string port) { int handle = ComNativeWrapper.Serial_Open( port, _baudRate, _dataBits, _parity, _stopBits, _readTimeoutMs, _writeTimeoutMs); if (handle <= 0) return false; try { ClearPortBuffer(handle); int resetWriteResult = ComNativeWrapper.Serial_WriteStringUTF8( handle, _cmdControllerReset, 1); if (resetWriteResult < 0) return false; string versionLine = ReadExpectedLine( handle, BoardAuthentication.IsControllerVersionLine, _controllerRestartTimeoutMs); if (versionLine == null) return false; string computerChallenge = BoardAuthentication.CreateChallengeHex(); int writeResult = ComNativeWrapper.Serial_WriteStringUTF8( handle, _cmdControllerAuth + computerChallenge, 1); if (writeResult < 0) return false; return ReadExpectedLine(handle, IsChallengeResponse, _authenticationResponseTimeoutMs) != null; } finally { ComNativeWrapper.Serial_Close(handle); } } private static string ReadExpectedLine(int handle, Func isExpectedResponse, int timeoutMs) { byte[] buffer = new byte[1024]; long deadline = DateTime.UtcNow.Ticks + timeoutMs * TimeSpan.TicksPerMillisecond; while (DateTime.UtcNow.Ticks < deadline) { int bytesRead = ComNativeWrapper.Serial_ReadLineUTF8_bytes(handle, buffer, buffer.Length); if (bytesRead <= 0) continue; string line = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim(); if (!string.IsNullOrEmpty(line) && isExpectedResponse(line)) return line; } return null; } private static bool IsChallengeResponse(string line) => BoardAuthentication.TryExtractHexPayload(line, "CHAL", out _); }