diff --git a/Models/PlcConfiguration.cs b/Models/PlcConfiguration.cs index 4d2455f..d0861c3 100644 --- a/Models/PlcConfiguration.cs +++ b/Models/PlcConfiguration.cs @@ -5,6 +5,7 @@ public string IpAddress { get; set; } public int Port { get; set; } public byte SlaveId { get; set; } + public PlcFloatWordOrder FloatWordOrder { get; set; } = PlcFloatWordOrder.LowWordFirst; // 硬度 public ushort HardnessMax { get; set; } @@ -14,7 +15,8 @@ public ushort HardnessSudu { get; set; } public ushort HardnessWeiyi { get; set; } - + public ushort HardnessLimit { get; set; } + public ushort HardnessForward { get; set; } public ushort HardnessBack { get; set; } @@ -67,4 +69,10 @@ public ushort Dissolution1SampleInterval { get; set; } public ushort Dissolution2SampleInterval { get; set; } } + + public enum PlcFloatWordOrder + { + LowWordFirst, + HighWordFirst + } } diff --git a/Services/ModbusTcpPlcService.cs b/Services/ModbusTcpPlcService.cs index b742660..7cede78 100644 --- a/Services/ModbusTcpPlcService.cs +++ b/Services/ModbusTcpPlcService.cs @@ -15,6 +15,7 @@ namespace TabletTester2025.Services private readonly PlcConfiguration _config; private readonly SemaphoreSlim _connectLock = new(1, 1); + private readonly SemaphoreSlim _ioLock = new(1, 1); private TcpClient? _tcpClient; private IModbusMaster? _master; @@ -149,10 +150,11 @@ namespace TabletTester2025.Services private async Task ExecuteAsync(Func> action) { - await EnsureConnectedAsync(); - + await _ioLock.WaitAsync(); try { + await EnsureConnectedAsync(); + if (_master == null) throw new InvalidOperationException("PLC连接未初始化"); @@ -163,9 +165,20 @@ namespace TabletTester2025.Services CloseConnection(); throw; } + finally + { + _ioLock.Release(); + } } - private static float RegistersToFloat(ushort highWord, ushort lowWord) + private float RegistersToFloat(ushort firstRegister, ushort secondRegister) + { + return _config.FloatWordOrder == PlcFloatWordOrder.HighWordFirst + ? WordsToFloat(firstRegister, secondRegister) + : WordsToFloat(secondRegister, firstRegister); + } + + private static float WordsToFloat(ushort highWord, ushort lowWord) { byte[] bytes = { @@ -177,14 +190,15 @@ namespace TabletTester2025.Services return BitConverter.ToSingle(bytes, 0); } - private static ushort[] FloatToRegisters(float value) + private ushort[] FloatToRegisters(float value) { byte[] bytes = BitConverter.GetBytes(value); - return new[] - { - (ushort)((bytes[3] << 8) | bytes[2]), - (ushort)((bytes[1] << 8) | bytes[0]) - }; + ushort highWord = (ushort)((bytes[3] << 8) | bytes[2]); + ushort lowWord = (ushort)((bytes[1] << 8) | bytes[0]); + + return _config.FloatWordOrder == PlcFloatWordOrder.HighWordFirst + ? new[] { highWord, lowWord } + : new[] { lowWord, highWord }; } private void CloseConnection() @@ -200,6 +214,7 @@ namespace TabletTester2025.Services { CloseConnection(); _connectLock.Dispose(); + _ioLock.Dispose(); } } diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 1177ee4..95a47d3 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -73,7 +73,7 @@ namespace TabletTester2025.ViewModels ShowDataCommand = new AsyncRelayCommand(async () => { // 用你项目里已有的PLC实例(假设叫 _plcClient) - var window = new ShowData(_plc); + var window = new ShowData(_plc, _plcConfig); window.Owner = Application.Current.MainWindow; window.ShowDialog(); }); diff --git a/Views/ShowData.xaml.cs b/Views/ShowData.xaml.cs index fbf669c..6cacce0 100644 --- a/Views/ShowData.xaml.cs +++ b/Views/ShowData.xaml.cs @@ -1,37 +1,39 @@ using Microsoft.Win32; -using Sunny.UI; using System; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using TabletTester2025.Helpers; +using TabletTester2025.Models; using TabletTester2025.Services; -using static OfficeOpenXml.ExcelErrorValue; namespace 片剂四用仪.Views { public partial class ShowData : Window { private readonly IPlcService _plc; - private CancellationTokenSource _cts; + private readonly PlcParamMapping[] _paramMappings; + private CancellationTokenSource? _cts; - public ShowData(IPlcService plc) + public ShowData(IPlcService plc, PlcConfiguration plcConfig) { InitializeComponent(); _plc = plc; + _paramMappings = BuildParamMappings(plcConfig); Loaded += ShowData_Loaded; Closing += ShowData_Closing; } private async void ShowData_Loaded(object sender, RoutedEventArgs e) { - _cts = new CancellationTokenSource(); + var cts = new CancellationTokenSource(); + _cts = cts; BindAllTextBoxWriteEvents(); - await StartPlcReadLoop(_cts.Token); + await StartPlcReadLoop(cts.Token); } - private void ShowData_Closing(object sender, System.ComponentModel.CancelEventArgs e) + private void ShowData_Closing(object? sender, System.ComponentModel.CancelEventArgs e) { _cts?.Cancel(); } @@ -135,37 +137,51 @@ namespace 片剂四用仪.Views } // ====================== 地址映射表 ====================== - private readonly PlcParamMapping[] _paramMappings = new[] + private static PlcParamMapping[] BuildParamMappings(PlcConfiguration plcConfig) { - new PlcParamMapping("txt_HardnessSpeed", 300, PlcParamType.Float), - new PlcParamMapping("txt_HardnessDisplacement", 310, PlcParamType.Float), - new PlcParamMapping("txt_HardnessMotorLimit", 298, PlcParamType.Float), - new PlcParamMapping("txt_HardnessDamageThreshold", 400, PlcParamType.Float), - + return new[] + { + new PlcParamMapping("txt_HardnessSpeed", AddressOrDefault(plcConfig.HardnessSudu, 300), PlcParamType.Float), + new PlcParamMapping("txt_HardnessDisplacement", AddressOrDefault(plcConfig.HardnessWeiyi, 310), PlcParamType.Float), + new PlcParamMapping("txt_HardnessMotorLimit", AddressOrDefault(plcConfig.HardnessLimit, 298), PlcParamType.Float), + new PlcParamMapping("txt_HardnessDamageThreshold", AddressOrDefault(plcConfig.HardnessPoSun, 400), PlcParamType.Float), - new PlcParamMapping("txt_BrittlenessTestTime", 410, PlcParamType.Int), - new PlcParamMapping("txt_PreBrittlenessMass", 412, PlcParamType.Float), - new PlcParamMapping("txt_PostBrittlenessMass", 414, PlcParamType.Float), - new PlcParamMapping("txt_WeightLossRate", 416, PlcParamType.Int), + new PlcParamMapping("txt_BrittlenessTestTime", AddressOrDefault(plcConfig.FriabilityTestTime, 410), PlcParamType.Int), + new PlcParamMapping("txt_PreBrittlenessMass", ResolveWeightRegister(plcConfig.FriabilityWeightBefore, plcConfig.WeightBefore, 412), PlcParamType.Float), + new PlcParamMapping("txt_PostBrittlenessMass", ResolveWeightRegister(plcConfig.FriabilityWeightAfter, plcConfig.WeightAfter, 414), PlcParamType.Float), + new PlcParamMapping("txt_WeightLossRate", 416, PlcParamType.Int), - new PlcParamMapping("txt_DisintegrationSpeed", 330, PlcParamType.Float), - new PlcParamMapping("txt_DisintegrationTime", 420, PlcParamType.Int), + new PlcParamMapping("txt_DisintegrationSpeed", AddressOrDefault(plcConfig.DisintegrationSpeed, 330), PlcParamType.Float), + new PlcParamMapping("txt_DisintegrationTime", AddressOrDefault(plcConfig.DisintegrationTime, 420), PlcParamType.Int), - new PlcParamMapping("txt_DissolutionTime", 430, PlcParamType.Int), - new PlcParamMapping("txt_Dissolution2Time", 440, PlcParamType.Int), - new PlcParamMapping("txt_Dissolution1SamplingInterval", 432, PlcParamType.Float), - new PlcParamMapping("txt_Dissolution2SamplingInterval", 442, PlcParamType.Float), + new PlcParamMapping("txt_DissolutionTime", AddressOrDefault(plcConfig.Dissolution1Time, 430), PlcParamType.Int), + new PlcParamMapping("txt_Dissolution2Time", AddressOrDefault(plcConfig.Dissolution2Time, 440), PlcParamType.Int), + new PlcParamMapping("txt_Dissolution1SamplingInterval", AddressOrDefault(plcConfig.Dissolution1SampleInterval, 432), PlcParamType.Float), + new PlcParamMapping("txt_Dissolution2SamplingInterval", AddressOrDefault(plcConfig.Dissolution2SampleInterval, 442), PlcParamType.Float), - - new PlcParamMapping("txt_ForceCoefficient", 1320, PlcParamType.Float), - new PlcParamMapping("txt_ForceProtection", 1322, PlcParamType.Float), - - new PlcParamMapping("txt_TemperatureDisplay", 1430, PlcParamType.Float), + new PlcParamMapping("txt_ForceCoefficient", 1320, PlcParamType.Float), + new PlcParamMapping("txt_ForceProtection", 1322, PlcParamType.Float), - new PlcParamMapping("txt_MaxForceCollect", 72, PlcParamType.Float),//读取 - new PlcParamMapping("txt_ForceDisplay", 1314, PlcParamType.Float),//读取 - new PlcParamMapping("txt_TemperatureCoefficient", 1428, PlcParamType.Float),//读取 - }; + new PlcParamMapping("txt_TemperatureDisplay", AddressOrDefault(plcConfig.DisintegrationTemp, 1430), PlcParamType.Float), + + new PlcParamMapping("txt_MaxForceCollect", AddressOrDefault(plcConfig.HardnessMax, 72), PlcParamType.Float), + new PlcParamMapping("txt_ForceDisplay", AddressOrDefault(plcConfig.HardnessShishilizhi, 1314), PlcParamType.Float), + new PlcParamMapping("txt_TemperatureCoefficient", 1428, PlcParamType.Float), + }; + } + + private static ushort AddressOrDefault(ushort configuredAddress, ushort fallbackAddress) + { + return configuredAddress != 0 ? configuredAddress : fallbackAddress; + } + + private static ushort ResolveWeightRegister(ushort primaryAddress, ushort compatibleAddress, ushort fallbackAddress) + { + if (primaryAddress != 0) + return primaryAddress; + + return AddressOrDefault(compatibleAddress, fallbackAddress); + } } internal class PlcParamMapping diff --git a/appsettings.json b/appsettings.json index f1e4f7a..d04d812 100644 --- a/appsettings.json +++ b/appsettings.json @@ -7,6 +7,7 @@ "IpAddress": "192.168.1.10", "Port": 502, "SlaveId": 1, + "FloatWordOrder": "LowWordFirst", // 三菱D寄存器REAL:低字在前,避免参数写入后PLC显示NaN //"HardnessValue": 72, "HardnessStartCoil": 70, //硬度工位1启动测试M70