✨ feat(*): 更新 PLC 相关配置与结果代码类型

- 修改 appConfig.json 中 PLC 和扫描仪的 IP 地址及字节序配置
- 将多个地方的 ResultCode 类型从 short 更新为 ushort,以确保与 PLC 通信一致
- 更新相关服务和模型以适应新的 ResultCode 类型
- 调整单元测试以验证新配置和类型的正确性
This commit is contained in:
2026-06-02 17:59:04 +08:00
parent 27e2c9d5d0
commit 17839a9e76
15 changed files with 155 additions and 153 deletions

View File

@@ -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"
};
}

View File

@@ -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)
{

View File

@@ -63,7 +63,7 @@ public sealed class RuntimeSnapshot
/// <summary>
/// 获取或设置结果代码。
/// </summary>
public short ResultCode { get; set; }
public ushort ResultCode { get; set; }
/// <summary>
/// 获取或设置上次触发时间。

View File

@@ -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>
/// 获取或设置结果描述。

View File

@@ -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>
/// 获取或设置轮询周期,单位为毫秒。

View File

@@ -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))
{

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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