@@ -50,7 +50,7 @@ namespace ModbusDemo | |||
//将读线圈注册 | |||
containerRegistry.Register<ModbusRTU>(); | |||
//附加功能如何读取单数寄存器; | |||
//containerRegistry.RegisterForNavigation<AttachUC, AttachUCViewModel>(); | |||
containerRegistry.RegisterForNavigation<AttachUC, AttachUCViewModel>(); | |||
// 1. 加载配置文件 | |||
@@ -6,17 +6,17 @@ using System.Threading.Tasks; | |||
namespace ModbusDemo.Device | |||
{ | |||
public interface IModbusRTU | |||
public interface IModbusRTU:IDisposable | |||
{ | |||
public bool[] ReadCoil(byte slaveAddress, ushort startAddress, ushort numberOfPoints); | |||
public void WriteCoil(byte slaveAddress, ushort startAddress, bool[] data); | |||
public bool WriteCoil(byte slaveAddress, ushort startAddress, bool[] data); | |||
public ushort[] ReadRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints); | |||
public ushort[] ReadRegister(byte slaveAddress, ushort startAddress, ushort numberOfPoints); | |||
public void WriteRegisters(byte slaveAddress, ushort startAddress, ushort[] data); | |||
public bool WriteRegister(byte slaveAddress, ushort startAddress, ushort[] data); | |||
} | |||
} |
@@ -5,16 +5,19 @@ using System; | |||
using System.Collections.Generic; | |||
using System.IO.Ports; | |||
using System.Linq; | |||
using System.Runtime.InteropServices; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using System.Windows; | |||
using static System.Runtime.InteropServices.JavaScript.JSType; | |||
namespace ModbusDemo.Device | |||
{ | |||
public class ModbusRTU : IModbusRTU | |||
{ | |||
// 标记是否已释放资源 | |||
private bool _disposed = false; | |||
//串口 | |||
private SerialPort _serialPort; | |||
//用于操作数据库 | |||
@@ -46,7 +49,7 @@ namespace ModbusDemo.Device | |||
return resultValue; | |||
} | |||
//构建发送报文 | |||
byte[] sendMessage = ConstructMessage<ushort>(0x01, slaveAddress, startAddress, numberOfPoints,ProcessReadData); | |||
byte[] sendMessage = ConstructMessage<ushort>(0x01, slaveAddress, startAddress, numberOfPoints, ProcessReadData); | |||
//发送报文 | |||
byte[] responseMessage = SendMessage(sendMessage); | |||
//判断发送过来的字节是否正确 | |||
@@ -72,7 +75,7 @@ namespace ModbusDemo.Device | |||
.ToArray(); | |||
} | |||
//将数据插入到数据库 | |||
InsertDatabase(sendMessage, responseMessage,"读线圈"); | |||
InsertDatabase(sendMessage, responseMessage, "读线圈"); | |||
return resultValue; | |||
} | |||
@@ -82,12 +85,12 @@ namespace ModbusDemo.Device | |||
/// <param name="slaveAddress">从站地址</param> | |||
/// <param name="startAddress">起始地址</param> | |||
/// <param name="data">写入的数据</param> | |||
public void WriteCoil(byte slaveAddress, ushort startAddress, bool[] data) | |||
public bool WriteCoil(byte slaveAddress, ushort startAddress, bool[] data) | |||
{ | |||
if (!_serialPort.IsOpen) | |||
{ | |||
MessageBox.Show("串口没有链接,请先链接"); | |||
return; | |||
return false; | |||
} | |||
//构建报文 | |||
byte[] sendMessage = ConstructMessage<bool[]>(0x0F, slaveAddress, startAddress, data, ProcessCoilData); | |||
@@ -95,13 +98,13 @@ namespace ModbusDemo.Device | |||
byte[] responseMessage = SendMessage(sendMessage); | |||
//将数据插入到数据库 | |||
InsertDatabase(sendMessage, responseMessage,"写线圈"); | |||
InsertDatabase(sendMessage, responseMessage, "写线圈"); | |||
//判断发送回来的数据是否正确 | |||
CheckResponse(responseMessage); | |||
return CheckResponse(responseMessage); | |||
} | |||
public ushort[] ReadRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints) | |||
public ushort[] ReadRegister(byte slaveAddress, ushort startAddress, ushort numberOfPoints) | |||
{ | |||
ushort[] resultValue = null; | |||
if (!_serialPort.IsOpen) | |||
@@ -115,7 +118,7 @@ namespace ModbusDemo.Device | |||
byte[] responseMessage = SendMessage(sendMessage); | |||
//将数据插入到数据库 | |||
InsertDatabase(sendMessage, responseMessage,"读寄存器"); | |||
InsertDatabase(sendMessage, responseMessage, "读寄存器"); | |||
//判断发送回来的数据是否正确 | |||
if (CheckResponse(responseMessage)) | |||
{ | |||
@@ -139,12 +142,12 @@ namespace ModbusDemo.Device | |||
/// <param name="slaveAddress"></param> | |||
/// <param name="startAddress"></param> | |||
/// <param name="data"></param> | |||
public void WriteRegisters(byte slaveAddress, ushort startAddress, ushort[] data) | |||
public bool WriteRegister(byte slaveAddress, ushort startAddress, ushort[] data) | |||
{ | |||
if (!_serialPort.IsOpen) | |||
{ | |||
MessageBox.Show("串口没有链接,请先链接"); | |||
return; | |||
return false; | |||
} | |||
//构建报文 | |||
byte[] sendMessage = ConstructMessage<ushort[]>(0x10, slaveAddress, startAddress, data, ProcessRegisterData); | |||
@@ -154,9 +157,9 @@ namespace ModbusDemo.Device | |||
//将数据插入到数据库 | |||
InsertDatabase(sendMessage, responseMessage, "写寄存器"); | |||
//判断发送回来的数据是否正确 | |||
CheckResponse(responseMessage); | |||
return CheckResponse(responseMessage); | |||
} | |||
@@ -190,7 +193,7 @@ namespace ModbusDemo.Device | |||
//获取CRC校验码 | |||
byte[] getCRC = sendByteList.ToArray(); | |||
byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length); | |||
byte[] resultCRC = CalculateCRC(getCRC, getCRC.Length); | |||
sendByteList.Add(resultCRC[0]); | |||
sendByteList.Add(resultCRC[1]); | |||
@@ -259,13 +262,8 @@ namespace ModbusDemo.Device | |||
} | |||
} | |||
/// <summary> | |||
/// 读寄存器操作 | |||
/// </summary> | |||
/// <param name="slaveAddress"></param> | |||
/// <param name="startAddress"></param> | |||
/// <param name="numberOfPoints"></param> | |||
/// <returns></returns> | |||
// 自定义锁 | |||
private static readonly object _Lock = new object(); | |||
/// <summary> | |||
/// 将构建好的报文发送出去 | |||
@@ -274,6 +272,7 @@ namespace ModbusDemo.Device | |||
/// <returns></returns> | |||
public byte[] SendMessage(byte[] sendByte) | |||
{ | |||
int maxRetries = 3; // 最大重试次数 | |||
int attempt = 0; | |||
byte[] response = null; | |||
@@ -281,48 +280,49 @@ namespace ModbusDemo.Device | |||
while (attempt < maxRetries) | |||
{ | |||
if (_serialPort.IsOpen) | |||
lock (_Lock) | |||
{ | |||
// 每次重试前清空缓冲区 | |||
responseData.Clear(); | |||
// 清除输入缓冲区残留数据 | |||
_serialPort.DiscardInBuffer(); | |||
} | |||
try | |||
{ | |||
_serialPort.Write(sendByte, 0, sendByte.Length); | |||
//使用时间去轮询查询数据 | |||
DateTime startTime = DateTime.Now; | |||
while ((DateTime.Now - startTime).TotalMilliseconds < _serialPort.ReadTimeout) | |||
if (_serialPort.IsOpen) | |||
{ | |||
// 每次重试前清空缓冲区 | |||
responseData.Clear(); | |||
// 清除输入缓冲区残留数据 | |||
_serialPort.DiscardInBuffer(); | |||
} | |||
try | |||
{ | |||
int bytesToRead = _serialPort.BytesToRead; | |||
if (bytesToRead > 0) | |||
_serialPort.Write(sendByte, 0, sendByte.Length); | |||
//使用时间去轮询查询数据 | |||
DateTime startTime = DateTime.Now; | |||
while ((DateTime.Now - startTime).TotalMilliseconds < _serialPort.ReadTimeout) | |||
{ | |||
byte[] buffer = new byte[bytesToRead]; | |||
int bytesRead = _serialPort.Read(buffer, 0, bytesToRead); | |||
responseData.AddRange(buffer.Take(bytesRead)); | |||
int bytesToRead = _serialPort.BytesToRead; | |||
if (bytesToRead > 0) | |||
{ | |||
byte[] buffer = new byte[bytesToRead]; | |||
int bytesRead = _serialPort.Read(buffer, 0, bytesToRead); | |||
responseData.AddRange(buffer.Take(bytesRead)); | |||
} | |||
//延迟20毫秒,节约cpu资源 | |||
Task.Delay(20).Wait(); | |||
} | |||
//延迟20毫秒,节约cpu资源 | |||
Thread.Sleep(20); | |||
response = responseData.ToArray(); | |||
if (response.Length == 0) | |||
{ | |||
attempt++; | |||
continue; | |||
} | |||
return response; | |||
} | |||
response = responseData.ToArray(); | |||
if (response.Length == 0) | |||
catch (Exception) | |||
{ | |||
attempt++; | |||
continue; | |||
} | |||
return response; | |||
} | |||
catch (Exception) | |||
{ | |||
finally | |||
{ | |||
attempt++; | |||
} | |||
} | |||
finally | |||
{ | |||
attempt++; | |||
} | |||
} | |||
return null; | |||
} | |||
@@ -517,5 +517,44 @@ namespace ModbusDemo.Device | |||
return (calculatedCrc[calculatedCrc.Length - 1] == receivedCrcHi) && | |||
(calculatedCrc[calculatedCrc.Length - 2] == receivedCrcLo); | |||
} | |||
/// <summary> | |||
/// 释放资源 | |||
/// </summary> | |||
public void Dispose() | |||
{ | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
/// <summary> | |||
/// 真实调用的方法 | |||
/// </summary> | |||
/// <param name="disposing"></param> | |||
protected virtual void Dispose(bool disposing) | |||
{ | |||
if (_disposed) | |||
return; // 避免重复释放 | |||
// 释放托管资源(仅在手动调用Dispose时执行) | |||
if (disposing) | |||
{ | |||
//_serialPort是单例的,不要手动释放 | |||
// 释放数据库上下文是瞬态的需要手动释放 | |||
if (_modbusDbContext != null) | |||
{ | |||
_modbusDbContext.Dispose(); | |||
_modbusDbContext = null; | |||
} | |||
} | |||
_disposed = true; // 标记为已释放 | |||
} | |||
} | |||
} |
@@ -13,6 +13,7 @@ namespace ModbusDemo.Extension | |||
/// </summary> | |||
class EnumBindingSourceExtension : MarkupExtension | |||
{ | |||
//枚举字段 | |||
private Type _enumType; | |||
public Type EnumType | |||
@@ -40,7 +41,13 @@ namespace ModbusDemo.Extension | |||
{ | |||
EnumType = enumType; | |||
} | |||
/// <summary> | |||
/// 将枚举转换为数组,返回出去 | |||
/// 可以绑定可空枚举 | |||
/// </summary> | |||
/// <param name="serviceProvider">xmal上下文</param> | |||
/// <returns></returns> | |||
/// <exception cref="InvalidOperationException"></exception> | |||
public override object ProvideValue(IServiceProvider serviceProvider) | |||
{ | |||
if (_enumType == null) | |||
@@ -14,6 +14,8 @@ | |||
<Compile Remove="Device\SerialPortAdapter.cs" /> | |||
<Compile Remove="ErrorCode\ErrorCodeExtensions.cs" /> | |||
<Compile Remove="ErrorCode\ErrorInfoAttribute.cs" /> | |||
<Compile Remove="Uitls\CheckData.cs" /> | |||
<Compile Remove="Uitls\CRCUitl.cs" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -12,7 +12,12 @@ namespace ModbusDemo.Model | |||
/// </summary> | |||
public class ModbusDbContext:DbContext | |||
{ | |||
public ModbusDbContext() | |||
{ | |||
} | |||
public ModbusDbContext(DbContextOptions<ModbusDbContext> options) :base(options) | |||
{ | |||
@@ -0,0 +1,82 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using System.Windows; | |||
namespace ModbusDemo.Uitls | |||
{ | |||
public class CheckInputUitl | |||
{ | |||
/// <summary> | |||
/// 检测输入的数据是否合理 | |||
/// </summary> | |||
/// <param name="slaveAddressStr"></param> | |||
/// <param name="startAddressStr"></param> | |||
/// <param name="numberOfPointsStr"></param> | |||
/// <returns></returns> | |||
public static (bool success, byte slaveAddress, ushort startAddress, ushort numberOfPoints) ValidateReadParameters( | |||
string slaveAddressStr, string startAddressStr, string numberOfPointsStr) | |||
{ | |||
if (!byte.TryParse(slaveAddressStr, out byte slaveAddress)) | |||
{ | |||
ShowMessage("SlaveAddress 格式无效"); | |||
return (false, 0, 0, 0); | |||
} | |||
if (!ushort.TryParse(startAddressStr, out ushort startAddress)) | |||
{ | |||
ShowMessage("StartAddress 格式无效"); | |||
return (false, 0, 0, 0); | |||
} | |||
if (!ushort.TryParse(numberOfPointsStr, out ushort numberOfPoints)) | |||
{ | |||
ShowMessage("NumberOfPoints 格式无效"); | |||
return (false, 0, 0, 0); | |||
} | |||
return (true, slaveAddress, startAddress, numberOfPoints); | |||
} | |||
/// <summary> | |||
/// 检测写的数据是否合理 | |||
/// </summary> | |||
/// <param name="slaveAddressStr"></param> | |||
/// <param name="startAddressStr"></param> | |||
/// <param name="WriteData"></param> | |||
/// <returns></returns> | |||
public static (bool success, byte slaveAddress, ushort startAddress) ValidateWriteParameters( | |||
string slaveAddressStr, string startAddressStr, string WriteData) | |||
{ | |||
if (!byte.TryParse(slaveAddressStr, out byte slaveAddress)) | |||
{ | |||
ShowMessage("SlaveAddress 格式无效"); | |||
return (false, 0, 0); | |||
} | |||
if (!ushort.TryParse(startAddressStr, out ushort startAddress)) | |||
{ | |||
ShowMessage("StartAddress 格式无效"); | |||
return (false, 0, 0); | |||
} | |||
if (string.IsNullOrEmpty(WriteData)) | |||
{ | |||
ShowMessage("WriteData 格式无效"); | |||
return (false, 0, 0); | |||
} | |||
return (true, slaveAddress, startAddress); | |||
} | |||
private static void ShowMessage(string message, string title = "warn", | |||
MessageBoxImage image = MessageBoxImage.Warning) | |||
{ | |||
MessageBox.Show(message, title, MessageBoxButton.OK, image); | |||
} | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
using ModbusDemo.Model; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace ModbusDemo.Uitls | |||
{ | |||
public class DatabaseHelper | |||
{ | |||
//private readonly ModbusDbContext _modbusDbContext; | |||
//public DatabaseHelper(ModbusDbContext modbusDbContext) | |||
//{ | |||
// _modbusDbContext = modbusDbContext; | |||
//} | |||
//public static List<ModbusLog> GetOperateCoil() | |||
//{ | |||
// return _modbusDbContext.ModbusLog | |||
// .Where(log => log.OperationType == "读线圈" || log.OperationType == "写线圈") | |||
// .OrderByDescending(log => log.Time) // 按时间倒序,最新的在前 | |||
// .Take(30) // 只取前30条记录 | |||
// .ToList(); // 执行查询 | |||
//} | |||
} | |||
} |
@@ -12,15 +12,22 @@ | |||
d:DesignHeight="450" | |||
d:DesignWidth="800" | |||
mc:Ignorable="d"> | |||
<UserControl.Resources> | |||
<Style TargetType="TextBox"> | |||
<Setter Property="Margin" Value="0,5" /> | |||
<Setter Property="FontSize" Value="14" /> | |||
</Style> | |||
</UserControl.Resources> | |||
<Grid> | |||
<Grid.RowDefinitions> | |||
<RowDefinition Height="80"> | |||
</RowDefinition> | |||
<RowDefinition> | |||
</RowDefinition> | |||
<RowDefinition Height="80"> | |||
</RowDefinition> | |||
<RowDefinition> | |||
</RowDefinition> | |||
</Grid.RowDefinitions> | |||
<Button | |||
@@ -33,8 +40,28 @@ | |||
</Button> | |||
<TextBox Grid.Row="1" Margin="5" Text="{Binding ReadResult}" Style="{StaticResource MaterialDesignOutlinedTextBox}"> | |||
</TextBox> | |||
<TextBlock Grid.Row="2" Text="{Binding Time}" FontSize="16"> | |||
</TextBlock> | |||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center"> | |||
<TextBlock Margin="10,30,10,0" Text="写入位数:"> | |||
</TextBlock> | |||
<TextBox MinWidth="80" Height="20"> | |||
</TextBox> | |||
<TextBlock Margin="10,30,10,0" Text="写入数据:"> | |||
</TextBlock> | |||
<TextBox MinWidth="80" Height="20" Margin="0 0 20 0"> | |||
</TextBox> | |||
<Button | |||
Grid.Row="2" | |||
Width="80" | |||
Height="40" | |||
materialDesign:ButtonAssist.CornerRadius="20" | |||
Command="{Binding WriteMoreRegisterCmm}" | |||
Content="读取" | |||
Style="{StaticResource MaterialDesignRaisedDarkButton}"> | |||
</Button> | |||
</StackPanel> | |||
<TextBox Grid.Row="3" Margin="5" Text="{Binding ReadResult}" Style="{StaticResource MaterialDesignOutlinedTextBox}"> | |||
</TextBox> | |||
</Grid> | |||
</UserControl> |
@@ -143,7 +143,7 @@ | |||
Margin="0,-50,0,0" | |||
HorizontalAlignment="Center" | |||
VerticalAlignment="Center" | |||
Text="1代表Ture,0代表flase"> | |||
Text="1为true,0为flase"> | |||
</TextBlock> | |||
@@ -17,7 +17,7 @@ namespace ModbusDemo.VIewModel | |||
{ | |||
private SerialPort _serialPort; | |||
public DelegateCommand ReadOddRegisterCmm { get; set; } | |||
public DelegateCommand WriteMoreRegisterCmm { get; set; } | |||
//读取的结果 | |||
private string _readResult; | |||
@@ -30,18 +30,7 @@ namespace ModbusDemo.VIewModel | |||
RaisePropertyChanged(); | |||
} | |||
} | |||
//显示操作的时间 | |||
private int _time; | |||
public int Time | |||
{ | |||
get { return _time; } | |||
set | |||
{ | |||
_time = value; | |||
RaisePropertyChanged(); | |||
} | |||
} | |||
public AttachUCViewModel() | |||
@@ -50,219 +39,14 @@ namespace ModbusDemo.VIewModel | |||
} | |||
public AttachUCViewModel(SerialPort serialPort) | |||
{ | |||
//ReadOddRegisterCmm = new DelegateCommand(ReadOddRegister); | |||
WriteMoreRegisterCmm = new DelegateCommand(WriteMoreRegister); | |||
_serialPort = serialPort; | |||
} | |||
/// <summary> | |||
/// 读1000个寄存器 | |||
/// </summary> | |||
//private void ReadOddRegister() | |||
//{ | |||
// // 创建 Modbus RTU 主站 | |||
// IModbusSerialMaster master = ModbusSerialMaster.CreateRtu(_serialPort); | |||
// byte slaveId = 1; | |||
// ushort totalRegisters = 1000; | |||
// ushort chunkSize = 100; | |||
// int numChunks = (int)Math.Ceiling((double)totalRegisters / chunkSize); | |||
// ushort[] allRegisters = new ushort[totalRegisters]; | |||
// Task[] tasks = new Task[numChunks]; | |||
// DateTime startTime = DateTime.Now; | |||
// for (int i = 0; i < numChunks; i++) | |||
// { | |||
// ushort startAddress = (ushort)(10300 + i * chunkSize); | |||
// ushort currentChunkSize = 100; | |||
// int chunkIndex = i; | |||
// tasks[i] = Task.Run(() => | |||
// { | |||
// try | |||
// { | |||
// ushort[] registers = master.ReadHoldingRegisters(slaveId, startAddress, currentChunkSize); | |||
// Array.Copy(registers, 0, allRegisters, chunkIndex * chunkSize, currentChunkSize); | |||
// } | |||
// catch (Exception ex) | |||
// { | |||
// MessageBox.Show($"线程 {chunkIndex} 读取寄存器时出错: {ex.Message}"); | |||
// } | |||
// }); | |||
// } | |||
// // 等待所有任务完成 | |||
// Task.WaitAll(tasks); | |||
// Time = (int)(DateTime.Now - startTime).TotalMilliseconds; | |||
// StringBuilder result = new StringBuilder(); | |||
// int count = 0; | |||
// for (int i = 1; i < totalRegisters; i += 2) | |||
// { | |||
// result.Append(allRegisters[i].ToString() + " "); | |||
// count++; | |||
// if (count % 50 == 0) | |||
// { | |||
// result.AppendLine(); | |||
// } | |||
// } | |||
// ReadResult = result.ToString(); | |||
//} | |||
/// <summary> | |||
/// 自己的类 | |||
/// </summary> | |||
private void ReadOddRegister2() | |||
private void WriteMoreRegister() | |||
{ | |||
byte slaveId = 1; | |||
ushort totalRegisters = 1000; | |||
ushort chunkSize = 100; | |||
int numChunks = (int)Math.Ceiling((double)totalRegisters / chunkSize); | |||
ushort[] allRegisters = new ushort[totalRegisters]; | |||
Task[] tasks = new Task[numChunks]; | |||
DateTime startTime = DateTime.Now; | |||
for (int i = 0; i < numChunks; i++) | |||
{ | |||
ushort startAddress = (ushort)(10300 + i * chunkSize); | |||
ushort currentChunkSize = 100; | |||
int chunkIndex = i; | |||
ushort[] registers = ReadRegisters(slaveId, startAddress, currentChunkSize); | |||
Array.Copy(registers, 0, allRegisters, chunkIndex * chunkSize, currentChunkSize); | |||
} | |||
// 等待所有任务完成 | |||
Time = (int)(DateTime.Now - startTime).TotalMilliseconds; | |||
StringBuilder result = new StringBuilder(); | |||
int count = 0; | |||
for (int i = 1; i < totalRegisters; i += 2) | |||
{ | |||
result.Append(allRegisters[i].ToString() + " "); | |||
count++; | |||
if (count % 50 == 0) | |||
{ | |||
result.AppendLine(); | |||
} | |||
} | |||
ReadResult = result.ToString(); | |||
} | |||
public ushort[] ReadRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints) | |||
{ | |||
ushort[] resultValue = null; | |||
if (!_serialPort.IsOpen) | |||
{ | |||
MessageBox.Show("串口没有链接,请先链接"); | |||
return resultValue; | |||
} | |||
var sendByteList = new List<byte>(8) | |||
{ | |||
slaveAddress, // 从站地址 | |||
0x03, // 功能码:读保持寄存器 | |||
}; | |||
// 添加起始地址(高字节在前) | |||
sendByteList.AddRange(BitConverter.GetBytes(startAddress).Reverse()); | |||
// 添加读取点数(高字节在前) | |||
sendByteList.AddRange(BitConverter.GetBytes(numberOfPoints).Reverse()); | |||
//获取CRC校验码 | |||
byte[] getCRC = sendByteList.ToArray(); | |||
byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length); | |||
sendByteList.Add(resultCRC[0]); | |||
sendByteList.Add(resultCRC[1]); | |||
byte[] sendByte = sendByteList.ToArray(); | |||
int maxRetries = 3; // 最大重试次数 | |||
int attempt = 0; | |||
List<byte> responseData = new List<byte>(); | |||
byte[] response = null; | |||
while (attempt < maxRetries) | |||
{ | |||
// 每次重试前清空缓冲区 | |||
responseData.Clear(); | |||
// 清除输入缓冲区残留数据 | |||
_serialPort.DiscardInBuffer(); | |||
try | |||
{ | |||
_serialPort.Write(sendByte, 0, sendByte.Length); | |||
//使用时间去轮询查询数据 | |||
DateTime startTime = DateTime.Now; | |||
while ((DateTime.Now - startTime).TotalMilliseconds < _serialPort.ReadTimeout) | |||
{ | |||
int bytesToRead = _serialPort.BytesToRead; | |||
if (bytesToRead > 0) | |||
{ | |||
byte[] buffer = new byte[bytesToRead]; | |||
int bytesRead = _serialPort.Read(buffer, 0, bytesToRead); | |||
responseData.AddRange(buffer.Take(bytesRead)); | |||
} | |||
//延迟20毫秒,节约cpu资源 | |||
Thread.Sleep(20); | |||
} | |||
response = responseData.ToArray(); | |||
if (response.Length == 0) | |||
{ | |||
continue; | |||
} | |||
resultValue = ParseRegistersresponse(sendByte, response, numberOfPoints); | |||
return resultValue; | |||
} | |||
catch (Exception) | |||
{ | |||
} | |||
finally | |||
{ | |||
attempt++; | |||
} | |||
} | |||
return resultValue; | |||
} | |||
/// <summary> | |||
/// 解析读取到的操作 | |||
/// </summary> | |||
/// <param name="sendByte"></param> | |||
/// <param name="response"></param> | |||
/// <param name="numberOfPoints"></param> | |||
/// <returns></returns> | |||
public ushort[] ParseRegistersresponse(byte[] sendByte, byte[] response, ushort numberOfPoints) | |||
{ | |||
//判断发送回来的数据是否正确 | |||
if (CheckData.CheckResponse(response)) | |||
{ | |||
ushort[] registers = new ushort[numberOfPoints]; | |||
for (int i = 0; i < numberOfPoints; i++) | |||
{ | |||
int pos = 3 + i * 2; | |||
// 大端序转换 (高位在前) | |||
registers[i] = (ushort)((response[pos] << 8) | response[pos + 1]); | |||
} | |||
return registers; | |||
} | |||
else | |||
{ | |||
return null; | |||
} | |||
} | |||
} | |||
} |
@@ -18,9 +18,10 @@ namespace ModbusDemo.VIewModel | |||
/// <summary> | |||
/// 这是CoilUC的VM用来支持数据绑定 | |||
/// </summary> | |||
public class CoilUCViewModel : BindableBase | |||
public class CoilUCViewModel : BindableBase,IDisposable | |||
{ | |||
// 标记是否已释放资源 | |||
private bool _disposed = false; | |||
//事件聚合器 | |||
private readonly IEventAggregator _eventAggregator; | |||
//定义数据库操作 | |||
@@ -205,33 +206,18 @@ namespace ModbusDemo.VIewModel | |||
/// </summary> | |||
private void ReadCoil() | |||
{ | |||
//将按钮设置为不可点击状态,并解析用户输入的信息 | |||
IsEnable = false; | |||
if (!byte.TryParse(SlaveAddress, out byte slaveAddressValue)) | |||
{ | |||
MessageBox.Show("SlaveAddress 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
if (!ushort.TryParse(StartAddress, out ushort startAddressValue)) | |||
//检测当前输入的信息是否正确 | |||
var validation = CheckInputUitl.ValidateReadParameters(SlaveAddress, StartAddress, NumberOfPoints); | |||
if (!validation.success) | |||
{ | |||
MessageBox.Show("StartAddress 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
if (!ushort.TryParse(NumberOfPoints, out ushort numberOfPointsValue)) | |||
{ | |||
MessageBox.Show("NumberOfPoints 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
//使用子线程,防止ui线程卡顿 | |||
Task.Run(() => | |||
{ | |||
bool[] result = _modbusRTU.ReadCoil(slaveAddressValue, startAddressValue, numberOfPointsValue); | |||
IsEnable = false; | |||
bool[] result = _modbusRTU.ReadCoil(validation.slaveAddress, validation.startAddress, validation.numberOfPoints); | |||
if (result == null) | |||
{ | |||
@@ -240,7 +226,6 @@ namespace ModbusDemo.VIewModel | |||
IsEnable = true; | |||
return; | |||
} | |||
string temp = ""; | |||
int count = 0; | |||
foreach (var item in result) | |||
@@ -266,24 +251,10 @@ namespace ModbusDemo.VIewModel | |||
private void WriteCoil() | |||
{ | |||
//将按钮设置为不可点击状态,并解析用户输入的信息 | |||
IsEnable = false; | |||
if (!byte.TryParse(WriteSlaveAddress, out byte writeSlaveAddressValue)) | |||
{ | |||
MessageBox.Show("WriteSlaveAddress 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
if (!ushort.TryParse(WriteStartAddress, out ushort WriteStartAddressValue)) | |||
{ | |||
MessageBox.Show("WriteStartAddress 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
if (string.IsNullOrEmpty(WriteData)) | |||
//检测当前输入的信息是否正确 | |||
var validation = CheckInputUitl.ValidateWriteParameters(WriteSlaveAddress, WriteStartAddress,WriteData); | |||
if (!validation.success) | |||
{ | |||
MessageBox.Show("WriteData 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
//去除字符串中的空格 | |||
@@ -294,19 +265,20 @@ namespace ModbusDemo.VIewModel | |||
//使用子线程执行,避免ui线程卡顿 | |||
Task.Run(() => | |||
{ | |||
_modbusRTU.WriteCoil(writeSlaveAddressValue, WriteStartAddressValue, data); | |||
IsEnable = false; | |||
bool issucceed = _modbusRTU.WriteCoil(validation.slaveAddress, validation.startAddress, data); | |||
ModbusLogList = GetOperateCoil(); | |||
ModbusLog modbusLog = ModbusLogList.FirstOrDefault(); | |||
if (modbusLog != null && modbusLog.ResponseData != string.Empty && modbusLog.ResponseData[3] !='8') | |||
if (issucceed) | |||
{ | |||
MessageBox.Show("写入成功"); | |||
IsEnable = true; | |||
} | |||
else | |||
{ | |||
MessageBox.Show("写入失败", "error", MessageBoxButton.OK, MessageBoxImage.Error); | |||
IsEnable = true; | |||
} | |||
IsEnable = true; | |||
}); | |||
} | |||
@@ -323,5 +295,48 @@ namespace ModbusDemo.VIewModel | |||
.Take(30) // 只取前20条记录 | |||
.ToList(); // 执行查询 | |||
} | |||
/// <summary> | |||
/// 释放资源 | |||
/// </summary> | |||
public void Dispose() | |||
{ | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
/// <summary> | |||
/// 真实调用的方法 | |||
/// </summary> | |||
/// <param name="disposing"></param> | |||
protected virtual void Dispose(bool disposing) | |||
{ | |||
if (_disposed) | |||
return; // 避免重复释放 | |||
// 释放托管资源(仅在手动调用Dispose时执行) | |||
if (disposing) | |||
{ | |||
//_serialPort是单例的,不要手动释放 | |||
// 释放数据库上下文是瞬态的需要手动释放 | |||
if (_modbusDbContext != null) | |||
{ | |||
_modbusDbContext.Dispose(); | |||
_modbusDbContext = null; | |||
} | |||
if (_modbusRTU != null) | |||
{ | |||
_modbusRTU.Dispose(); | |||
_modbusRTU = null; | |||
} | |||
} | |||
_disposed = true; // 标记为已释放 | |||
} | |||
} | |||
} |
@@ -1,6 +1,7 @@ | |||
using ModbusDemo.Device; | |||
using ModbusDemo.Event; | |||
using ModbusDemo.Model; | |||
using ModbusDemo.Uitls; | |||
using Prism.Commands; | |||
using Prism.Events; | |||
using Prism.Mvvm; | |||
@@ -14,8 +15,10 @@ using System.Windows; | |||
namespace ModbusDemo.VIewModel | |||
{ | |||
class RegisterUCViewModel : BindableBase | |||
class RegisterUCViewModel : BindableBase,IDisposable | |||
{ | |||
// 标记是否已释放资源 | |||
private bool _disposed = false; | |||
//事件聚合器 | |||
private readonly IEventAggregator _eventAggregator; | |||
//定义数据库操作类 | |||
@@ -200,32 +203,16 @@ namespace ModbusDemo.VIewModel | |||
/// </summary> | |||
private void ReadRegister() | |||
{ | |||
//将按钮设置为不可点击状态,并解析用户输入的信息 | |||
IsEnable = false; | |||
if (!byte.TryParse(SlaveAddress, out byte slaveAddressValue)) | |||
//检测当前输入的信息是否正确 | |||
var validation = CheckInputUitl.ValidateReadParameters(SlaveAddress, StartAddress, NumberOfPoints); | |||
if (!validation.success) | |||
{ | |||
MessageBox.Show("SlaveAddress 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
if (!ushort.TryParse(StartAddress, out ushort startAddressValue)) | |||
{ | |||
MessageBox.Show("StartAddress 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
if (!ushort.TryParse(NumberOfPoints, out ushort numberOfPointsValue)) | |||
{ | |||
MessageBox.Show("NumberOfPoints 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
//使用子线程进行读取,防止ui线程卡顿 | |||
Task.Run(() => | |||
{ | |||
ushort[] result = _modbusRTU.ReadRegisters(slaveAddressValue, startAddressValue, numberOfPointsValue); | |||
{ IsEnable = false; | |||
ushort[] result = _modbusRTU.ReadRegister(validation.slaveAddress, validation.startAddress, validation.numberOfPoints); | |||
if (result == null) | |||
{ | |||
MessageBox.Show("读取失败", "error", MessageBoxButton.OK, MessageBoxImage.Error); | |||
@@ -257,31 +244,17 @@ namespace ModbusDemo.VIewModel | |||
private void WriteRegister() | |||
{ | |||
//将按钮设置为不可点击状态,并解析用户输入的信息 | |||
IsEnable = false; | |||
if (!byte.TryParse(WriteSlaveAddress, out byte writeSlaveAddressValue)) | |||
{ | |||
MessageBox.Show("WriteSlaveAddress 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
if (!ushort.TryParse(WriteStartAddress, out ushort WriteStartAddressValue)) | |||
//检测当前输入的信息是否正确 | |||
var validation = CheckInputUitl.ValidateWriteParameters(WriteSlaveAddress, WriteStartAddress, WriteData); | |||
if (!validation.success) | |||
{ | |||
MessageBox.Show("WriteStartAddress 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
if (string.IsNullOrEmpty(WriteData)) | |||
{ | |||
MessageBox.Show("WriteData 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||
IsEnable = true; | |||
return; | |||
} | |||
string[] parts = WriteData.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); | |||
// 3. 创建结果数组 | |||
// 创建结果数组 | |||
ushort[] data = new ushort[parts.Length]; | |||
// 4. 遍历并转换每个部分 | |||
// 遍历并转换每个部分 | |||
for (int i = 0; i < parts.Length; i++) | |||
{ | |||
// 尝试解析为ushort | |||
@@ -293,7 +266,7 @@ namespace ModbusDemo.VIewModel | |||
{ | |||
MessageBox.Show($"无法将 '{parts[i]}' 转换为 ushort 类型"); | |||
IsEnable = true; | |||
return; | |||
} | |||
} | |||
@@ -301,10 +274,11 @@ namespace ModbusDemo.VIewModel | |||
//使用子线程来去执行操作,防止ui线程卡顿 | |||
Task.Run(() => | |||
{ | |||
_modbusRTU.WriteRegisters(writeSlaveAddressValue, WriteStartAddressValue, data); | |||
IsEnable = false; | |||
bool issucceed = _modbusRTU.WriteRegister(validation.slaveAddress, validation.startAddress, data); | |||
ModbusLogList = GetOperateRegister(); | |||
ModbusLog modbusLog = ModbusLogList.FirstOrDefault(); | |||
if (modbusLog != null && modbusLog.ResponseData != string.Empty && modbusLog.ResponseData[3] != '8') | |||
if (issucceed) | |||
{ | |||
MessageBox.Show("写入成功"); | |||
IsEnable = true; | |||
@@ -332,5 +306,47 @@ namespace ModbusDemo.VIewModel | |||
.Take(30) // 只取前20条记录 | |||
.ToList(); // 执行查询 | |||
} | |||
/// <summary> | |||
/// 释放资源 | |||
/// </summary> | |||
public void Dispose() | |||
{ | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
/// <summary> | |||
/// 真实调用的方法 | |||
/// </summary> | |||
/// <param name="disposing"></param> | |||
protected virtual void Dispose(bool disposing) | |||
{ | |||
if (_disposed) | |||
return; // 避免重复释放 | |||
// 释放托管资源(仅在手动调用Dispose时执行) | |||
if (disposing) | |||
{ | |||
//_serialPort是单例的,不要手动释放 | |||
// 释放数据库上下文是瞬态的需要手动释放 | |||
if (_modbusDbContext != null) | |||
{ | |||
_modbusDbContext.Dispose(); | |||
_modbusDbContext = null; | |||
} | |||
if (_modbusRTU != null) | |||
{ | |||
_modbusRTU.Dispose(); | |||
_modbusRTU = null; | |||
} | |||
} | |||
_disposed = true; // 标记为已释放 | |||
} | |||
} | |||
} |
@@ -129,17 +129,17 @@ namespace ModbusDemo.VIewModel | |||
_regionManager = regionManager; | |||
_eventAggregator = eventAggregator; | |||
_modbusRTU = modbusRTU; | |||
_checkTimer = new Timer(1000); // 每秒检查一次 | |||
_checkTimer = new Timer(5000); // 每秒检查一次 | |||
_checkTimer.Elapsed += CheckPortStatus; | |||
_checkTimer.AutoReset = true; | |||
} | |||
private void CheckPortStatus(object sender, ElapsedEventArgs e) | |||
{ | |||
bool isConnected = SerialPort.GetPortNames().Contains(PortName); | |||
//bool isConnected = SerialPort.GetPortNames().Contains(PortName); | |||
byte[] bytes = new byte[] { 0x01, 0x01, 0x01, 0x2C, 0x00, 0x01, 0x3D, 0xFF }; | |||
byte[] response = _modbusRTU.SendMessage(bytes); | |||
if (!isConnected || response ==null) | |||
if (response ==null) | |||
{ | |||
_checkTimer.Stop(); | |||
_serialPort.Close(); | |||
@@ -14,7 +14,7 @@ namespace ModbusTest | |||
{ | |||
public class ModbusRTUTest | |||
{ | |||
//设计缺陷,应该使用适配器模式重写SerialPort | |||
private SerialPort _serialport; | |||
private Mock<ModbusDbContext> _mockDbContext; | |||
private ModbusRTU _modbusRtu; | |||
@@ -38,6 +38,7 @@ namespace ModbusTest | |||
{ | |||
// 确保在测试结束后释放资源 | |||
_serialport.Dispose(); | |||
_modbusRtu.Dispose(); | |||
} | |||
/// <summary> | |||
/// 测试线圈读取 | |||
@@ -100,7 +101,7 @@ namespace ModbusTest | |||
{ | |||
ushort[] data = new ushort[5]; | |||
Array.Fill(data, (ushort)0); | |||
var expected = _modbusRtu.ReadRegisters(1, 300, 5); | |||
var expected = _modbusRtu.ReadRegister(1, 300, 5); | |||
CollectionAssert.AreEqual(data, expected); | |||
} | |||
@@ -112,7 +113,7 @@ namespace ModbusTest | |||
ushort[] data = new ushort[5]; | |||
Array.Fill(data, (ushort)1); | |||
var expected = _modbusRtu.ReadRegisters(1, 305, 5); | |||
var expected = _modbusRtu.ReadRegister(1, 305, 5); | |||
CollectionAssert.AreEqual(data, expected); | |||
} | |||
@@ -126,8 +127,8 @@ namespace ModbusTest | |||
ushort[] data = new ushort[5]; | |||
Array.Fill(data, (ushort)1); | |||
_modbusRtu.WriteRegisters(1, 310, data); | |||
var expected = _modbusRtu.ReadRegisters(1, 310, 5); | |||
_modbusRtu.WriteRegister(1, 310, data); | |||
var expected = _modbusRtu.ReadRegister(1, 310, 5); | |||
CollectionAssert.AreEqual(data, expected); | |||
} | |||
@@ -138,11 +139,58 @@ namespace ModbusTest | |||
ushort[] data = new ushort[5]; | |||
Array.Fill(data, (ushort)2); | |||
_modbusRtu.WriteRegisters(1, 315, data); | |||
var expected = _modbusRtu.ReadRegisters(1, 315, 5); | |||
_modbusRtu.WriteRegister(1, 315, data); | |||
var expected = _modbusRtu.ReadRegister(1, 315, 5); | |||
CollectionAssert.AreEqual(data, expected); | |||
} | |||
[Test] | |||
public void CRC1() | |||
{ | |||
byte[] data = { 0x48, 0x65, 0x6C, 0x6C, 0x6F }; | |||
int length = data.Length; | |||
byte[] crc = ModbusRTU.CalculateCRC(data, length); | |||
// Assert | |||
Assert.That(crc[0], Is.EqualTo(0x77)); | |||
Assert.That(crc[1], Is.EqualTo(0xF3)); | |||
} | |||
[Test] | |||
public void CRC2() | |||
{ | |||
byte[] data = { 0x49, 0x66, 0x51, 0x6C, 0x7F }; | |||
int length = data.Length; | |||
byte[] crc = ModbusRTU.CalculateCRC(data, length); | |||
// Assert | |||
Assert.That(crc[0], Is.EqualTo(0xDA)); | |||
Assert.That(crc[1], Is.EqualTo(0x77)); | |||
} | |||
[Test] | |||
public void CRC3() | |||
{ | |||
byte[] data = { 0x01, 0x90, 0x03, 0x0C, 0x01 }; | |||
bool isTrue = ModbusRTU.ValidateCRC(data); | |||
// Assert | |||
Assert.IsTrue(isTrue); | |||
} | |||
[Test] | |||
public void CRC4() | |||
{ | |||
byte[] data = { 0x01, 0x90, 0x03, 0x0C, 0x00 }; | |||
bool isFalse = ModbusRTU.ValidateCRC(data); | |||
// Assert | |||
Assert.IsFalse(isFalse); | |||
} | |||
} | |||
///// <summary> | |||
///// 解析线圈的返回值 | |||
///// </summary> | |||
@@ -197,4 +245,4 @@ namespace ModbusTest | |||
//} | |||
} | |||
} | |||