using System; using System.Globalization; using System.Text.RegularExpressions; using leak_test_project.Models; namespace leak_test_project.Utils { public static class SentinelParser { // 실시간 스트리밍 데이터 정규식 (헤더 이후 본문 예: LR 0.123456 sccm) private static readonly Regex BodyValueRegex = new Regex(@"(?[A-Z]{2})\s+(?-?\d+\.?\d*)\s+(?\w+)", RegexOptions.Compiled); /// /// C28 헤더 추출 (매뉴얼 기준: XXYYZZZ + Tab/Space + H + Tab + Body) /// XX: 8-Bit CRC, YY: Sequence Code, ZZZ: Data Length, H: Data Type Code /// public static string ExtractBody(string input, out char typeCode) { typeCode = '\0'; if (string.IsNullOrWhiteSpace(input) || input.Length < 8) return input; // 정규식을 통해 "XXYYZZZ(7글자) + 스페이스/탭 + H(1글자) + 스페이스/탭 + 데이터" 패턴 매칭 var match = Regex.Match(input.TrimStart(), @"^.{7}[\s\t]+([a-zA-Z])[\s\t]+(.*)$", RegexOptions.Singleline); if (match.Success) { typeCode = char.ToUpper(match.Groups[1].Value[0]); return match.Groups[2].Value.Trim(); } // 폴백(Fallback): 정규식 실패 시 첫 번째 탭 기준으로 파싱 int firstTabIndex = input.IndexOf('\t'); if (firstTabIndex != -1) { string beforeTab = input.Substring(0, firstTabIndex).TrimEnd(); if (beforeTab.Length > 0) { // 탭 바로 앞의 문자를 Data Type Code(H)로 간주 typeCode = char.ToUpper(beforeTab[beforeTab.Length - 1]); } // 두 번째 탭이 있는지 확인 int secondTabIndex = input.IndexOf('\t', firstTabIndex + 1); if (secondTabIndex != -1 && (secondTabIndex - firstTabIndex) <= 2) { // H 문자가 두 탭 사이에 있는 경우 (XXYYZZZ\tH\tDATA) // typeCode는 두 번째 탭 바로 앞 문자가 됨 string betweenTabs = input.Substring(firstTabIndex + 1, secondTabIndex - firstTabIndex - 1).Trim(); if (betweenTabs.Length > 0) typeCode = char.ToUpper(betweenTabs[betweenTabs.Length - 1]); return input.Substring(secondTabIndex + 1).Trim(); } return input.Substring(firstTabIndex + 1).Trim(); } return input; } /// /// 스트리밍 값 파싱 /// public static ParsedData ParseStreamingValue(string input) { var result = new ParsedData { RawData = input }; string body = ExtractBody(input, out char type); // 스트리밍 데이터는 본문 내에도 탭이나 콤마가 있을 수 있음 var match = BodyValueRegex.Match(body); if (match.Success) { result.MeasuredValue = double.Parse(match.Groups["value"].Value, CultureInfo.InvariantCulture); result.Unit = match.Groups["unit"].Value; } return result; } /// /// 최종 결과 데이터 파싱 (C28 매뉴얼 Appendix D 참조) /// 형식: Channel# Port# Program# LinkInfo Time Date UniqueID Evaluation SPCFlag TestType TestEval TestData1 ... /// 구분자: TAB (\t) /// public static ParsedData ParseFinalResult(string input) { var result = new ParsedData { RawData = input }; try { // 헤더 제거 및 필드 분리 (탭 기준) string body = ExtractBody(input, out char type); var fields = body.Split(new[] { '\t' }, StringSplitOptions.None); if (fields.Length >= 8) { result.ChannelNo = fields[0].Trim(); // C## result.ProgramNo = fields[2].Trim(); // P## result.TestTime = fields[4].Trim(); // HH:MM:SS.XXX result.TestDate = fields[5].Trim(); // MM/DD/YY result.UniqueId = fields[6].Trim(); // ########## result.SerialNo = result.UniqueId.Length > 5 ? result.UniqueId.Substring(result.UniqueId.Length - 5) : result.UniqueId; // 판정 코드 매핑 (A=Accept, R=Reject) string eval = fields[7].Trim(); result.Judgment = (eval == "A") ? "OK" : "NG"; result.SensorJudgment = eval; // 센서 원본 판정 코드 보존 (교차 검증용) // 측정값 필드 검색 (TestData 1, 2...) // 매뉴얼 예시: PLR, P, LR 0.123456 sccm for (int i = 9; i < fields.Length; i++) { var match = BodyValueRegex.Match(fields[i]); if (match.Success) { result.MeasuredValue = double.Parse(match.Groups["value"].Value, CultureInfo.InvariantCulture); result.Unit = match.Groups["unit"].Value; break; } } } } catch (Exception ex) { Console.WriteLine($"[C28 Parse Error] {ex.Message}"); } return result; } public static string EvaluateJudgment(double value, double ul, double ll) { if (value >= ll && value <= ul) return "OK"; return "NG"; } } }