更新20260518

This commit is contained in:
GukSang.Jin
2026-05-19 18:22:00 +08:00
parent 00c224ceff
commit 2f4388723c
5 changed files with 83 additions and 43 deletions

View File

@@ -5,6 +5,7 @@
public string IpAddress { get; set; } public string IpAddress { get; set; }
public int Port { get; set; } public int Port { get; set; }
public byte SlaveId { get; set; } public byte SlaveId { get; set; }
public PlcFloatWordOrder FloatWordOrder { get; set; } = PlcFloatWordOrder.LowWordFirst;
// 硬度 // 硬度
public ushort HardnessMax { get; set; } public ushort HardnessMax { get; set; }
@@ -14,7 +15,8 @@
public ushort HardnessSudu { get; set; } public ushort HardnessSudu { get; set; }
public ushort HardnessWeiyi { get; set; } public ushort HardnessWeiyi { get; set; }
public ushort HardnessLimit { get; set; }
public ushort HardnessForward { get; set; } public ushort HardnessForward { get; set; }
public ushort HardnessBack { get; set; } public ushort HardnessBack { get; set; }
@@ -67,4 +69,10 @@
public ushort Dissolution1SampleInterval { get; set; } public ushort Dissolution1SampleInterval { get; set; }
public ushort Dissolution2SampleInterval { get; set; } public ushort Dissolution2SampleInterval { get; set; }
} }
public enum PlcFloatWordOrder
{
LowWordFirst,
HighWordFirst
}
} }

View File

@@ -15,6 +15,7 @@ namespace TabletTester2025.Services
private readonly PlcConfiguration _config; private readonly PlcConfiguration _config;
private readonly SemaphoreSlim _connectLock = new(1, 1); private readonly SemaphoreSlim _connectLock = new(1, 1);
private readonly SemaphoreSlim _ioLock = new(1, 1);
private TcpClient? _tcpClient; private TcpClient? _tcpClient;
private IModbusMaster? _master; private IModbusMaster? _master;
@@ -149,10 +150,11 @@ namespace TabletTester2025.Services
private async Task<T> ExecuteAsync<T>(Func<IModbusMaster, Task<T>> action) private async Task<T> ExecuteAsync<T>(Func<IModbusMaster, Task<T>> action)
{ {
await EnsureConnectedAsync(); await _ioLock.WaitAsync();
try try
{ {
await EnsureConnectedAsync();
if (_master == null) if (_master == null)
throw new InvalidOperationException("PLC连接未初始化"); throw new InvalidOperationException("PLC连接未初始化");
@@ -163,9 +165,20 @@ namespace TabletTester2025.Services
CloseConnection(); CloseConnection();
throw; 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 = byte[] bytes =
{ {
@@ -177,14 +190,15 @@ namespace TabletTester2025.Services
return BitConverter.ToSingle(bytes, 0); return BitConverter.ToSingle(bytes, 0);
} }
private static ushort[] FloatToRegisters(float value) private ushort[] FloatToRegisters(float value)
{ {
byte[] bytes = BitConverter.GetBytes(value); byte[] bytes = BitConverter.GetBytes(value);
return new[] ushort highWord = (ushort)((bytes[3] << 8) | bytes[2]);
{ ushort lowWord = (ushort)((bytes[1] << 8) | bytes[0]);
(ushort)((bytes[3] << 8) | bytes[2]),
(ushort)((bytes[1] << 8) | bytes[0]) return _config.FloatWordOrder == PlcFloatWordOrder.HighWordFirst
}; ? new[] { highWord, lowWord }
: new[] { lowWord, highWord };
} }
private void CloseConnection() private void CloseConnection()
@@ -200,6 +214,7 @@ namespace TabletTester2025.Services
{ {
CloseConnection(); CloseConnection();
_connectLock.Dispose(); _connectLock.Dispose();
_ioLock.Dispose();
} }
} }

View File

@@ -73,7 +73,7 @@ namespace TabletTester2025.ViewModels
ShowDataCommand = new AsyncRelayCommand(async () => ShowDataCommand = new AsyncRelayCommand(async () =>
{ {
// 用你项目里已有的PLC实例假设叫 _plcClient // 用你项目里已有的PLC实例假设叫 _plcClient
var window = new ShowData(_plc); var window = new ShowData(_plc, _plcConfig);
window.Owner = Application.Current.MainWindow; window.Owner = Application.Current.MainWindow;
window.ShowDialog(); window.ShowDialog();
}); });

View File

@@ -1,37 +1,39 @@
using Microsoft.Win32; using Microsoft.Win32;
using Sunny.UI;
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using TabletTester2025.Helpers; using TabletTester2025.Helpers;
using TabletTester2025.Models;
using TabletTester2025.Services; using TabletTester2025.Services;
using static OfficeOpenXml.ExcelErrorValue;
namespace .Views namespace .Views
{ {
public partial class ShowData : Window public partial class ShowData : Window
{ {
private readonly IPlcService _plc; 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(); InitializeComponent();
_plc = plc; _plc = plc;
_paramMappings = BuildParamMappings(plcConfig);
Loaded += ShowData_Loaded; Loaded += ShowData_Loaded;
Closing += ShowData_Closing; Closing += ShowData_Closing;
} }
private async void ShowData_Loaded(object sender, RoutedEventArgs e) private async void ShowData_Loaded(object sender, RoutedEventArgs e)
{ {
_cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
_cts = cts;
BindAllTextBoxWriteEvents(); 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(); _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), return new[]
new PlcParamMapping("txt_HardnessDisplacement", 310, PlcParamType.Float), {
new PlcParamMapping("txt_HardnessMotorLimit", 298, PlcParamType.Float), new PlcParamMapping("txt_HardnessSpeed", AddressOrDefault(plcConfig.HardnessSudu, 300), PlcParamType.Float),
new PlcParamMapping("txt_HardnessDamageThreshold", 400, 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_BrittlenessTestTime", AddressOrDefault(plcConfig.FriabilityTestTime, 410), PlcParamType.Int),
new PlcParamMapping("txt_PreBrittlenessMass", 412, PlcParamType.Float), new PlcParamMapping("txt_PreBrittlenessMass", ResolveWeightRegister(plcConfig.FriabilityWeightBefore, plcConfig.WeightBefore, 412), PlcParamType.Float),
new PlcParamMapping("txt_PostBrittlenessMass", 414, PlcParamType.Float), new PlcParamMapping("txt_PostBrittlenessMass", ResolveWeightRegister(plcConfig.FriabilityWeightAfter, plcConfig.WeightAfter, 414), PlcParamType.Float),
new PlcParamMapping("txt_WeightLossRate", 416, PlcParamType.Int), new PlcParamMapping("txt_WeightLossRate", 416, PlcParamType.Int),
new PlcParamMapping("txt_DisintegrationSpeed", 330, PlcParamType.Float), new PlcParamMapping("txt_DisintegrationSpeed", AddressOrDefault(plcConfig.DisintegrationSpeed, 330), PlcParamType.Float),
new PlcParamMapping("txt_DisintegrationTime", 420, PlcParamType.Int), new PlcParamMapping("txt_DisintegrationTime", AddressOrDefault(plcConfig.DisintegrationTime, 420), PlcParamType.Int),
new PlcParamMapping("txt_DissolutionTime", 430, PlcParamType.Int), new PlcParamMapping("txt_DissolutionTime", AddressOrDefault(plcConfig.Dissolution1Time, 430), PlcParamType.Int),
new PlcParamMapping("txt_Dissolution2Time", 440, PlcParamType.Int), new PlcParamMapping("txt_Dissolution2Time", AddressOrDefault(plcConfig.Dissolution2Time, 440), PlcParamType.Int),
new PlcParamMapping("txt_Dissolution1SamplingInterval", 432, PlcParamType.Float), new PlcParamMapping("txt_Dissolution1SamplingInterval", AddressOrDefault(plcConfig.Dissolution1SampleInterval, 432), PlcParamType.Float),
new PlcParamMapping("txt_Dissolution2SamplingInterval", 442, PlcParamType.Float), new PlcParamMapping("txt_Dissolution2SamplingInterval", AddressOrDefault(plcConfig.Dissolution2SampleInterval, 442), PlcParamType.Float),
new PlcParamMapping("txt_ForceCoefficient", 1320, PlcParamType.Float),
new PlcParamMapping("txt_ForceCoefficient", 1320, PlcParamType.Float), new PlcParamMapping("txt_ForceProtection", 1322, PlcParamType.Float),
new PlcParamMapping("txt_ForceProtection", 1322, PlcParamType.Float),
new PlcParamMapping("txt_TemperatureDisplay", 1430, PlcParamType.Float),
new PlcParamMapping("txt_MaxForceCollect", 72, PlcParamType.Float),//读取 new PlcParamMapping("txt_TemperatureDisplay", AddressOrDefault(plcConfig.DisintegrationTemp, 1430), PlcParamType.Float),
new PlcParamMapping("txt_ForceDisplay", 1314, PlcParamType.Float),//读取
new PlcParamMapping("txt_TemperatureCoefficient", 1428, 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 internal class PlcParamMapping

View File

@@ -7,6 +7,7 @@
"IpAddress": "192.168.1.10", "IpAddress": "192.168.1.10",
"Port": 502, "Port": 502,
"SlaveId": 1, "SlaveId": 1,
"FloatWordOrder": "LowWordFirst", // 三菱D寄存器REAL低字在前避免参数写入后PLC显示NaN
//"HardnessValue": 72, //"HardnessValue": 72,
"HardnessStartCoil": 70, //硬度工位1启动测试M70 "HardnessStartCoil": 70, //硬度工位1启动测试M70