235 lines
7.2 KiB
C#
235 lines
7.2 KiB
C#
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";
|
|
|
|
/// <summary>
|
|
/// Сканирует все доступные COM-порты и возвращает словарь {порт = тип устройства}
|
|
/// </summary>
|
|
public static Dictionary<string, DeviceType> ScanPorts()
|
|
{
|
|
var result = new Dictionary<string, DeviceType>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Проверяет один порт на наличие известных устройств
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Очищает входной буфер порта
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Проверяет устройство на порту и возвращает ответ
|
|
/// </summary>
|
|
private static string CheckDevice(string port, string command, Func<string, bool> 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<string, bool> 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 _);
|
|
}
|