✨ feat(*): 更新 PLC 相关配置与结果代码类型
- 修改 appConfig.json 中 PLC 和扫描仪的 IP 地址及字节序配置 - 将多个地方的 ResultCode 类型从 short 更新为 ushort,以确保与 PLC 通信一致 - 更新相关服务和模型以适应新的 ResultCode 类型 - 调整单元测试以验证新配置和类型的正确性
This commit is contained in:
@@ -5,7 +5,7 @@ using AxiOmron.PcbCheck.Models;
|
||||
namespace AxiOmron.PcbCheck.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// 将 <see cref="WorkflowResultCode"/>(以 <see cref="short"/> 存储)映射为简短 Tag 文本。
|
||||
/// 将 <see cref="WorkflowResultCode"/>(以 <see cref="ushort"/> 存储)映射为简短 Tag 文本。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 映射规则:Passed → "OK";Processing → "处理中";None → "-";其余视为 "NG"。
|
||||
@@ -22,19 +22,20 @@ public sealed class ResultCodeToTagTextConverter : IValueConverter
|
||||
/// <returns>简短标签文本。</returns>
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
short code = value switch
|
||||
ushort code = value switch
|
||||
{
|
||||
short s => s,
|
||||
int i => (short)i,
|
||||
WorkflowResultCode rc => (short)rc,
|
||||
ushort s => s,
|
||||
short s when s >= 0 => (ushort)s,
|
||||
int i when i >= 0 && i <= ushort.MaxValue => (ushort)i,
|
||||
WorkflowResultCode rc => (ushort)rc,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
return code switch
|
||||
{
|
||||
(short)WorkflowResultCode.Passed => "OK",
|
||||
(short)WorkflowResultCode.Processing => "处理中",
|
||||
(short)WorkflowResultCode.None => "-",
|
||||
(ushort)WorkflowResultCode.Passed => "OK",
|
||||
(ushort)WorkflowResultCode.Processing => "处理中",
|
||||
(ushort)WorkflowResultCode.None => "-",
|
||||
_ => "NG"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class DesignTimeAppStateStore : IAppStateStore
|
||||
CurrentBarcode = "PCB240417000128",
|
||||
ResultDescription = "已扫码,等待 SFTP 文件确认",
|
||||
FaultMessage = string.Empty,
|
||||
ResultCode = (short)WorkflowResultCode.Processing,
|
||||
ResultCode = (ushort)WorkflowResultCode.Processing,
|
||||
LastTriggeredAt = now.AddSeconds(-18),
|
||||
LastCompletedAt = now.AddMinutes(-2),
|
||||
IsBusy = true,
|
||||
@@ -44,7 +44,7 @@ public sealed class DesignTimeAppStateStore : IAppStateStore
|
||||
_snapshot.PlcMonitorItems.Add(new PlcMonitorItem { GroupName = "Inputs", Name = "PlcAckRelease", Address = "10003", CurrentValue = "False", LastUpdatedAt = now.AddSeconds(-1) });
|
||||
_snapshot.PlcMonitorItems.Add(new PlcMonitorItem { GroupName = "Outputs", Name = "PcBusy", Address = "51", CurrentValue = "True", LastUpdatedAt = now.AddSeconds(-1) });
|
||||
_snapshot.PlcMonitorItems.Add(new PlcMonitorItem { GroupName = "Outputs", Name = "ReleasePermit", Address = "52", CurrentValue = "False", LastUpdatedAt = now.AddSeconds(-1) });
|
||||
_snapshot.PlcMonitorItems.Add(new PlcMonitorItem { GroupName = "Registers", Name = "ResultCode", Address = "40001", CurrentValue = ((short)WorkflowResultCode.Processing).ToString(), LastUpdatedAt = now.AddSeconds(-1) });
|
||||
_snapshot.PlcMonitorItems.Add(new PlcMonitorItem { GroupName = "Registers", Name = "ResultCode", Address = "40001", CurrentValue = ((ushort)WorkflowResultCode.Processing).ToString(), LastUpdatedAt = now.AddSeconds(-1) });
|
||||
|
||||
_logs = new List<UiLogEntry>
|
||||
{
|
||||
@@ -64,7 +64,7 @@ public sealed class DesignTimeAppStateStore : IAppStateStore
|
||||
Barcode = "PCB240417000125",
|
||||
ScanTryCount = 1,
|
||||
SftpTryCount = 1,
|
||||
ResultCode = (short)WorkflowResultCode.Passed,
|
||||
ResultCode = (ushort)WorkflowResultCode.Passed,
|
||||
ResultDescription = "OK 放行",
|
||||
ReleaseSent = true,
|
||||
ExceptionSummary = string.Empty
|
||||
@@ -76,7 +76,7 @@ public sealed class DesignTimeAppStateStore : IAppStateStore
|
||||
Barcode = "PCB240417000126",
|
||||
ScanTryCount = 3,
|
||||
SftpTryCount = 0,
|
||||
ResultCode = (short)WorkflowResultCode.NgReleased,
|
||||
ResultCode = (ushort)WorkflowResultCode.NgReleased,
|
||||
ResultDescription = "扫码失败后放行",
|
||||
ReleaseSent = true,
|
||||
ExceptionSummary = "扫码连续失败三次"
|
||||
@@ -88,7 +88,7 @@ public sealed class DesignTimeAppStateStore : IAppStateStore
|
||||
Barcode = "PCB240417000127",
|
||||
ScanTryCount = 1,
|
||||
SftpTryCount = 3,
|
||||
ResultCode = (short)WorkflowResultCode.NgReleased,
|
||||
ResultCode = (ushort)WorkflowResultCode.NgReleased,
|
||||
ResultDescription = "处理结果 NG,随后放行",
|
||||
ReleaseSent = true,
|
||||
ExceptionSummary = "SFTP 文件查询超时"
|
||||
@@ -97,8 +97,8 @@ public sealed class DesignTimeAppStateStore : IAppStateStore
|
||||
|
||||
_snapshot.RecordStatisticDate = DateOnly.FromDateTime(now.LocalDateTime.Date);
|
||||
_snapshot.TodayProcessCount = _records.Count;
|
||||
_snapshot.TodayOkCount = _records.Count(record => record.ResultCode == (short)WorkflowResultCode.Passed);
|
||||
_snapshot.TodayNgCount = _records.Count(record => record.ResultCode != (short)WorkflowResultCode.Passed);
|
||||
_snapshot.TodayOkCount = _records.Count(record => record.ResultCode == (ushort)WorkflowResultCode.Passed);
|
||||
_snapshot.TodayNgCount = _records.Count(record => record.ResultCode != (ushort)WorkflowResultCode.Passed);
|
||||
_snapshot.RecordsLastUpdatedAt = now;
|
||||
foreach (BoardProcessRecord record in _records)
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ public sealed class RuntimeSnapshot
|
||||
/// <summary>
|
||||
/// 获取或设置结果代码。
|
||||
/// </summary>
|
||||
public short ResultCode { get; set; }
|
||||
public ushort ResultCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置上次触发时间。
|
||||
|
||||
@@ -69,7 +69,7 @@ public enum WorkflowState
|
||||
/// <summary>
|
||||
/// 表示最终结果代码定义。
|
||||
/// </summary>
|
||||
public enum WorkflowResultCode : short
|
||||
public enum WorkflowResultCode : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// 无结果。
|
||||
@@ -182,7 +182,7 @@ public sealed class PlcProcessState
|
||||
/// <summary>
|
||||
/// 获取或设置结果代码寄存器值。
|
||||
/// </summary>
|
||||
public short ResultCode { get; set; }
|
||||
public ushort ResultCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建当前状态对象的浅拷贝。
|
||||
@@ -496,7 +496,7 @@ public sealed class BoardProcessRecord
|
||||
/// <summary>
|
||||
/// 获取或设置结果代码。
|
||||
/// </summary>
|
||||
public short ResultCode { get; set; }
|
||||
public ushort ResultCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置结果描述。
|
||||
|
||||
@@ -130,9 +130,9 @@ public sealed class PlcOptions
|
||||
public byte SourceNodeAddress { get; set; } = 25;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置 PLC 寄存器字节序,默认使用 BADC 以匹配现场 D 区 Int16 高低字节顺序。
|
||||
/// 获取或设置 PLC 寄存器字节序,默认使用 ABCD 以避免 UInt16 结果代码发生高低字节交换。
|
||||
/// </summary>
|
||||
public string? EndianFormat { get; set; } = "BADC";
|
||||
public string? EndianFormat { get; set; } = "ABCD";
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置轮询周期,单位为毫秒。
|
||||
|
||||
@@ -247,10 +247,10 @@ public sealed class CsvBoardRecordRepository : IBoardRecordRepository
|
||||
{
|
||||
StatisticDate = date,
|
||||
TotalCount = records.Count,
|
||||
OkCount = records.Count(record => record.ResultCode == (short)WorkflowResultCode.Passed),
|
||||
NgCount = records.Count(record => record.ResultCode != (short)WorkflowResultCode.Passed
|
||||
&& record.ResultCode != (short)WorkflowResultCode.Processing
|
||||
&& record.ResultCode != (short)WorkflowResultCode.None),
|
||||
OkCount = records.Count(record => record.ResultCode == (ushort)WorkflowResultCode.Passed),
|
||||
NgCount = records.Count(record => record.ResultCode != (ushort)WorkflowResultCode.Passed
|
||||
&& record.ResultCode != (ushort)WorkflowResultCode.Processing
|
||||
&& record.ResultCode != (ushort)WorkflowResultCode.None),
|
||||
LastUpdatedAt = _timeProvider.GetLocalNow()
|
||||
};
|
||||
|
||||
@@ -400,7 +400,7 @@ public sealed class CsvBoardRecordRepository : IBoardRecordRepository
|
||||
|| !DateTimeOffset.TryParse(fields[1], out DateTimeOffset completedAt)
|
||||
|| !int.TryParse(fields[3], out int scanTryCount)
|
||||
|| !int.TryParse(fields[4], out int sftpTryCount)
|
||||
|| !short.TryParse(fields[5], out short resultCode)
|
||||
|| !ushort.TryParse(fields[5], out ushort resultCode)
|
||||
|| !bool.TryParse(fields[7], out bool releaseSent)
|
||||
|| !bool.TryParse(fields[8], out bool alarmRaised))
|
||||
{
|
||||
|
||||
@@ -89,7 +89,7 @@ public sealed class OmronFinsPlcService : IPlcService, IDisposable
|
||||
{
|
||||
PcBusy = ReadBoolean(ToDataMemoryAddress(_options.Outputs.PcBusy)),
|
||||
ReleasePermit = ReadBoolean(ToDataMemoryAddress(_options.Outputs.ReleasePermit)),
|
||||
ResultCode = ReadInt16(ToDataMemoryAddress(_options.Registers.ResultCode))
|
||||
ResultCode = ReadUInt16(ToDataMemoryAddress(_options.Registers.ResultCode))
|
||||
},
|
||||
CapturedAt = capturedAt
|
||||
};
|
||||
@@ -276,15 +276,15 @@ public sealed class OmronFinsPlcService : IPlcService, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取单个 D 区有符号 16 位整数。
|
||||
/// 读取单个 D 区无符号 16 位整数。
|
||||
/// </summary>
|
||||
/// <param name="address">FINS 地址。</param>
|
||||
/// <returns>读取到的寄存器值。</returns>
|
||||
private short ReadInt16(string address)
|
||||
private ushort ReadUInt16(string address)
|
||||
{
|
||||
var client = _client ?? throw new InvalidOperationException("PLC 客户端尚未初始化。");
|
||||
var result = client.ReadInt16(address);
|
||||
EnsureSuccess(result.IsSucceed, result.Err, $"读取 FINS Int16 失败,地址={address}");
|
||||
var result = client.ReadUInt16(address);
|
||||
EnsureSuccess(result.IsSucceed, result.Err, $"读取 FINS UInt16 失败,地址={address}");
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
@@ -295,10 +295,10 @@ public sealed class OmronFinsPlcService : IPlcService, IDisposable
|
||||
private void WriteChangedState(PlcProcessState state)
|
||||
{
|
||||
var previous = _lastWrittenState;
|
||||
short? previousResultCode = previous is null ? null : previous.ResultCode;
|
||||
ushort? previousResultCode = previous is null ? null : previous.ResultCode;
|
||||
WriteBooleanIfChanged(previous?.PcBusy, state.PcBusy, ToDataMemoryAddress(_options.Outputs.PcBusy));
|
||||
WriteBooleanIfChanged(previous?.ReleasePermit, state.ReleasePermit, ToDataMemoryAddress(_options.Outputs.ReleasePermit));
|
||||
WriteInt16IfChanged(previousResultCode, state.ResultCode, ToDataMemoryAddress(_options.Registers.ResultCode));
|
||||
WriteUInt16IfChanged(previousResultCode, state.ResultCode, ToDataMemoryAddress(_options.Registers.ResultCode));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -320,12 +320,12 @@ public sealed class OmronFinsPlcService : IPlcService, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅当值发生变化时写入单个 D 区有符号 16 位整数。
|
||||
/// 仅当值发生变化时写入单个 D 区无符号 16 位整数。
|
||||
/// </summary>
|
||||
/// <param name="previous">上一值。</param>
|
||||
/// <param name="current">当前值。</param>
|
||||
/// <param name="address">FINS 地址。</param>
|
||||
private void WriteInt16IfChanged(short? previous, short current, string address)
|
||||
private void WriteUInt16IfChanged(ushort? previous, ushort current, string address)
|
||||
{
|
||||
if (previous.HasValue && previous.Value == current)
|
||||
{
|
||||
@@ -334,7 +334,7 @@ public sealed class OmronFinsPlcService : IPlcService, IDisposable
|
||||
|
||||
var client = _client ?? throw new InvalidOperationException("PLC 客户端尚未初始化。");
|
||||
var result = client.Write(address, current);
|
||||
EnsureSuccess(result.IsSucceed, result.Err, $"写入 FINS Int16 失败,地址={address},值={current}");
|
||||
EnsureSuccess(result.IsSucceed, result.Err, $"写入 FINS UInt16 失败,地址={address},值={current}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -848,7 +848,7 @@ public sealed class WorkflowHostedService : BackgroundService, IWorkflowControlS
|
||||
Barcode = _currentBarcode,
|
||||
ScanTryCount = _scanTryCount,
|
||||
SftpTryCount = _sftpTryCount,
|
||||
ResultCode = (short)_resultCode,
|
||||
ResultCode = (ushort)_resultCode,
|
||||
ResultDescription = GetResultDescription(_resultCode),
|
||||
ReleaseSent = _releaseSent,
|
||||
AlarmRaised = false,
|
||||
@@ -1082,7 +1082,7 @@ public sealed class WorkflowHostedService : BackgroundService, IWorkflowControlS
|
||||
lock (_stateSyncRoot)
|
||||
{
|
||||
updateAction(_plcState);
|
||||
_plcState.ResultCode = (short)_resultCode;
|
||||
_plcState.ResultCode = (ushort)_resultCode;
|
||||
snapshot = _plcState.Clone();
|
||||
}
|
||||
|
||||
@@ -1103,7 +1103,7 @@ public sealed class WorkflowHostedService : BackgroundService, IWorkflowControlS
|
||||
{
|
||||
DateTimeOffset? startedAt;
|
||||
string barcode;
|
||||
short resultCode;
|
||||
ushort resultCode;
|
||||
bool isBusy;
|
||||
string faultMessage;
|
||||
|
||||
@@ -1111,7 +1111,7 @@ public sealed class WorkflowHostedService : BackgroundService, IWorkflowControlS
|
||||
{
|
||||
startedAt = _currentBoardStartedAt;
|
||||
barcode = _currentBarcode;
|
||||
resultCode = (short)_resultCode;
|
||||
resultCode = (ushort)_resultCode;
|
||||
isBusy = _plcState.PcBusy;
|
||||
faultMessage = _faultLatched ? description : string.Empty;
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
/// 获取或设置结果代码。
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private short _resultCode;
|
||||
private ushort _resultCode;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置最近触发时间。
|
||||
@@ -557,6 +557,7 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
await AppendLogOnUiAsync(entry).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理管理员解锁倒计时,超时后自动恢复锁定。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件源。</param>
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
{
|
||||
"Plc": {
|
||||
"Host": "127.0.0.1",
|
||||
"Port": 9600,
|
||||
"UnitAddress": 0,
|
||||
"SourceNodeAddress": 25,
|
||||
"EndianFormat": "BADC",
|
||||
"PollIntervalMs": 200,
|
||||
"ConnectTimeoutMs": 3000,
|
||||
"ReleasePulseMs": 500,
|
||||
"ReleaseAckTimeoutMs": 2000,
|
||||
"Inputs": {
|
||||
"PcbArrived": 101,
|
||||
"PlcReset": 102,
|
||||
"PlcAckRelease": 103
|
||||
},
|
||||
"Outputs": {
|
||||
"PcBusy": 51,
|
||||
"ReleasePermit": 52
|
||||
},
|
||||
"Registers": {
|
||||
"ResultCode": 401
|
||||
}
|
||||
},
|
||||
"Scanner": {
|
||||
"Scanner1": {
|
||||
"Name": "扫码枪1",
|
||||
"Host": "127.0.0.1",
|
||||
"Port": 2001
|
||||
},
|
||||
"Scanner2": {
|
||||
"Name": "扫码枪2",
|
||||
"Host": "127.0.0.1",
|
||||
"Port": 2001
|
||||
},
|
||||
"ConnectTimeoutMs": 3000,
|
||||
"ReadTimeoutMs": 3000,
|
||||
"TriggerCommand": "T",
|
||||
"ResponseTerminator": "\\r",
|
||||
"TriggerInParallel": true
|
||||
},
|
||||
"FileCheck": {
|
||||
"Provider": "Local",
|
||||
"FileNamePattern": "${barcode}.txt",
|
||||
"RetryIntervalSeconds": 2,
|
||||
"MaxRetryCount": 3,
|
||||
"Local": {
|
||||
"RootPath": "D:\\PcbCheck\\Files"
|
||||
}
|
||||
},
|
||||
"Sftp": {
|
||||
"Host": "127.0.0.1",
|
||||
"Port": 22,
|
||||
"Username": "uaesadmin",
|
||||
"Password": "uaes,123",
|
||||
"PrivateKeyPath": "",
|
||||
"PrivateKeyPassphrase": "",
|
||||
"RootPath": "/home/uaesadmin/",
|
||||
"FileNamePattern": "${barcode}.txt",
|
||||
"RetryIntervalSeconds": 2,
|
||||
"MaxRetryCount": 3,
|
||||
"ConnectTimeoutMs": 3000
|
||||
},
|
||||
"Andon": {
|
||||
"Enable": false,
|
||||
"Url": "https://shp.prod.aliyun.mit.uaes.com/api/mit/MAE/Station/Status/alarm",
|
||||
"Method": "POST",
|
||||
"TimeoutMs": 3000,
|
||||
"AndonTheme": "SS7_Auto_Andon",
|
||||
"Eid": "PR1965269806334783488",
|
||||
"FromSign": "IOT",
|
||||
"ServiceId": "4dccd822-86b2-47dd-89bd-ca472ba0d14d",
|
||||
"EnableScanFailAlarm": true,
|
||||
"EnableFileNotFoundAlarm": false,
|
||||
"Headers": {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
},
|
||||
"Workflow": {
|
||||
"RequireManualResetAfterFault": true,
|
||||
"MaxUiLogEntries": 200,
|
||||
"MaxBoardRecords": 100
|
||||
},
|
||||
"RecordPersistence": {
|
||||
"DirectoryPath": "Data\\Records",
|
||||
"DayChangeCheckIntervalSeconds": 30
|
||||
},
|
||||
"Security": {
|
||||
"AdminPassword": "AxiOmron@123",
|
||||
"AdminSessionTimeoutMinutes": 15
|
||||
}
|
||||
{
|
||||
"Plc": {
|
||||
"Host": "192.168.2.100",
|
||||
"Port": 9600,
|
||||
"UnitAddress": 0,
|
||||
"SourceNodeAddress": 0,
|
||||
"EndianFormat": "ABCD",
|
||||
"PollIntervalMs": 200,
|
||||
"ConnectTimeoutMs": 3000,
|
||||
"ReleasePulseMs": 500,
|
||||
"ReleaseAckTimeoutMs": 2000,
|
||||
"Inputs": {
|
||||
"PcbArrived": 101,
|
||||
"PlcReset": 102,
|
||||
"PlcAckRelease": 103
|
||||
},
|
||||
"Outputs": {
|
||||
"PcBusy": 51,
|
||||
"ReleasePermit": 52
|
||||
},
|
||||
"Registers": {
|
||||
"ResultCode": 401
|
||||
}
|
||||
},
|
||||
"Scanner": {
|
||||
"Scanner1": {
|
||||
"Name": "\u626B\u7801\u67AA1",
|
||||
"Host": "192.168.2.13",
|
||||
"Port": 2001
|
||||
},
|
||||
"Scanner2": {
|
||||
"Name": "\u626B\u7801\u67AA2",
|
||||
"Host": "192.168.2.14",
|
||||
"Port": 2001
|
||||
},
|
||||
"ConnectTimeoutMs": 3000,
|
||||
"TriggerCommand": "T",
|
||||
"ResponseTerminator": "",
|
||||
"TriggerInParallel": true,
|
||||
"ReadTimeoutMs": 5000
|
||||
},
|
||||
"FileCheck": {
|
||||
"Provider": "Local",
|
||||
"FileNamePattern": "${barcode}.txt",
|
||||
"RetryIntervalSeconds": 2,
|
||||
"MaxRetryCount": 3,
|
||||
"Local": {
|
||||
"RootPath": "D:\\test"
|
||||
}
|
||||
},
|
||||
"Sftp": {
|
||||
"Host": "127.0.0.1",
|
||||
"Port": 22,
|
||||
"Username": "uaesadmin",
|
||||
"Password": "uaes,123",
|
||||
"PrivateKeyPath": "",
|
||||
"PrivateKeyPassphrase": "",
|
||||
"RootPath": "/home/uaesadmin/",
|
||||
"FileNamePattern": "${barcode}.txt",
|
||||
"RetryIntervalSeconds": 2,
|
||||
"MaxRetryCount": 3,
|
||||
"ConnectTimeoutMs": 3000
|
||||
},
|
||||
"Andon": {
|
||||
"Enable": false,
|
||||
"Url": "https://shp.prod.aliyun.mit.uaes.com/api/mit/MAE/Station/Status/alarm",
|
||||
"Method": "POST",
|
||||
"TimeoutMs": 3000,
|
||||
"AndonTheme": "SS7_Auto_Andon",
|
||||
"Eid": "PR1965269806334783488",
|
||||
"FromSign": "IOT",
|
||||
"ServiceId": "4dccd822-86b2-47dd-89bd-ca472ba0d14d",
|
||||
"EnableScanFailAlarm": false,
|
||||
"EnableFileNotFoundAlarm": false,
|
||||
"Headers": {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
},
|
||||
"Workflow": {
|
||||
"RequireManualResetAfterFault": true,
|
||||
"MaxUiLogEntries": 200,
|
||||
"MaxBoardRecords": 100
|
||||
},
|
||||
"RecordPersistence": {
|
||||
"DirectoryPath": "Data\\Records",
|
||||
"DayChangeCheckIntervalSeconds": 30
|
||||
},
|
||||
"Security": {
|
||||
"AdminPassword": "AxiOmron@123",
|
||||
"AdminSessionTimeoutMinutes": 15
|
||||
}
|
||||
}
|
||||
@@ -37,10 +37,10 @@ public sealed class CsvBoardRecordRepositoryTests : IDisposable
|
||||
timeProvider);
|
||||
|
||||
await repository.AppendAsync(
|
||||
CreateRecord("PCB-001", new DateTimeOffset(2026, 4, 19, 8, 0, 0, TimeSpan.FromHours(8)), (short)WorkflowResultCode.Passed),
|
||||
CreateRecord("PCB-001", new DateTimeOffset(2026, 4, 19, 8, 0, 0, TimeSpan.FromHours(8)), (ushort)WorkflowResultCode.Passed),
|
||||
CancellationToken.None);
|
||||
await repository.AppendAsync(
|
||||
CreateRecord("PCB-002", new DateTimeOffset(2026, 4, 19, 8, 5, 0, TimeSpan.FromHours(8)), (short)WorkflowResultCode.NgReleased),
|
||||
CreateRecord("PCB-002", new DateTimeOffset(2026, 4, 19, 8, 5, 0, TimeSpan.FromHours(8)), (ushort)WorkflowResultCode.NgReleased),
|
||||
CancellationToken.None);
|
||||
|
||||
BoardRecordLoadResult result = await repository.LoadCurrentDayAsync(CancellationToken.None);
|
||||
@@ -100,7 +100,7 @@ public sealed class CsvBoardRecordRepositoryTests : IDisposable
|
||||
timeProvider);
|
||||
|
||||
await repository.AppendAsync(
|
||||
CreateRecord("PCB-OLD", new DateTimeOffset(2026, 4, 19, 23, 59, 55, TimeSpan.FromHours(8)), (short)WorkflowResultCode.Passed),
|
||||
CreateRecord("PCB-OLD", new DateTimeOffset(2026, 4, 19, 23, 59, 55, TimeSpan.FromHours(8)), (ushort)WorkflowResultCode.Passed),
|
||||
CancellationToken.None);
|
||||
|
||||
timeProvider.SetLocalNow(new DateTimeOffset(2026, 4, 20, 0, 0, 10, TimeSpan.FromHours(8)));
|
||||
@@ -131,7 +131,7 @@ public sealed class CsvBoardRecordRepositoryTests : IDisposable
|
||||
/// <param name="completedAt">完成时间。</param>
|
||||
/// <param name="resultCode">结果代码。</param>
|
||||
/// <returns>测试记录。</returns>
|
||||
private static BoardProcessRecord CreateRecord(string barcode, DateTimeOffset completedAt, short resultCode)
|
||||
private static BoardProcessRecord CreateRecord(string barcode, DateTimeOffset completedAt, ushort resultCode)
|
||||
{
|
||||
return new BoardProcessRecord
|
||||
{
|
||||
@@ -141,7 +141,7 @@ public sealed class CsvBoardRecordRepositoryTests : IDisposable
|
||||
ScanTryCount = 1,
|
||||
SftpTryCount = 1,
|
||||
ResultCode = resultCode,
|
||||
ResultDescription = resultCode == (short)WorkflowResultCode.Passed ? "OK" : "NG",
|
||||
ResultDescription = resultCode == (ushort)WorkflowResultCode.Passed ? "OK" : "NG",
|
||||
ReleaseSent = true,
|
||||
AlarmRaised = false,
|
||||
ExceptionSummary = string.Empty
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed class MainWindowViewModelTests
|
||||
Barcode = $"PCB-{index:D3}",
|
||||
ScanTryCount = 1,
|
||||
SftpTryCount = 1,
|
||||
ResultCode = index % 2 == 0 ? (short)WorkflowResultCode.Passed : (short)WorkflowResultCode.NgReleased,
|
||||
ResultCode = index % 2 == 0 ? (ushort)WorkflowResultCode.Passed : (ushort)WorkflowResultCode.NgReleased,
|
||||
ResultDescription = index % 2 == 0 ? "OK" : "NG",
|
||||
ReleaseSent = true,
|
||||
AlarmRaised = false
|
||||
|
||||
@@ -11,16 +11,16 @@ namespace AxiOmron.PcbCheck.Tests;
|
||||
public sealed class OmronFinsPlcServiceTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认字节序应使用 BADC,以匹配现场 D 区 Int16 的高低字节顺序。
|
||||
/// 默认字节序应使用 ABCD,避免 UInt16 结果代码发生高低字节交换。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ResolveEndianFormat_ShouldUseBadc_WhenConfigurationIsDefault()
|
||||
public void ResolveEndianFormat_ShouldUseAbcd_WhenConfigurationIsDefault()
|
||||
{
|
||||
var options = new PlcOptions();
|
||||
|
||||
EndianFormat result = OmronFinsPlcService.ResolveEndianFormat(options);
|
||||
|
||||
Assert.Equal(EndianFormat.BADC, result);
|
||||
Assert.Equal(EndianFormat.ABCD, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,29 +5,29 @@ using Xunit;
|
||||
namespace AxiOmron.PcbCheck.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// 验证结果代码在模型与界面层统一使用 16 位有符号整数。
|
||||
/// 验证结果代码在模型与界面层统一使用 16 位无符号整数。
|
||||
/// </summary>
|
||||
public sealed class ResultCodeTypeTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 结果代码枚举应以 Int16 作为底层类型,匹配 PLC 的 ResultCode 定义。
|
||||
/// 结果代码枚举应以 UInt16 作为底层类型,匹配 PLC 的 ResultCode 定义。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void WorkflowResultCode_ShouldUseInt16UnderlyingType()
|
||||
public void WorkflowResultCode_ShouldUseUInt16UnderlyingType()
|
||||
{
|
||||
Assert.Equal(typeof(short), Enum.GetUnderlyingType(typeof(WorkflowResultCode)));
|
||||
Assert.Equal(typeof(ushort), Enum.GetUnderlyingType(typeof(WorkflowResultCode)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结果代码属性应统一使用 Int16,避免经过界面或快照时被误建模为 UInt16 或 Int32。
|
||||
/// 结果代码属性应统一使用 UInt16,避免经过界面或快照时被误建模为 Int16 或 Int32。
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(typeof(RuntimeSnapshot))]
|
||||
[InlineData(typeof(PlcProcessState))]
|
||||
[InlineData(typeof(BoardProcessRecord))]
|
||||
[InlineData(typeof(MainWindowViewModel))]
|
||||
public void ResultCodeProperties_ShouldUseInt16(Type ownerType)
|
||||
public void ResultCodeProperties_ShouldUseUInt16(Type ownerType)
|
||||
{
|
||||
Assert.Equal(typeof(short), ownerType.GetProperty("ResultCode")?.PropertyType);
|
||||
Assert.Equal(typeof(ushort), ownerType.GetProperty("ResultCode")?.PropertyType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,12 +148,12 @@ public sealed class WorkflowHostedServiceTests
|
||||
|
||||
Assert.True(scannerService.TriggerCount > 0);
|
||||
Assert.Contains(plcService.WrittenStates, state => state.ReleasePermit);
|
||||
Assert.Contains(plcService.WrittenStates, state => state.ResultCode == (short)WorkflowResultCode.Passed);
|
||||
Assert.Contains(plcService.WrittenStates, state => state.ResultCode == (ushort)WorkflowResultCode.Passed);
|
||||
|
||||
RuntimeSnapshot snapshot = stateStore.GetSnapshot();
|
||||
Assert.Contains(snapshot.PlcMonitorItems, item => item.Name == "PcbArrived" && item.CurrentValue == "False");
|
||||
Assert.Contains(snapshot.PlcMonitorItems, item => item.Name == "PcBusy" && item.CurrentValue == "False");
|
||||
Assert.Contains(snapshot.PlcMonitorItems, item => item.Name == "ResultCode" && item.CurrentValue == ((short)WorkflowResultCode.Passed).ToString());
|
||||
Assert.Contains(snapshot.PlcMonitorItems, item => item.Name == "ResultCode" && item.CurrentValue == ((ushort)WorkflowResultCode.Passed).ToString());
|
||||
Assert.All(snapshot.PlcMonitorItems, item => Assert.NotEqual(default, item.LastUpdatedAt));
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ public sealed class WorkflowHostedServiceTests
|
||||
|
||||
Assert.Equal(1, scannerService.TriggerCount);
|
||||
Assert.Equal("PCB-MERGED", fileCheckService.LastBarcode);
|
||||
Assert.Contains(plcService.WrittenStates, state => state.ResultCode == (short)WorkflowResultCode.Passed);
|
||||
Assert.Contains(plcService.WrittenStates, state => state.ResultCode == (ushort)WorkflowResultCode.Passed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -304,7 +304,7 @@ public sealed class WorkflowHostedServiceTests
|
||||
Barcode = "PCB-RESTORED",
|
||||
ScanTryCount = 1,
|
||||
SftpTryCount = 1,
|
||||
ResultCode = (short)WorkflowResultCode.Passed,
|
||||
ResultCode = (ushort)WorkflowResultCode.Passed,
|
||||
ResultDescription = "OK",
|
||||
ReleaseSent = true
|
||||
}
|
||||
@@ -409,7 +409,7 @@ public sealed class WorkflowHostedServiceTests
|
||||
|
||||
RuntimeSnapshot snapshot = stateStore.GetSnapshot();
|
||||
Assert.NotEqual(WorkflowState.Faulted, snapshot.WorkflowState);
|
||||
Assert.Equal((short)WorkflowResultCode.Passed, snapshot.ResultCode);
|
||||
Assert.Equal((ushort)WorkflowResultCode.Passed, snapshot.ResultCode);
|
||||
}
|
||||
|
||||
private sealed class FakePlcService : IPlcService
|
||||
|
||||
Reference in New Issue
Block a user