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 _);
}