Усильте авторизацию плат и восстановление COM-соединения
This commit is contained in:
parent
ded80f043f
commit
ca42cf53ba
@ -145,7 +145,11 @@ public class GameManager : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationQuit() => ComNativeWrapper.Serial_CloseAll();
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
ComWrapper.Shutdown();
|
||||
ComNativeWrapper.Serial_CloseAll();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
@ -172,6 +176,7 @@ public class GameManager : MonoBehaviour
|
||||
RefundEntryIfAvailable();
|
||||
_isLeavingGameplay = true;
|
||||
CancelPrizeFlow();
|
||||
VitrinaView.Instance?.Deactivate();
|
||||
ControlsPerformer.Instance.SetControlable(null);
|
||||
timerManager.HideBars();
|
||||
VideoUtility.CrossFadeToMenu();
|
||||
|
||||
116
Assets/Scripts/Input/ComSystem/BoardAuthentication.cs
Normal file
116
Assets/Scripts/Input/ComSystem/BoardAuthentication.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
public static class BoardAuthentication
|
||||
{
|
||||
private const int _blockSizeBytes = 16;
|
||||
private const int _hexBlockLength = _blockSizeBytes * 2;
|
||||
|
||||
private static readonly byte[] _aesObfuscatedKey =
|
||||
{
|
||||
0x69, 0x40, 0xC7, 0x0E, 0xF2, 0xC7, 0x45, 0x70,
|
||||
0x72, 0xCD, 0x28, 0x58, 0x99, 0x8F, 0x73, 0xA8,
|
||||
0x5F, 0x5F, 0xED, 0xB4, 0xC1, 0x62, 0xB4, 0x24,
|
||||
0xAC, 0xD7, 0xA3, 0xD6, 0xD8, 0x0B, 0x50, 0x37
|
||||
};
|
||||
|
||||
public static string CreateChallengeHex()
|
||||
{
|
||||
byte[] challenge = new byte[_blockSizeBytes];
|
||||
using RandomNumberGenerator generator = RandomNumberGenerator.Create();
|
||||
generator.GetBytes(challenge);
|
||||
return ToHex(challenge);
|
||||
}
|
||||
|
||||
public static string EncryptHexBlock(string plainHex)
|
||||
{
|
||||
byte[] plain = ParseHexBlock(plainHex);
|
||||
|
||||
using Aes aes = Aes.Create();
|
||||
aes.KeySize = 256;
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.ECB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.Key = (byte[])_aesObfuscatedKey.Clone();
|
||||
|
||||
using ICryptoTransform encryptor = aes.CreateEncryptor();
|
||||
byte[] encrypted = encryptor.TransformFinalBlock(plain, 0, plain.Length);
|
||||
return ToHex(encrypted);
|
||||
}
|
||||
|
||||
public static bool TryExtractHexPayload(string line, string prefix, out string payload)
|
||||
{
|
||||
payload = null;
|
||||
if (string.IsNullOrEmpty(line)
|
||||
|| string.IsNullOrEmpty(prefix)
|
||||
|| line.Length != prefix.Length + _hexBlockLength
|
||||
|| !line.StartsWith(prefix, StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
string candidate = line.Substring(prefix.Length);
|
||||
for (int i = 0; i < candidate.Length; i++)
|
||||
{
|
||||
if (!IsHexCharacter(candidate[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
payload = candidate.ToUpperInvariant();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateEncryptedResponse(string challengeHex, string responseHex)
|
||||
{
|
||||
if (!TryNormalizeHexBlock(responseHex, out string normalizedResponse))
|
||||
return false;
|
||||
|
||||
string expectedResponse = EncryptHexBlock(challengeHex);
|
||||
int difference = 0;
|
||||
for (int i = 0; i < expectedResponse.Length; i++)
|
||||
difference |= expectedResponse[i] ^ normalizedResponse[i];
|
||||
|
||||
return difference == 0;
|
||||
}
|
||||
|
||||
private static byte[] ParseHexBlock(string value)
|
||||
{
|
||||
if (!TryNormalizeHexBlock(value, out string normalized))
|
||||
throw new ArgumentException($"Ожидался HEX-блок длиной {_hexBlockLength} символа.", nameof(value));
|
||||
|
||||
byte[] bytes = new byte[_blockSizeBytes];
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
bytes[i] = Convert.ToByte(normalized.Substring(i * 2, 2), 16);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static bool TryNormalizeHexBlock(string value, out string normalized)
|
||||
{
|
||||
normalized = null;
|
||||
if (string.IsNullOrEmpty(value) || value.Length != _hexBlockLength)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
if (!IsHexCharacter(value[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
normalized = value.ToUpperInvariant();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsHexCharacter(char value)
|
||||
=> value >= '0' && value <= '9'
|
||||
|| value >= 'A' && value <= 'F'
|
||||
|| value >= 'a' && value <= 'f';
|
||||
|
||||
private static string ToHex(byte[] bytes)
|
||||
{
|
||||
StringBuilder builder = new(bytes.Length * 2);
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
builder.Append(bytes[i].ToString("X2"));
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b353d677af907bc47a6706e829da2a34
|
||||
@ -15,7 +15,8 @@ public static class ComPortScanner
|
||||
|
||||
// Команды устройств
|
||||
private const string CmdPrize = "97"; // Для витрины и периферийника2
|
||||
private const string CmdControllerV1 = "666"; // Только для периферийника1
|
||||
private const string CmdControllerAuth = "07";
|
||||
private const int DeviceResponseTimeoutMs = 1000;
|
||||
|
||||
// Типы устройств
|
||||
public enum DeviceType
|
||||
@ -27,8 +28,6 @@ public static class ComPortScanner
|
||||
}
|
||||
|
||||
// Префиксы ответов
|
||||
private const string ControllerV1ResponsePrefix = "ENC:"; // Периферийник1 на 666
|
||||
private const string ControllerV1NotActivatedPrefix = "NOT_ACTIVATED:"; // Периферийник1 на всё остальное
|
||||
private const string RolePrefix = "ROLE :"; // Периферийник2 и витрина на 97
|
||||
private const string PrizeRoleValue = "vitrina"; // Витрина: ROLE : vitrina
|
||||
|
||||
@ -61,19 +60,10 @@ public static class ComPortScanner
|
||||
private static DeviceType CheckPort(string port)
|
||||
{
|
||||
Debug.Log($"Проверка порта {port}");
|
||||
// ШАГ 1: Проверяем периферийник1 по команде 666
|
||||
string response666 = CheckDevice(port, CmdControllerV1);
|
||||
if (response666 != null)
|
||||
{
|
||||
// Если ответ начинается с "ENC:" - это точно периферийник1
|
||||
if (response666.StartsWith(ControllerV1ResponsePrefix) || response666.StartsWith("ALREADY_ACTIVATED"))
|
||||
{
|
||||
return DeviceType.ControllerV1;
|
||||
}
|
||||
}
|
||||
|
||||
// ШАГ 2: Проверяем по команде 97 (витрина и периферийник2)
|
||||
string response97 = CheckDevice(port, CmdPrize);
|
||||
string response97 = CheckDevice(
|
||||
port,
|
||||
CmdPrize,
|
||||
line => line.StartsWith(RolePrefix, StringComparison.Ordinal));
|
||||
if (response97 != null && response97.StartsWith(RolePrefix))
|
||||
{
|
||||
Debug.Log($"Role: {response97}");
|
||||
@ -89,15 +79,15 @@ public static class ComPortScanner
|
||||
{
|
||||
return DeviceType.ControllerV2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ШАГ 3: Дополнительная проверка для неактивированного периферийника1
|
||||
// Если устройство ответило на 97 с "NOT_ACTIVATED:" - это периферийник1
|
||||
if (response97 != null && response97.StartsWith(ControllerV1NotActivatedPrefix))
|
||||
{
|
||||
string computerChallenge = BoardAuthentication.CreateChallengeHex();
|
||||
string authResponse = CheckDevice(
|
||||
port,
|
||||
CmdControllerAuth + computerChallenge,
|
||||
IsAuthenticationResponse);
|
||||
if (authResponse != null)
|
||||
return DeviceType.ControllerV1;
|
||||
}
|
||||
|
||||
return DeviceType.None;
|
||||
}
|
||||
@ -142,7 +132,7 @@ public static class ComPortScanner
|
||||
/// <summary>
|
||||
/// Проверяет устройство на порту и возвращает ответ
|
||||
/// </summary>
|
||||
private static string CheckDevice(string port, string command)
|
||||
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)
|
||||
@ -161,19 +151,28 @@ public static class ComPortScanner
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead = ComNativeWrapper.Serial_ReadLineUTF8_bytes(handle, buffer, buffer.Length);
|
||||
|
||||
if (bytesRead <= 0)
|
||||
long deadline = DateTime.UtcNow.Ticks
|
||||
+ DeviceResponseTimeoutMs * TimeSpan.TicksPerMillisecond;
|
||||
while (DateTime.UtcNow.Ticks < deadline)
|
||||
{
|
||||
return null;
|
||||
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;
|
||||
}
|
||||
|
||||
string line = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim();
|
||||
return string.IsNullOrEmpty(line) ? null : line;
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ComNativeWrapper.Serial_CloseAll();
|
||||
ComNativeWrapper.Serial_Close(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAuthenticationResponse(string line)
|
||||
=> BoardAuthentication.TryExtractHexPayload(line, "CHAL", out _)
|
||||
|| BoardAuthentication.TryExtractHexPayload(line, "RAES", out _);
|
||||
}
|
||||
|
||||
@ -16,25 +16,37 @@ public static class ComWrapper
|
||||
public static event Action PaymentReceived;
|
||||
public static event Action<string> InputRecieved;
|
||||
|
||||
private const string REQUEST_INPUT_COMMAND = "91";
|
||||
private const string REQUEST_ENCRYPTED_COMMAND = "666";
|
||||
private const string REQUEST_CONFIRMATION_COMMAND = "777";
|
||||
private const string _requestInputCommand = "91";
|
||||
private const string _requestAuthCommand = "07";
|
||||
private const string _responseAuthCommand = "08";
|
||||
private const string _challengePrefix = "CHAL";
|
||||
private const string _encryptedResponsePrefix = "RAES";
|
||||
private const string _readyResponse = "READY!";
|
||||
private const string _cabinetLightingOnCommand = "856001";
|
||||
private const string _cabinetLightingOffCommand = "856000";
|
||||
private const int _authStepTimeoutMs = 3000;
|
||||
private const int _authReadyTimeoutMs = 5000;
|
||||
private const int _inputIdleBeforePingMs = 60_000;
|
||||
private const int _keepAlivePollMs = 5000;
|
||||
private const int _reconnectRetryMs = 10_000;
|
||||
|
||||
private static CancellationTokenSource _lifetimeCts;
|
||||
private static Thread _readerThread;
|
||||
private static CancellationTokenSource _readerCts;
|
||||
|
||||
private static int h;
|
||||
public static int BoardVersion { get; private set; }
|
||||
private static ILogger logger;
|
||||
private static string[] AvailablePorts => ComNativeWrapper.ListPorts();
|
||||
private static int _lastInputTick;
|
||||
private static string _controllerPort;
|
||||
private static volatile bool _isAuthorized;
|
||||
|
||||
private static int _lastInputTick = 0;
|
||||
private const int INPUT_IDLE_BEFORE_PING_MS = 60_000;//1 минута
|
||||
public static int BoardVersion { get; private set; }
|
||||
|
||||
private static ILogger logger;
|
||||
|
||||
public static void Initialize(string port, int boardVersion)//Добавить логгер в конструктор
|
||||
{
|
||||
BoardVersion = boardVersion;
|
||||
_controllerPort = port;
|
||||
|
||||
if (_lifetimeCts != null) return;
|
||||
OpenPort(port);
|
||||
@ -45,7 +57,7 @@ public static class ComWrapper
|
||||
}
|
||||
UnityMainThreadDispatcher.Initialize();
|
||||
_lifetimeCts = new CancellationTokenSource();
|
||||
|
||||
MarkInputActivity();
|
||||
|
||||
StartReaderThread();
|
||||
_ = SetupBoardAsync(_lifetimeCts.Token);
|
||||
@ -130,13 +142,19 @@ public static class ComWrapper
|
||||
|
||||
if (line == "COIN")
|
||||
{
|
||||
if (!_isAuthorized)
|
||||
return;
|
||||
|
||||
PaymentReceived?.Invoke();
|
||||
Send("10");
|
||||
|
||||
}
|
||||
else if (line.StartsWith("INPUT"))
|
||||
{
|
||||
Volatile.Write(ref _lastInputTick, Environment.TickCount);
|
||||
if (!_isAuthorized)
|
||||
return;
|
||||
|
||||
MarkInputActivity();
|
||||
|
||||
string rawInputString = line.Substring(5);
|
||||
string inputString = BoardVersion == 2 ? DecimalToBinary(rawInputString) : rawInputString;
|
||||
@ -215,8 +233,8 @@ public static class ComWrapper
|
||||
|
||||
static Task<string> WaitForPrefixAsync(string prefix, int timeoutMs, CancellationToken ct)
|
||||
=> WaitForAsync(s => s.StartsWith(prefix, StringComparison.Ordinal), timeoutMs, ct);
|
||||
static Task<string> WaitForContainsAsync(string prefix, int timeoutMs, CancellationToken ct)
|
||||
=> WaitForAsync(s => s.IndexOf(prefix, StringComparison.Ordinal) >= 0, timeoutMs, ct);
|
||||
static Task<string> WaitForExactAsync(string expected, int timeoutMs, CancellationToken ct)
|
||||
=> WaitForAsync(s => string.Equals(s, expected, StringComparison.Ordinal), timeoutMs, ct);
|
||||
|
||||
static void OnLine(string line)
|
||||
{
|
||||
@ -249,18 +267,12 @@ public static class ComWrapper
|
||||
|
||||
private static async Task SetupBoardAsync(CancellationToken ct)
|
||||
{
|
||||
bool auth;
|
||||
if (BoardVersion == 1)
|
||||
auth = await BoardAuthAsync(ct);
|
||||
else
|
||||
auth = true;
|
||||
_isAuthorized = await BoardAuthAsync(ct);
|
||||
|
||||
if (!auth)
|
||||
{
|
||||
if (!_isAuthorized)
|
||||
Log("Авторизация на плате не пройдена.");
|
||||
UnityMainThreadDispatcher.Instance.Enqueue(() => Application.Quit());
|
||||
return;
|
||||
}
|
||||
else
|
||||
HandleAuthorizationSucceeded();
|
||||
|
||||
_ = StartKeepAliveProcessAsync(ct);
|
||||
}
|
||||
@ -270,7 +282,6 @@ public static class ComWrapper
|
||||
{
|
||||
Log("Инициализация порта");
|
||||
|
||||
ComNativeWrapper.Serial_CloseAll();
|
||||
Log("Открываю порт: " + port);
|
||||
|
||||
h = ComNativeWrapper.Serial_Open(port, 115200, 8, 0, 1, 4, 10);
|
||||
@ -351,52 +362,70 @@ public static class ComWrapper
|
||||
|
||||
private static async Task<bool> BoardAuthAsync(CancellationToken ct)
|
||||
{
|
||||
//Проверяем зашифрована или нет
|
||||
using CancellationTokenSource authCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
CancellationToken authToken = authCts.Token;
|
||||
string computerChallenge = BoardAuthentication.CreateChallengeHex();
|
||||
|
||||
try
|
||||
{
|
||||
Send(REQUEST_INPUT_COMMAND);
|
||||
var inputLine = await WaitForPrefixAsync("INPUT", 1000, ct);
|
||||
Task<string> controllerChallengeTask = WaitForAsync(
|
||||
line => BoardAuthentication.TryExtractHexPayload(line, _challengePrefix, out _),
|
||||
_authStepTimeoutMs,
|
||||
authToken);
|
||||
Send(_requestAuthCommand + computerChallenge);
|
||||
|
||||
string controllerChallengeLine = await controllerChallengeTask;
|
||||
if (!BoardAuthentication.TryExtractHexPayload(
|
||||
controllerChallengeLine,
|
||||
_challengePrefix,
|
||||
out string controllerChallenge))
|
||||
return false;
|
||||
|
||||
Task<string> encryptedResponseTask = WaitForAsync(
|
||||
line => BoardAuthentication.TryExtractHexPayload(line, _encryptedResponsePrefix, out _),
|
||||
_authStepTimeoutMs,
|
||||
authToken);
|
||||
Task<string> readyTask = WaitForExactAsync(
|
||||
_readyResponse,
|
||||
_authReadyTimeoutMs,
|
||||
authToken);
|
||||
Send(_responseAuthCommand + BoardAuthentication.EncryptHexBlock(controllerChallenge));
|
||||
|
||||
string encryptedResponseLine = await encryptedResponseTask;
|
||||
if (!BoardAuthentication.TryExtractHexPayload(
|
||||
encryptedResponseLine,
|
||||
_encryptedResponsePrefix,
|
||||
out string encryptedResponse)
|
||||
|| !BoardAuthentication.ValidateEncryptedResponse(computerChallenge, encryptedResponse))
|
||||
{
|
||||
Log("Контроллер не прошёл взаимную AES-проверку.");
|
||||
return false;
|
||||
}
|
||||
|
||||
await readyTask;
|
||||
Log("Авторизация платы полностью завершена по READY!.");
|
||||
return true;
|
||||
}
|
||||
catch (TimeoutException)
|
||||
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
||||
{
|
||||
//Не пришел инпут
|
||||
Log("Шифрованная плата");
|
||||
}
|
||||
|
||||
//Запрашиваем шифрованную строку
|
||||
string encLine;
|
||||
try
|
||||
{
|
||||
Send(REQUEST_ENCRYPTED_COMMAND);
|
||||
encLine = await WaitForPrefixAsync("ENC:", 1000, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log($"Зашифрованная строка не пришла");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Расшифровка
|
||||
var encrypted = encLine.Substring(4);
|
||||
var decrypted = BoardUtility.DecryptSerial(encrypted);
|
||||
|
||||
//Отправляем расшифрованное на подтверждение
|
||||
try
|
||||
catch (Exception exception)
|
||||
{
|
||||
Send(REQUEST_CONFIRMATION_COMMAND + decrypted);
|
||||
await WaitForContainsAsync("OK", 1000, ct);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log("Подтверждение не пришло");
|
||||
Log($"Ошибка авторизации платы: {exception.Message}");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
authCts.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
if (h > 0)
|
||||
Send(_cabinetLightingOffCommand);
|
||||
|
||||
_lifetimeCts?.Cancel();
|
||||
_lifetimeCts?.Dispose();
|
||||
_lifetimeCts = null;
|
||||
@ -404,6 +433,8 @@ public static class ComWrapper
|
||||
StopReaderThread();
|
||||
CancelAllWaiters();
|
||||
ClosePort();
|
||||
_isAuthorized = false;
|
||||
_controllerPort = null;
|
||||
}
|
||||
|
||||
private static void CancelAllWaiters()
|
||||
@ -431,7 +462,6 @@ public static class ComWrapper
|
||||
}
|
||||
|
||||
|
||||
//Сделать ожидание простоя около минуты на проверку и переподелючение.
|
||||
private static async Task StartKeepAliveProcessAsync(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@ -439,20 +469,32 @@ public static class ComWrapper
|
||||
Log("Запуск асинхронной проверки порта");
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
if (!IsInputIdleTooLong())
|
||||
if (!_isAuthorized)
|
||||
{
|
||||
await Task.Delay(5000, ct);
|
||||
Log("Нет авторизованного соединения. Запускаю переподключение.");
|
||||
await ReinitializePortAsync(ct);
|
||||
await Task.Delay(_reconnectRetryMs, ct);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsInputIdleTooLong())
|
||||
{
|
||||
await Task.Delay(_keepAlivePollMs, ct);
|
||||
continue;
|
||||
}
|
||||
|
||||
//Пингуем плату
|
||||
if (!await CheckPortConnectionAsync(ct))
|
||||
{
|
||||
Log("Плата отвалилась. Запускаю переподключение.");
|
||||
_isAuthorized = false;
|
||||
await ReinitializePortAsync(ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
MarkInputActivity();
|
||||
}
|
||||
|
||||
await Task.Delay(10000, ct);
|
||||
await Task.Delay(_keepAlivePollMs, ct);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@ -470,8 +512,9 @@ public static class ComWrapper
|
||||
{
|
||||
try
|
||||
{
|
||||
Send(REQUEST_INPUT_COMMAND);
|
||||
await WaitForPrefixAsync("INPUT", 1000, ct);
|
||||
Task<string> inputResponseTask = WaitForPrefixAsync("INPUT", 1000, ct);
|
||||
Send(_requestInputCommand);
|
||||
await inputResponseTask;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@ -480,40 +523,57 @@ public static class ComWrapper
|
||||
}
|
||||
}
|
||||
|
||||
//реализовать
|
||||
private static async Task ReinitializePortAsync(CancellationToken ct)
|
||||
private static async Task<bool> ReinitializePortAsync(CancellationToken ct)
|
||||
{
|
||||
StopReaderThread();
|
||||
CancelAllWaiters();
|
||||
ClosePort();
|
||||
|
||||
OpenPort(AvailablePorts[^1]);
|
||||
if (string.IsNullOrWhiteSpace(_controllerPort))
|
||||
{
|
||||
Log("Не сохранён COM-порт контроллера.");
|
||||
return false;
|
||||
}
|
||||
|
||||
OpenPort(_controllerPort);
|
||||
|
||||
if (h <= 0)
|
||||
{
|
||||
Log($"Ошибка при переподключении.");
|
||||
return;
|
||||
Log("Ошибка при переподключении.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
StartReaderThread();
|
||||
var auth = await BoardAuthAsync(ct);
|
||||
if (!auth)
|
||||
_isAuthorized = await BoardAuthAsync(ct);
|
||||
if (!_isAuthorized)
|
||||
{
|
||||
Application.Quit();
|
||||
Debug.Log("Не пройдена авторизация на плате. Выход.");
|
||||
Log("Повторная авторизация платы не пройдена.");
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleAuthorizationSucceeded();
|
||||
Log($"Соединение с {_controllerPort} восстановлено и авторизовано.");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsInputIdleTooLong()
|
||||
{
|
||||
int last = Volatile.Read(ref _lastInputTick);
|
||||
if (last == 0) return true;
|
||||
if (last == 0) return false;
|
||||
int now = Environment.TickCount;
|
||||
|
||||
// переполнение TickCount тут ок: сравнение интервала < 24 дней работает корректно
|
||||
int elapsed = unchecked(now - last);
|
||||
return elapsed >= INPUT_IDLE_BEFORE_PING_MS;
|
||||
return elapsed >= _inputIdleBeforePingMs;
|
||||
}
|
||||
|
||||
private static void MarkInputActivity()
|
||||
=> Volatile.Write(ref _lastInputTick, Environment.TickCount);
|
||||
|
||||
private static void HandleAuthorizationSucceeded()
|
||||
{
|
||||
MarkInputActivity();
|
||||
Send(_cabinetLightingOnCommand);
|
||||
}
|
||||
|
||||
private static void Log(string message) => logger?.Log($"CW{message}");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user