using System.Text; using System.Text.RegularExpressions; using Housing.Models; namespace Housing.Services; public sealed class BoardTestService { private readonly BoardHardwareSettings _boardSettings; private readonly EquipmentMeasurementSettings _equipmentSettings; public BoardTestService(BoardHardwareSettings boardSettings, EquipmentMeasurementSettings equipmentSettings) { _boardSettings = boardSettings; _equipmentSettings = equipmentSettings; } public async Task RunAsync( Func? onIcSnReadAsync = null, DateTime? measurementNotBeforeUtc = null) { var log = new StringBuilder(); var equipmentService = new EquipmentMeasurementService(_equipmentSettings); log.Append(await equipmentService.RunPreBoardCommandAsync()); if (_equipmentSettings.PreBoardDelayMilliseconds > 0) { log.AppendLine($"> CURRENT PRE-BOARD DELAY: {_equipmentSettings.PreBoardDelayMilliseconds} ms"); await Task.Delay(_equipmentSettings.PreBoardDelayMilliseconds); } using var boardClient = new SerialBoardClient(_boardSettings); log.AppendLine($"> BOARD PORT: {boardClient.PortName} @ {_boardSettings.BaudRate}"); await SendOnlyAndLogAsync(boardClient, "PRE_CONNECT", _boardSettings.PreConnectCommand, log); var connectResponse = await SendAndLogAsync(boardClient, "CONNECT", _boardSettings.ConnectCommand, _boardSettings.ReadTimeout, true, log); if (!ContainsSuccess(connectResponse)) { throw new InvalidOperationException("시료 연결 확인 실패"); } var idResponse = await SendAndLogAsync(boardClient, "READ_ID", _boardSettings.ReadIdCommand, _boardSettings.ReadTimeout, true, log); var icSn = ExtractFirstHexLine(idResponse); if (string.IsNullOrWhiteSpace(icSn)) { throw new InvalidOperationException("IC_SN 읽기 실패"); } await SendOnlyAndLogAsync(boardClient, "POST_READ_ID", _boardSettings.PostReadIdCommand, log); if (onIcSnReadAsync is not null) { await onIcSnReadAsync(icSn); } await SendAndLogAsync(boardClient, "CAL_DEFAULT", _boardSettings.CalDefaultCommand, _boardSettings.ReadTimeout, false, log); if (measurementNotBeforeUtc.HasValue) { var remainingDelay = measurementNotBeforeUtc.Value - DateTime.UtcNow; if (remainingDelay > TimeSpan.Zero) { log.AppendLine($"> MEASUREMENT START DELAY: {(int)remainingDelay.TotalMilliseconds} ms"); await Task.Delay(remainingDelay); } } var equipmentResult = await equipmentService.ReadAsync(); log.Append(equipmentResult.RawLog); return new BoardMeasurementResult { IcSn = icSn, Voltage = equipmentResult.Voltage, Current = equipmentResult.Current, RawLog = log.ToString() }; } private static async Task SendAndLogAsync( SerialBoardClient client, string label, string command, int timeoutMilliseconds, bool waitForEndToken, StringBuilder log) { log.AppendLine($"> BOARD {label}: {command}"); var response = await client.SendCommandAsync(command, timeoutMilliseconds, waitForEndToken); log.Append(response); return response; } private static async Task SendOnlyAndLogAsync( SerialBoardClient client, string label, string command, StringBuilder log) { if (string.IsNullOrWhiteSpace(command)) { return; } log.AppendLine($"> BOARD {label}: {command}"); await client.SendOnlyAsync(command); } private static bool ContainsSuccess(string response) { return response .Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries) .Any(line => string.Equals(line.Trim(), "Success", StringComparison.OrdinalIgnoreCase)); } private static string ExtractFirstHexLine(string response) { foreach (var rawLine in response.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries)) { var line = rawLine.Trim(); if (line.Equals("", StringComparison.OrdinalIgnoreCase) || line.Equals("Fail", StringComparison.OrdinalIgnoreCase)) { continue; } if (Regex.IsMatch(line, "^[0-9A-Fa-f]{8,32}$")) { return line.ToUpperInvariant(); } } return string.Empty; } }