Browse Source

add DB

main
gudae 2 months ago
parent
commit
a2a5cdeeb6
  1. BIN
      .vs/Housing/DesignTimeBuild/.dtbcache.v2
  2. 28
      Hardware.ini
  3. 5
      MainWindow.xaml
  4. 112
      MainWindow.xaml.cs
  5. 8
      Models/HousingAssemblyRecord.cs
  6. 27
      Services/BoardHardwareSettings.cs
  7. 36
      Services/BoardTestService.cs
  8. 144
      Services/EquipmentMeasurementService.cs
  9. 15
      Services/EquipmentMeasurementSettings.cs
  10. 74
      Services/HousingAssemblyRepository.cs
  11. 19
      Services/SerialBoardClient.cs
  12. 96
      Services/SerialScpiClient.cs

BIN
.vs/Housing/DesignTimeBuild/.dtbcache.v2

Binary file not shown.

28
Hardware.ini

@ -1,11 +1,14 @@
[Board]
PortName=Auto
PortName=COM5
BaudRate=115200
ReadTimeout=5000
PreConnectCommand=x00o
ConnectCommand=x00c_001101:owt28006727ea97c7801
ReadIdCommand=x00c_001101:ow2800326003e
CalDefaultCommand=x00c_001001
CalDefaultCommand=x00c_001001:owt28006727ea97c7801
EndToken=<end>
DtrEnable=false
RtsEnable=false
[Equipment]
; 목표: 장비 화면에 표시되는 현재 측정값을 읽습니다.
@ -15,20 +18,31 @@ EndToken=<end>
; 예: USB0::0x2A8D::0x1301::MY12345678::INSTR
Timeout=5000
SettleMilliseconds=300
PreBoardCommand=VOLT 5, (@2);OUTP ON, (@2)
PreBoardDelayMilliseconds=2000
LogoutCommand=OUTP OFF, (@1);OUTP OFF, (@2)
; 34465A DMM
VoltageConnection=Visa
VoltageConnection=Tcp
VoltageResource=
VoltageIdnMatch=34460,34461,34465,34470,3446,3447
VoltageHost=192.168.0.3
VoltagePort=5025
VoltageSetupCommand=
VoltageReadCommand=MEAS:VOLT:DC?
; E36233A Power Supply
CurrentConnection=Visa
; E36200 Series Power Supply
CurrentConnection=Tcp
CurrentResource=
CurrentIdnMatch=E362
CurrentSetupCommand=
CurrentReadCommand=MEAS:CURR? CH1
CurrentHost=
CurrentPort=5025
CurrentPortName=
CurrentBaudRate=9600
CurrentTerminator=LF
CurrentSetupCommand=OUTP ON, (@1)
CurrentCleanupCommand=OUTP OFF, (@1)
CurrentReadCommand=MEAS:CURR? (@1)
; COM 포트로 잡히는 장비 예시:
; VoltageConnection=Serial

5
MainWindow.xaml

@ -528,8 +528,9 @@
BorderBrush="{StaticResource HmiWarningBrush}"
FontSize="22"
FontWeight="SemiBold"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Top"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
TextAlignment="Center"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"/>

112
MainWindow.xaml.cs

@ -109,16 +109,22 @@ public partial class MainWindow : Window
var boardSettings = BoardHardwareSettings.Load(GetHardwareIniPath());
var equipmentSettings = EquipmentMeasurementSettings.Load(GetHardwareIniPath());
var service = new BoardTestService(boardSettings, equipmentSettings);
var measurement = await service.RunAsync();
string? dbHeaderSaveError = null;
var measurement = await service.RunAsync(async icSn =>
{
IcSnOutputTextBox.Text = icSn;
dbHeaderSaveError = await SaveHeaderStatusAsync(icSn);
});
IcSnOutputTextBox.Text = measurement.IcSn;
VOutputTextBox.Text = FormatDecimal(measurement.Voltage);
AOutputTextBox.Text = FormatDecimal(measurement.Current);
var resultCode = _inspectionSettings.IsPass(measurement.Voltage, measurement.Current) ? "OK" : "NG";
ResultOutputTextBox.Text = $"{resultCode}\r\n{measurement.RawLog}";
ResultOutputTextBox.Text = resultCode;
await SaveCurrentResultAsync(resultCode);
var dbMeasurementSaveError = await SaveMeasurementStatusAsync(resultCode);
ResultOutputTextBox.Text = $"{resultCode}\r\n{BuildDbSaveMessage(dbHeaderSaveError, dbMeasurementSaveError)}";
}
catch (Exception ex)
{
@ -132,8 +138,11 @@ public partial class MainWindow : Window
}
}
private void LogoutButton_Click(object sender, RoutedEventArgs e)
private async void LogoutButton_Click(object sender, RoutedEventArgs e)
{
LogoutButton.IsEnabled = false;
await RunEquipmentLogoutCommandAsync();
ResetScreen();
_loginResult = new StartupLoginWindowResult();
OperatorTextBlock.Text = "-";
@ -145,6 +154,20 @@ public partial class MainWindow : Window
}
}
private async Task RunEquipmentLogoutCommandAsync()
{
try
{
var equipmentSettings = EquipmentMeasurementSettings.Load(GetHardwareIniPath());
var equipmentService = new EquipmentMeasurementService(equipmentSettings);
await equipmentService.RunLogoutCommandAsync();
}
catch (Exception ex)
{
MessageBox.Show(this, $"파워서플라이 logout 전원 OFF 실패: {ex.Message}", "Logout", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
private bool ShowLoginPopup()
{
var loginWindow = new StartupLoginWindow
@ -166,9 +189,11 @@ public partial class MainWindow : Window
private void ApplyLoginResult()
{
OperatorTextBlock.Text = string.IsNullOrWhiteSpace(_loginResult.Operator) ? "-" : _loginResult.Operator;
LogoutButton.Visibility = string.IsNullOrWhiteSpace(_loginResult.Operator)
? Visibility.Collapsed
: Visibility.Visible;
var isLoggedIn = !string.IsNullOrWhiteSpace(_loginResult.Operator);
LogoutButton.Visibility = isLoggedIn
? Visibility.Visible
: Visibility.Collapsed;
LogoutButton.IsEnabled = isLoggedIn;
}
private void ResetScreen()
@ -201,33 +226,58 @@ public partial class MainWindow : Window
}), DispatcherPriority.Input);
}
private async Task SaveCurrentResultAsync(string resultCode)
private async Task<string?> SaveHeaderStatusAsync(string icSn)
{
try
{
var repository = new HousingAssemblyRepository(DatabaseSettings.Load(GetDatabaseIniPath()));
var record = CreateCurrentRecord(resultCode);
await repository.InsertAsync(record);
var record = CreateHeaderRecord(icSn);
await repository.InsertHeaderAsync(record);
ResultOutputTextBox.Text += "\r\nDB 저장 완료";
return null;
}
catch (Exception ex)
{
ResultOutputTextBox.Text += $"\r\nDB 저장 실패: {ex.Message}";
return ex.Message;
}
}
private HousingAssemblyRecord CreateCurrentRecord(string resultCode)
private async Task<string?> SaveMeasurementStatusAsync(string resultCode)
{
var barcode = BarcodeTextBox.Text.Trim();
var icSn = IcSnOutputTextBox.Text.Trim();
try
{
var repository = new HousingAssemblyRepository(DatabaseSettings.Load(GetDatabaseIniPath()));
var record = CreateMeasurementRecord(resultCode);
await repository.UpdateMeasurementAsync(record);
if (string.IsNullOrWhiteSpace(icSn))
return null;
}
catch (Exception ex)
{
icSn = barcode;
IcSnOutputTextBox.Text = icSn;
return ex.Message;
}
}
private static string BuildDbSaveMessage(params string?[] errors)
{
var failureMessages = new List<string>();
foreach (var error in errors)
{
if (!string.IsNullOrWhiteSpace(error))
{
failureMessages.Add(error);
}
}
return failureMessages.Count == 0
? "DB 저장 완료"
: $"DB 저장 실패 ({string.Join("; ", failureMessages)})";
}
private HousingAssemblyRecord CreateHeaderRecord(string icSn)
{
var barcode = BarcodeTextBox.Text.Trim();
return new HousingAssemblyRecord
{
IcSn = icSn,
@ -241,10 +291,32 @@ public partial class MainWindow : Window
ProductionDate = DateTime.Now,
Line = _loginResult.LineNo,
LotNo = _loginResult.LotNo,
JigNo = _loginResult.JigNo,
JigNo = _loginResult.JigNo
};
}
private HousingAssemblyRecord CreateMeasurementRecord(string resultCode)
{
var barcode = BarcodeTextBox.Text.Trim();
var icSn = IcSnOutputTextBox.Text.Trim();
if (string.IsNullOrWhiteSpace(icSn))
{
icSn = barcode;
IcSnOutputTextBox.Text = icSn;
}
return new HousingAssemblyRecord
{
IcSn = icSn,
PcbBarcode = barcode,
PtVol1 = ParseDecimal(VOutputTextBox.Text),
PtCurrent1 = ParseDecimal(AOutputTextBox.Text),
Result = resultCode
Result = resultCode,
Spare1 = null,
Spare2 = null,
Spare3 = null,
Spare4 = null
};
}

8
Models/HousingAssemblyRecord.cs

@ -19,8 +19,8 @@ public sealed class HousingAssemblyRecord
public decimal PtVol1 { get; set; }
public decimal PtCurrent1 { get; set; }
public string Result { get; set; } = string.Empty;
public string Spare1 { get; set; } = string.Empty;
public string Spare2 { get; set; } = string.Empty;
public string Spare3 { get; set; } = string.Empty;
public string Spare4 { get; set; } = string.Empty;
public string? Spare1 { get; set; }
public string? Spare2 { get; set; }
public string? Spare3 { get; set; }
public string? Spare4 { get; set; }
}

27
Services/BoardHardwareSettings.cs

@ -7,10 +7,13 @@ public sealed class BoardHardwareSettings
public string PortName { get; set; } = "Auto";
public int BaudRate { get; set; } = 115200;
public int ReadTimeout { get; set; } = 5000;
public string PreConnectCommand { get; set; } = "x00o";
public string ConnectCommand { get; set; } = "x00c_001101:owt28006727ea97c7801";
public string ReadIdCommand { get; set; } = "x00c_001101:ow2800326003e";
public string CalDefaultCommand { get; set; } = "x00c_001001";
public string CalDefaultCommand { get; set; } = "x00c_001001:owt28006727ea97c7801";
public string EndToken { get; set; } = "<end>";
public bool DtrEnable { get; set; }
public bool RtsEnable { get; set; }
public static BoardHardwareSettings Load(string filePath)
{
@ -25,10 +28,13 @@ public sealed class BoardHardwareSettings
PortName = GetString(values, "PortName", "Auto"),
BaudRate = GetInt(values, "BaudRate", 115200),
ReadTimeout = GetInt(values, "ReadTimeout", 5000),
PreConnectCommand = GetString(values, "PreConnectCommand", "x00o"),
ConnectCommand = GetString(values, "ConnectCommand", "x00c_001101:owt28006727ea97c7801"),
ReadIdCommand = GetString(values, "ReadIdCommand", "x00c_001101:ow2800326003e"),
CalDefaultCommand = GetString(values, "CalDefaultCommand", "x00c_001001"),
EndToken = GetString(values, "EndToken", "<end>")
CalDefaultCommand = GetString(values, "CalDefaultCommand", "x00c_001001:owt28006727ea97c7801"),
EndToken = GetString(values, "EndToken", "<end>"),
DtrEnable = GetBool(values, "DtrEnable", false),
RtsEnable = GetBool(values, "RtsEnable", false)
};
}
@ -45,4 +51,19 @@ public sealed class BoardHardwareSettings
? result
: defaultValue;
}
private static bool GetBool(Dictionary<string, string> values, string key, bool defaultValue)
{
if (!values.TryGetValue(key, out var value) || string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
return value.Trim().ToUpperInvariant() switch
{
"1" or "TRUE" or "YES" or "ON" => true,
"0" or "FALSE" or "NO" or "OFF" => false,
_ => defaultValue
};
}
}

36
Services/BoardTestService.cs

@ -15,12 +15,23 @@ public sealed class BoardTestService
_equipmentSettings = equipmentSettings;
}
public async Task<BoardMeasurementResult> RunAsync()
public async Task<BoardMeasurementResult> RunAsync(Func<string, Task>? onIcSnReadAsync = null)
{
using var boardClient = new SerialBoardClient(_boardSettings);
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))
{
@ -34,9 +45,13 @@ public sealed class BoardTestService
throw new InvalidOperationException("IC_SN 읽기 실패");
}
if (onIcSnReadAsync is not null)
{
await onIcSnReadAsync(icSn);
}
await SendAndLogAsync(boardClient, "CAL_DEFAULT", _boardSettings.CalDefaultCommand, _boardSettings.ReadTimeout, false, log);
var equipmentService = new EquipmentMeasurementService(_equipmentSettings);
var equipmentResult = await equipmentService.ReadAsync();
log.Append(equipmentResult.RawLog);
@ -63,6 +78,21 @@ public sealed class BoardTestService
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

144
Services/EquipmentMeasurementService.cs

@ -14,40 +14,131 @@ public sealed class EquipmentMeasurementService
_settings = settings;
}
public async Task<string> RunPreBoardCommandAsync()
{
var log = new StringBuilder();
if (IsDisabled(_settings.Current) || string.IsNullOrWhiteSpace(_settings.PreBoardCommand))
{
return string.Empty;
}
using var client = await CreateClientAsync("CURRENT", _settings.Current);
log.AppendLine($"> CURRENT PRE-BOARD CONNECT: {DescribeClient(client, _settings.Current)}");
log.AppendLine($"> CURRENT PRE-BOARD SETUP: {_settings.PreBoardCommand}");
await SendCommandListAsync(client, _settings.PreBoardCommand);
return log.ToString();
}
public async Task<string> RunLogoutCommandAsync()
{
var log = new StringBuilder();
if (IsDisabled(_settings.Current) || string.IsNullOrWhiteSpace(_settings.LogoutCommand))
{
return string.Empty;
}
using var client = await CreateClientAsync("CURRENT", _settings.Current);
log.AppendLine($"> CURRENT LOGOUT CONNECT: {DescribeClient(client, _settings.Current)}");
log.AppendLine($"> CURRENT LOGOUT: {_settings.LogoutCommand}");
await SendCommandListAsync(client, _settings.LogoutCommand);
return log.ToString();
}
public async Task<BoardMeasurementResult> ReadAsync()
{
var log = new StringBuilder();
IScpiClient? currentClient = null;
if (_settings.SettleMilliseconds > 0)
try
{
if (!IsDisabled(_settings.Current) && !string.IsNullOrWhiteSpace(_settings.Current.SetupCommand))
{
currentClient = await CreateClientAsync("CURRENT", _settings.Current);
log.AppendLine($"> CURRENT PRE-READ CONNECT: {DescribeClient(currentClient, _settings.Current)}");
log.AppendLine($"> CURRENT PRE-READ SETUP: {_settings.Current.SetupCommand}");
await SendCommandListAsync(currentClient, _settings.Current.SetupCommand);
}
if (_settings.SettleMilliseconds > 0)
{
await Task.Delay(_settings.SettleMilliseconds);
}
var voltage = await ReadChannelAsync("VOLTAGE", _settings.Voltage, log);
var current = currentClient is null
? await ReadChannelAsync("CURRENT", _settings.Current, log)
: await ReadChannelWithClientAsync("CURRENT", _settings.Current, currentClient, log, skipSetupCommand: true);
return new BoardMeasurementResult
{
Voltage = voltage,
Current = current,
RawLog = log.ToString()
};
}
finally
{
await Task.Delay(_settings.SettleMilliseconds);
await SendCleanupCommandAsync(currentClient, _settings.Current, log);
currentClient?.Dispose();
}
}
var voltage = await ReadChannelAsync("VOLTAGE", _settings.Voltage, log);
var current = await ReadChannelAsync("CURRENT", _settings.Current, log);
private static async Task SendCleanupCommandAsync(IScpiClient? client, ScpiChannelSettings channel, StringBuilder log)
{
if (client is null || string.IsNullOrWhiteSpace(channel.CleanupCommand))
{
return;
}
return new BoardMeasurementResult
try
{
Voltage = voltage,
Current = current,
RawLog = log.ToString()
};
log.AppendLine($"> CURRENT CLEANUP: {channel.CleanupCommand}");
await SendCommandListAsync(client, channel.CleanupCommand);
}
catch (Exception ex)
{
log.AppendLine($"> CURRENT CLEANUP FAILED: {ex.Message}");
}
}
private async Task<decimal> ReadChannelAsync(string label, ScpiChannelSettings channel, StringBuilder log)
private async Task<decimal> 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);
log.AppendLine($"> {label} CONNECT: {DescribeClient(client, channel)}");
return await ReadChannelWithClientAsync(label, channel, client, log, skipSetupCommand);
}
private static async Task<decimal> 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 값을 확인하세요.");
}
if (!string.IsNullOrWhiteSpace(channel.SetupCommand))
log.AppendLine($"> {label} CONNECT: {DescribeClient(client, channel)}");
if (!skipSetupCommand && !string.IsNullOrWhiteSpace(channel.SetupCommand))
{
log.AppendLine($"> {label} SETUP: {channel.SetupCommand}");
await client.SendAsync(channel.SetupCommand);
await SendCommandListAsync(client, channel.SetupCommand);
}
log.AppendLine($"> {label} READ: {channel.ReadCommand}");
@ -63,7 +154,7 @@ public sealed class EquipmentMeasurementService
return connection switch
{
"TCP" or "LAN" => CreateTcpClientAsync(label, channel),
"SERIAL" or "COM" => Task.FromResult<IScpiClient>(new SerialScpiClient(channel.PortName, channel.BaudRate, _settings.Timeout, channel.GetIdnMatches())),
"SERIAL" or "COM" => Task.FromResult<IScpiClient>(new SerialScpiClient(channel.PortName, channel.BaudRate, _settings.Timeout, channel.GetIdnMatches(), channel.GetTerminator())),
"VISA" or "USB" => CreateVisaClientAsync(channel),
_ => throw new InvalidOperationException($"{label} 장비 연결 방식은 Visa, Serial, Tcp 중 하나로 설정하세요.")
};
@ -135,4 +226,29 @@ public sealed class EquipmentMeasurementService
return decimal.Parse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture);
}
private static async Task SendCommandListAsync(IScpiClient client, string commands)
{
foreach (var command in SplitCommands(commands))
{
await client.SendAsync(command);
await Task.Delay(100);
}
}
private static IEnumerable<string> SplitCommands(string commands)
{
return commands
.Split(';')
.Select(command => command.Trim())
.Where(command => !string.IsNullOrWhiteSpace(command));
}
private static bool IsDisabled(ScpiChannelSettings channel)
{
var connection = channel.Connection.Trim();
return string.Equals(connection, "None", StringComparison.OrdinalIgnoreCase) ||
string.Equals(connection, "Disabled", StringComparison.OrdinalIgnoreCase) ||
string.Equals(connection, "Off", StringComparison.OrdinalIgnoreCase);
}
}

15
Services/EquipmentMeasurementSettings.cs

@ -6,6 +6,9 @@ public sealed class EquipmentMeasurementSettings
{
public int Timeout { get; set; } = 5000;
public int SettleMilliseconds { get; set; } = 300;
public string PreBoardCommand { get; set; } = string.Empty;
public int PreBoardDelayMilliseconds { get; set; } = 0;
public string LogoutCommand { get; set; } = string.Empty;
public ScpiChannelSettings Voltage { get; set; } = new();
public ScpiChannelSettings Current { get; set; } = new();
@ -21,6 +24,9 @@ public sealed class EquipmentMeasurementSettings
{
Timeout = GetInt(values, "Timeout", 5000),
SettleMilliseconds = GetInt(values, "SettleMilliseconds", 300),
PreBoardCommand = GetString(values, "PreBoardCommand", string.Empty),
PreBoardDelayMilliseconds = GetInt(values, "PreBoardDelayMilliseconds", 0),
LogoutCommand = GetString(values, "LogoutCommand", string.Empty),
Voltage = LoadChannel(values, "Voltage", "MEAS:VOLT:DC?", "34460,34461,34465,34470,3446,3447"),
Current = LoadChannel(values, "Current", "MEAS:CURR? CH1", "E362")
};
@ -41,7 +47,9 @@ public sealed class EquipmentMeasurementSettings
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)
};
}
@ -70,7 +78,9 @@ public sealed class ScpiChannelSettings
public int Port { get; set; } = 5025;
public string PortName { get; set; } = string.Empty;
public int BaudRate { get; set; } = 115200;
public string Terminator { get; set; } = string.Empty;
public string SetupCommand { get; set; } = string.Empty;
public string CleanupCommand { get; set; } = string.Empty;
public string ReadCommand { get; set; } = string.Empty;
public string[] GetIdnMatches()
@ -81,4 +91,9 @@ public sealed class ScpiChannelSettings
.Where(value => !string.IsNullOrWhiteSpace(value))
.ToArray();
}
public string GetTerminator()
{
return string.IsNullOrWhiteSpace(Terminator) ? "\n" : Terminator;
}
}

74
Services/HousingAssemblyRepository.cs

@ -13,7 +13,7 @@ public sealed class HousingAssemblyRepository
_settings = settings;
}
public async Task InsertAsync(HousingAssemblyRecord record)
public async Task InsertHeaderAsync(HousingAssemblyRecord record)
{
using var connection = new SqlConnection(CreateConnectionString());
using var command = connection.CreateCommand();
@ -32,14 +32,7 @@ INSERT INTO dbo.Housing_Assembly
[Production_Date],
[Line],
[Lot_No],
[Jig_No],
[PT_Vol_1],
[PT_Current_1],
[Result],
[Spare_1],
[Spare_2],
[Spare_3],
[Spare_4]
[Jig_No]
)
VALUES
(
@ -54,14 +47,7 @@ VALUES
@Production_Date,
@Line,
@Lot_No,
@Jig_No,
@PT_Vol_1,
@PT_Current_1,
@Result,
@Spare_1,
@Spare_2,
@Spare_3,
@Spare_4
@Jig_No
);";
AddNVarChar(command, "@IC_SN", 50, record.IcSn);
@ -76,16 +62,52 @@ VALUES
AddNVarChar(command, "@Line", 5, record.Line);
AddNVarChar(command, "@Lot_No", 8, record.LotNo);
AddNVarChar(command, "@Jig_No", 10, record.JigNo);
await connection.OpenAsync();
await command.ExecuteNonQueryAsync();
}
public async Task UpdateMeasurementAsync(HousingAssemblyRecord record)
{
using var connection = new SqlConnection(CreateConnectionString());
using var command = connection.CreateCommand();
command.CommandText = @"
UPDATE dbo.Housing_Assembly
SET
[PT_Vol_1] = @PT_Vol_1,
[PT_Current_1] = @PT_Current_1,
[Result] = @Result,
[Spare_1] = @Spare_1,
[Spare_2] = @Spare_2,
[Spare_3] = @Spare_3,
[Spare_4] = @Spare_4
WHERE
[IC_SN] = @IC_SN
AND [PCB_Barcode] = @PCB_Barcode;";
AddNVarChar(command, "@IC_SN", 50, record.IcSn);
AddNVarChar(command, "@PCB_Barcode", 50, record.PcbBarcode);
AddDecimal(command, "@PT_Vol_1", record.PtVol1);
AddDecimal(command, "@PT_Current_1", record.PtCurrent1);
AddNVarChar(command, "@Result", 5, record.Result);
AddNVarChar(command, "@Spare_1", 30, record.Spare1);
AddNVarChar(command, "@Spare_2", 30, record.Spare2);
AddNVarChar(command, "@Spare_3", 30, record.Spare3);
AddNVarChar(command, "@Spare_4", 30, record.Spare4);
AddNullableNVarChar(command, "@Spare_1", 30, record.Spare1);
AddNullableNVarChar(command, "@Spare_2", 30, record.Spare2);
AddNullableNVarChar(command, "@Spare_3", 30, record.Spare3);
AddNullableNVarChar(command, "@Spare_4", 30, record.Spare4);
await connection.OpenAsync();
await command.ExecuteNonQueryAsync();
var affectedRows = await command.ExecuteNonQueryAsync();
if (affectedRows == 0)
{
throw new InvalidOperationException("DB measurement update target was not found.");
}
}
public async Task InsertAsync(HousingAssemblyRecord record)
{
await InsertHeaderAsync(record);
await UpdateMeasurementAsync(record);
}
private string CreateConnectionString()
@ -116,6 +138,14 @@ VALUES
command.Parameters.Add(new SqlParameter(name, SqlDbType.NVarChar, size) { Value = value });
}
private static void AddNullableNVarChar(SqlCommand command, string name, int size, string? value)
{
command.Parameters.Add(new SqlParameter(name, SqlDbType.NVarChar, size)
{
Value = string.IsNullOrWhiteSpace(value) ? DBNull.Value : value
});
}
private static void AddDecimal(SqlCommand command, string name, decimal value)
{
var parameter = new SqlParameter(name, SqlDbType.Decimal)

19
Services/SerialBoardClient.cs

@ -34,6 +34,21 @@ public sealed class SerialBoardClient : IDisposable
return Task.Run(() => SendCommand(command, timeoutMilliseconds, waitForEndToken));
}
public Task SendOnlyAsync(string command)
{
return Task.Run(() =>
{
if (string.IsNullOrWhiteSpace(command))
{
return;
}
Open();
_serialPort.DiscardInBuffer();
_serialPort.Write(command.Trim() + "\r\n");
});
}
private static SerialPort CreateSerialPort(string portName, BoardHardwareSettings settings)
{
return new SerialPort(portName, settings.BaudRate)
@ -42,8 +57,8 @@ public sealed class SerialBoardClient : IDisposable
NewLine = "\r\n",
ReadTimeout = settings.ReadTimeout,
WriteTimeout = settings.ReadTimeout,
DtrEnable = true,
RtsEnable = true
DtrEnable = settings.DtrEnable,
RtsEnable = settings.RtsEnable
};
}

96
Services/SerialScpiClient.cs

@ -7,13 +7,20 @@ namespace Housing.Services;
public sealed class SerialScpiClient : IScpiClient
{
private readonly SerialPort _serialPort;
private readonly string _commandTerminator;
public string PortName => _serialPort.PortName;
public SerialScpiClient(string portName, int baudRate, int timeoutMilliseconds, IEnumerable<string>? idnMatches = null)
public SerialScpiClient(
string portName,
int baudRate,
int timeoutMilliseconds,
IEnumerable<string>? idnMatches = null,
string commandTerminator = "\n")
{
var resolvedPortName = ResolvePortName(portName, baudRate, timeoutMilliseconds, idnMatches);
_commandTerminator = NormalizeTerminator(commandTerminator);
_serialPort = CreateSerialPort(resolvedPortName, baudRate, timeoutMilliseconds);
_serialPort.Open();
_serialPort.DiscardInBuffer();
@ -22,7 +29,7 @@ public sealed class SerialScpiClient : IScpiClient
public Task SendAsync(string command)
{
return Task.Run(() => _serialPort.Write(command.Trim() + "\n"));
return Task.Run(() => _serialPort.Write(command.Trim() + _commandTerminator));
}
public Task<string> QueryAsync(string command)
@ -30,11 +37,51 @@ public sealed class SerialScpiClient : IScpiClient
return Task.Run(() =>
{
_serialPort.DiscardInBuffer();
_serialPort.Write(command.Trim() + "\n");
return _serialPort.ReadLine().Trim();
_serialPort.Write(command.Trim() + _commandTerminator);
return ReadResponse(command);
});
}
private string ReadResponse(string command)
{
var response = new List<byte>();
while (true)
{
try
{
var value = _serialPort.ReadByte();
if (value < 0)
{
break;
}
if (value is '\r' or '\n')
{
if (response.Count > 0)
{
break;
}
continue;
}
response.Add((byte)value);
}
catch (TimeoutException)
{
if (response.Count > 0)
{
break;
}
throw new TimeoutException($"{_serialPort.PortName} did not respond to {command}.");
}
}
return Encoding.ASCII.GetString(response.ToArray()).Trim();
}
private static SerialPort CreateSerialPort(string portName, int baudRate, int timeoutMilliseconds)
{
return new SerialPort(portName, baudRate)
@ -62,11 +109,11 @@ public sealed class SerialScpiClient : IScpiClient
if (portNames.Length == 0)
{
throw new InvalidOperationException("사용 가능한 장비 COM 포트를 찾을 수 없습니다.");
throw new InvalidOperationException("No available SCPI COM port.");
}
var tokens = NormalizeIdnMatches(idnMatches);
if (tokens.Length == 0 && portNames.Length == 1)
if (portNames.Length == 1)
{
return portNames[0];
}
@ -79,7 +126,7 @@ public sealed class SerialScpiClient : IScpiClient
}
}
throw new InvalidOperationException($"SCPI 장비 자동 포트 검색 실패. 확인한 포트: {string.Join(", ", portNames)}");
throw new InvalidOperationException($"SCPI device auto port search failed. Checked ports: {string.Join(", ", portNames)}");
}
private static bool IsAutoPort(string portName)
@ -113,9 +160,26 @@ public sealed class SerialScpiClient : IScpiClient
probe.Open();
probe.DiscardInBuffer();
probe.DiscardOutBuffer();
probe.Write("*IDN?\n");
var idn = probe.ReadLine().Trim();
probe.Write("*IDN?\r\n");
var response = new List<byte>();
while (true)
{
var value = probe.ReadByte();
if (value < 0 || value is '\r' or '\n')
{
if (response.Count > 0)
{
break;
}
continue;
}
response.Add((byte)value);
}
var idn = Encoding.ASCII.GetString(response.ToArray()).Trim();
return idnMatches.Length == 0 ||
idnMatches.Any(token => idn.Contains(token, StringComparison.OrdinalIgnoreCase));
}
@ -125,6 +189,20 @@ public sealed class SerialScpiClient : IScpiClient
}
}
private static string NormalizeTerminator(string value)
{
return value.Trim().ToUpperInvariant() switch
{
"CRLF" => "\r\n",
"CR" => "\r",
"LF" => "\n",
"\\R\\N" => "\r\n",
"\\R" => "\r",
"\\N" => "\n",
_ => string.IsNullOrEmpty(value) ? "\n" : value
};
}
public void Dispose()
{
_serialPort.Dispose();

Loading…
Cancel
Save