# DMM Related Code This file is a reference-only extraction. It is not compiled by the Housing app. ## Source Files - `Services/EquipmentMeasurementSettings.cs` - `Services/EquipmentMeasurementService.cs` - `Services/IScpiClient.cs` - `Services/TcpScpiClient.cs` - `Services/SerialScpiClient.cs` - `Services/VisaScpiClient.cs` ## Hardware.ini Keys ```ini [Equipment] Timeout=5000 SettleMilliseconds=300 ; 34465A DMM VoltageConnection=LAN VoltageResource= VoltageIdnMatch=34465A VoltageHost=192.168.10.13 VoltagePort=5025 VoltageSetupCommand= VoltageReadCommand=MEAS:VOLT:DC? ``` ## Settings Load Code ```csharp Voltage = LoadChannel( values, "Voltage", "MEAS:VOLT:DC?", "34460,34461,34465,34470,3446,3447"); ``` ```csharp private static ScpiChannelSettings LoadChannel( Dictionary values, string prefix, string defaultReadCommand, string defaultIdnMatch) { return new ScpiChannelSettings { Connection = GetString(values, prefix + "Connection", string.Empty), ResourceName = GetString(values, prefix + "Resource", string.Empty), IdnMatch = GetString(values, prefix + "IdnMatch", defaultIdnMatch), Host = GetString(values, prefix + "Host", string.Empty), Port = GetInt(values, prefix + "Port", 5025), PortName = GetString(values, prefix + "PortName", string.Empty), BaudRate = GetInt(values, prefix + "BaudRate", 115200), Terminator = GetString(values, prefix + "Terminator", string.Empty), SetupCommand = GetString(values, prefix + "SetupCommand", string.Empty), CleanupCommand = GetString(values, prefix + "CleanupCommand", string.Empty), ReadCommand = GetString(values, prefix + "ReadCommand", defaultReadCommand) }; } ``` ## Voltage Read Flow ```csharp var voltage = await ReadChannelAsync("VOLTAGE", _settings.Voltage, log); ``` ```csharp private async Task ReadChannelAsync( string label, ScpiChannelSettings channel, StringBuilder log, bool skipSetupCommand = false) { if (IsDisabled(channel)) { log.AppendLine($"> {label} SKIP: disabled in Hardware.ini"); return 0m; } if (string.IsNullOrWhiteSpace(channel.ReadCommand)) { throw new InvalidOperationException($"Hardware.ini [Equipment] {label} ReadCommand 값을 확인하세요."); } using var client = await CreateClientAsync(label, channel); return await ReadChannelWithClientAsync(label, channel, client, log, skipSetupCommand); } ``` ```csharp private static async Task ReadChannelWithClientAsync( string label, ScpiChannelSettings channel, IScpiClient client, StringBuilder log, bool skipSetupCommand = false) { if (string.IsNullOrWhiteSpace(channel.ReadCommand)) { throw new InvalidOperationException($"Hardware.ini [Equipment] {label} ReadCommand 값을 확인하세요."); } log.AppendLine($"> {label} CONNECT: {DescribeClient(client, channel)}"); if (!skipSetupCommand && !string.IsNullOrWhiteSpace(channel.SetupCommand)) { log.AppendLine($"> {label} SETUP: {channel.SetupCommand}"); await SendCommandListAsync(client, channel.SetupCommand); } log.AppendLine($"> {label} READ: {channel.ReadCommand}"); var response = await client.QueryAsync(channel.ReadCommand); log.AppendLine(response); return ExtractFirstDecimal(response, label); } ``` ## Shared SCPI Connection Code ```csharp private Task CreateClientAsync(string label, ScpiChannelSettings channel) { var connection = GetConnectionType(channel); return connection switch { "TCP" or "LAN" or "ETHERNET" => CreateTcpClientAsync(label, channel), "SERIAL" or "COM" => Task.FromResult( new SerialScpiClient( channel.PortName, channel.BaudRate, _settings.Timeout, channel.GetIdnMatches(), channel.GetTerminator())), "VISA" or "USB" => CreateVisaClientAsync(channel), _ => throw new InvalidOperationException($"{label} 장비 연결 방식은 Visa, Serial, Tcp 중 하나로 설정하세요.") }; } ``` ```csharp private static decimal ExtractFirstDecimal(string response, string label) { var match = Regex.Match(response, @"[-+]?\d+(?:\.\d+)?(?:[Ee][-+]?\d+)?"); if (!match.Success) { throw new InvalidOperationException($"{label} 측정값 파싱 실패"); } return decimal.Parse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture); } ```