Porównaj commity

...

6 Commity

28 zmienionych plików z 1286 dodań i 619 usunięć
  1. +38
    -10
      ModbusDemo/App.xaml.cs
  2. +4
    -4
      ModbusDemo/Device/IModbusRTU.cs
  3. +45
    -0
      ModbusDemo/Device/IStreamResource.cs
  4. +407
    -371
      ModbusDemo/Device/ModbusRTU.cs
  5. +71
    -0
      ModbusDemo/Device/SerialPortAdapter.cs
  6. +51
    -0
      ModbusDemo/ErrorCode/ErrorCode.cs
  7. +14
    -0
      ModbusDemo/Event/SerialPortSettingsChangedEvent .cs
  8. +10
    -2
      ModbusDemo/Extension/EnumBindingSourceExtension.cs
  9. +2
    -2
      ModbusDemo/MainWindow.xaml
  10. +20
    -1
      ModbusDemo/ModbusDemo.csproj
  11. BIN
      ModbusDemo/ModbusRTU_64x64.ico
  12. +7
    -1
      ModbusDemo/Model/ModbusDbContext.cs
  13. +4
    -0
      ModbusDemo/Model/ModbusLog.cs
  14. +16
    -6
      ModbusDemo/Uitls/CheckData.cs
  15. +82
    -0
      ModbusDemo/Uitls/CheckInputUitl.cs
  16. +27
    -0
      ModbusDemo/Uitls/DatabaseHelper.cs
  17. +30
    -3
      ModbusDemo/VIew/AttachUC.xaml
  18. +22
    -13
      ModbusDemo/VIew/CoilUC.xaml
  19. +19
    -9
      ModbusDemo/VIew/RegisterUC.xaml
  20. +30
    -2
      ModbusDemo/VIew/SettingsUC.xaml
  21. +8
    -63
      ModbusDemo/VIewModel/AttachUCViewModel.cs
  22. +90
    -49
      ModbusDemo/VIewModel/CoilUCViewModel.cs
  23. +1
    -1
      ModbusDemo/VIewModel/MainWindowViewModel.cs
  24. +87
    -49
      ModbusDemo/VIewModel/RegisterUCViewModel.cs
  25. +70
    -14
      ModbusDemo/VIewModel/SettingsUCViewModel.cs
  26. +1
    -1
      ModbusDemo/appsettings.json
  27. +124
    -17
      ModbusTest/ModbusRTUTest.cs
  28. +6
    -1
      ModbusTest/UnitTest1.cs

+ 38
- 10
ModbusDemo/App.xaml.cs Wyświetl plik

@@ -12,6 +12,8 @@ using System.Data;
using System.DirectoryServices;
using System.IO;
using System.IO.Ports;
using System.Security.Cryptography;
using System.Text;
using System.Windows;

namespace ModbusDemo
@@ -56,13 +58,14 @@ namespace ModbusDemo
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();

// 2. 配置 DbContextOptions
// 2.解密连接字符串
string decryptedConnection = Decrypt(configuration.GetConnectionString("ConnStr"));
// 3. 配置 DbContextOptions
var options = new DbContextOptionsBuilder<ModbusDbContext>()
.UseSqlServer(configuration.GetConnectionString("ConnStr"))
.UseSqlServer(decryptedConnection)
.Options;

// 3. 注册 DbContext
// 4. 注册 DbContext
containerRegistry.Register<ModbusDbContext>(() => new ModbusDbContext(options));
}
/// <summary>
@@ -74,15 +77,40 @@ namespace ModbusDemo
mainWindowViewModel!.DefultNaigation();
base.OnInitialized();
}
//硬编码
private static readonly byte[] Key = Encoding.UTF8.GetBytes("8Jn3pQ7sV9y$B&E)"); // 16字节,AES-128
private static readonly byte[] IV = Encoding.UTF8.GetBytes("2r5u8x/A?D*G-KaP"); // 16字节

/// <summary>
/// 解密字符串
/// </summary>
/// <param name="encryptedText">加密后的Base64字符串</param>
/// <returns>解密后的原始字符串</returns>
public static string Decrypt(string encryptedText)
{
if (string.IsNullOrEmpty(encryptedText))
return string.Empty;

using (Aes aes = Aes.Create())
{
aes.Key = Key;
aes.IV = IV;

// 创建解密器
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

//private SerialPort CreateSerialPort()
//{
// SerialPort serialPort = new SerialPort();
// return serialPort;
//}
using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(encryptedText)))
{
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (StreamReader sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
}

}

+ 4
- 4
ModbusDemo/Device/IModbusRTU.cs Wyświetl plik

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

}
}

+ 45
- 0
ModbusDemo/Device/IStreamResource.cs Wyświetl plik

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ModbusDemo.Device
{
public interface IStreamResource : IDisposable
{
int InfiniteTimeout { get; }

/// <summary>
/// Gets or sets the number of milliseconds before a timeout occurs when a read operation does not finish.
/// </summary>
int ReadTimeout { get; set; }

/// <summary>
/// Gets or sets the number of milliseconds before a timeout occurs when a write operation does not finish.
/// </summary>
int WriteTimeout { get; set; }

/// <summary>
/// Purges the receive buffer.
/// </summary>
void DiscardInBuffer();

/// <summary>
/// Reads a number of bytes from the input buffer and writes those bytes into a byte array at the specified offset.
/// </summary>
/// <param name="buffer">The byte array to write the input to.</param>
/// <param name="offset">The offset in the buffer array to begin writing.</param>
/// <param name="count">The number of bytes to read.</param>
/// <returns>The number of bytes read.</returns>
int Read(byte[] buffer, int offset, int count);

/// <summary>
/// Writes a specified number of bytes to the port from an output buffer, starting at the specified offset.
/// </summary>
/// <param name="buffer">The byte array that contains the data to write to the port.</param>
/// <param name="offset">The offset in the buffer array to begin writing.</param>
/// <param name="count">The number of bytes to write.</param>
void Write(byte[] buffer, int offset, int count);
}
}

+ 407
- 371
ModbusDemo/Device/ModbusRTU.cs Wyświetl plik

@@ -1,28 +1,37 @@
using ModbusDemo.Model;
using DryIoc;
using ModbusDemo.Model;
using ModbusDemo.Uitls;
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;


namespace ModbusDemo.Device
{
public class ModbusRTU : IModbusRTU
{
// 标记是否已释放资源
private bool _disposed = false;
//串口
private SerialPort _serialPort;
//用于操作数据库
private ModbusDbContext _modbusDbContext;
//TODO,修改
//private SerialPortAdapter _portAdapter;

public ModbusRTU(SerialPort serialPort, ModbusDbContext modbusDbContext)
{
_serialPort = serialPort;
_modbusDbContext = modbusDbContext;
//_portAdapter = new SerialPortAdapter(serialPort);
}

/// <summary>
/// 用来发送读写线圈的指令
/// </summary>
@@ -39,463 +48,310 @@ namespace ModbusDemo.Device
MessageBox.Show("串口没有链接,请先链接");
return resultValue;
}
List<byte> sendByteList = new List<byte>();
//设置从站地址
sendByteList.Add(slaveAddress);
//设置功能码
sendByteList.Add(0x01);
//设置起始地址的高位和低位
sendByteList.Add(BitConverter.GetBytes(startAddress)[1]);
sendByteList.Add(BitConverter.GetBytes(startAddress)[0]);
//设置读取几个线圈的高位和低位
sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[1]);
sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[0]);
//获取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;
byte[] response = null;
List<byte> responseData = new List<byte>();
_serialPort.Write(sendByte, 0, sendByte.Length);
while (attempt < maxRetries)
//构建发送报文
byte[] sendMessage = ConstructMessage<ushort>(0x01, slaveAddress, startAddress, numberOfPoints, ProcessReadData);
//发送报文
byte[] responseMessage = SendMessage(sendMessage);
//判断发送过来的字节是否正确
if (CheckResponse(responseMessage))
{
try
{
//使用时间去轮询查询数据
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资源
Task.Delay(20);
}
response = responseData.ToArray();
if (response[0] == 0x00)
{
continue;
}
resultValue = ParseCoilresponse(sendByte, response, numberOfPoints);
return resultValue;
}
catch (Exception)
{

attempt++;
}

// 移除前3个字节和后2个校验码字节,并反转数组
var processedBytes = responseMessage
.Skip(3) // 移除前3个字节
.Take(responseMessage.Count() - 5) // 保留中间部分 (总长度-5)
.Reverse() // 反转字节顺序
.ToList();

// 将每个字节转换为8位二进制字符串,不足8位时左边补0
string binaryString = string.Concat(
processedBytes.Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))
);

// 反转二进制字符串中的所有位,并转换为布尔数组
resultValue = binaryString
.Reverse() // 反转所有位的顺序
.Take(numberOfPoints) // 取指定数量的位
.Select(c => c == '1') // 将字符'1'转换为true,其他转换为false
.ToArray();
}
//将操作失败的指令,插入数据库
string RequestStr = ByteArrayToString(sendByte);

ModbusLog m = new ModbusLog();
m.OperationType = "读线圈";
m.ResponseData = "";
m.RequestData = RequestStr;
_modbusDbContext.Add(m);
_modbusDbContext.SaveChanges();
//将数据插入到数据库
InsertDatabase(sendMessage, responseMessage, "读线圈");
return resultValue;
}

/// <summary>
/// 用来解析返回的数据
/// </summary>
/// <param name="sendByte"></param>
/// <param name="response"></param>
/// <param name="numberOfPoints"></param>
/// <returns></returns>
public bool[] ParseCoilresponse(byte[] sendByte, byte[] response, ushort numberOfPoints)
{
//将操作的指令,插入数据库
string RequestStr = ByteArrayToString(sendByte);
string responseStr = ByteArrayToString(response);
ModbusLog m = new ModbusLog();
m.OperationType = "读线圈";
m.ResponseData = responseStr;
m.RequestData = RequestStr;
_modbusDbContext.Add(m);
_modbusDbContext.SaveChanges();


//判断发送回来的数据是否正确
if (CheckData.CheckResponse(response))
{
List<byte> responseList = new List<byte>(response);
//移除前两位
responseList.RemoveRange(0, 3);
//移除后两位校验码
responseList.RemoveRange(responseList.Count - 2, 2);
responseList.Reverse();
//数组反转之后,转为二进制字符
List<string> StringList = responseList.Select(m => Convert.ToString(m, 2)).ToList();

var result = "";

foreach (var item in StringList)
{
result += item.PadLeft(8, '0');
}
//先将字符串转为数组,再反转,再转为字节数组
char[] chars = result.ToArray().Reverse<char>().ToArray();
bool[] ultimately = new bool[numberOfPoints];
for (int i = 0; i < numberOfPoints; i++)
{
if (chars[i] == '1')
{
ultimately[i] = true;
}
else
{
ultimately[i] = false;
}
}
return ultimately;
}
else
{
return null;
}



}
/// <summary>
/// 写入多个线圈操作
/// </summary>
/// <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;
}
List<byte> sendByteList = new List<byte>();
//设置从站地址
sendByteList.Add(slaveAddress);
//设置功能码
sendByteList.Add(0x0F);
//设置起始地址的高位和低位
sendByteList.Add(BitConverter.GetBytes(startAddress)[1]);
sendByteList.Add(BitConverter.GetBytes(startAddress)[0]);
//设置写入几个线圈
sendByteList.Add(BitConverter.GetBytes(data.Length)[1]);
sendByteList.Add(BitConverter.GetBytes(data.Length)[0]);
//构建报文
byte[] sendMessage = ConstructMessage<bool[]>(0x0F, slaveAddress, startAddress, data, ProcessCoilData);
//发送报文
byte[] responseMessage = SendMessage(sendMessage);

// 计算字节数
int byteCount = (data.Length + 7) / 8;
sendByteList.Add((byte)byteCount);
//将数据插入到数据库
InsertDatabase(sendMessage, responseMessage, "写线圈");
//判断发送回来的数据是否正确
return CheckResponse(responseMessage);
}

// 初始化字节数组
byte[] coilBytes = new byte[byteCount];

// 把bool数组转换为字节
for (int i = 0; i < data.Length; i++)
public ushort[] ReadRegister(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
{
ushort[] resultValue = null;
if (!_serialPort.IsOpen)
{
int byteIndex = i / 8;
int bitIndex = i % 8;
if (data[i])
{
coilBytes[byteIndex] |= (byte)(1 << bitIndex);
}
MessageBox.Show("串口没有链接,请先链接");
return resultValue;
}
sendByteList.AddRange(coilBytes);

//获取CRC校验码
byte[] getCRC = sendByteList.ToArray();
byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length);
sendByteList.Add(resultCRC[0]);
sendByteList.Add(resultCRC[1]);
byte[] sendByte = sendByteList.ToArray();
//构建发送报文
byte[] sendMessage = ConstructMessage<ushort>(0x03, slaveAddress, startAddress, numberOfPoints, ProcessReadData);
//发送报文
byte[] responseMessage = SendMessage(sendMessage);

int maxRetries = 3; // 最大重试次数
int attempt = 0;
byte[] response = null;
List<byte> responseData = new List<byte>();
_serialPort.Write(sendByte, 0, sendByte.Length);
while (attempt < maxRetries)
//将数据插入到数据库
InsertDatabase(sendMessage, responseMessage, "读寄存器");
//判断发送回来的数据是否正确
if (CheckResponse(responseMessage))
{
try
ushort[] registers = new ushort[numberOfPoints];
for (int i = 0; i < numberOfPoints; i++)
{
//使用时间去轮询查询数据
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资源
Task.Delay(20);
}
response = responseData.ToArray();
if (response[0] != 0x00)
{
break;
}
int pos = 3 + i * 2;
// 大端序转换 (高位在前)
registers[i] = (ushort)((responseMessage[pos] << 8) | responseMessage[pos + 1]);
}
catch (Exception)
{

attempt++;
}
resultValue = registers;
}

return resultValue;
}

/// <summary>
/// 写寄存器
/// </summary>
/// <param name="slaveAddress"></param>
/// <param name="startAddress"></param>
/// <param name="data"></param>
public bool WriteRegister(byte slaveAddress, ushort startAddress, ushort[] data)
{
if (!_serialPort.IsOpen)
{
MessageBox.Show("串口没有链接,请先链接");
return false;
}
//将操作的指令,插入数据库
string RequestStr = ByteArrayToString(sendByte);
string responseStr = ByteArrayToString(response);
ModbusLog m = new ModbusLog();
m.OperationType = "写线圈";
m.ResponseData = responseStr;
m.RequestData = RequestStr;
_modbusDbContext.Add(m);
_modbusDbContext.SaveChanges();
//构建报文
byte[] sendMessage = ConstructMessage<ushort[]>(0x10, slaveAddress, startAddress, data, ProcessRegisterData);
//发送报文
byte[] responseMessage = SendMessage(sendMessage);

//将数据插入到数据库
InsertDatabase(sendMessage, responseMessage, "写寄存器");


//判断发送回来的数据是否正确
if (CheckData.CheckResponse(response))
{
return CheckResponse(responseMessage);

}

}

///
// 定义委托处理不同数据类型的转换
private delegate void DataProcessor<T>(List<byte> sendByteList, T data);
/// <summary>
/// 读寄存器操作
/// 生成发送报文
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="Function"></param>
/// <param name="slaveAddress"></param>
/// <param name="startAddress"></param>
/// <param name="numberOfPoints"></param>
/// <param name="data"></param>
/// <param name="processor"></param>
/// <returns></returns>
public ushort[] ReadRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
private byte[] ConstructMessage<T>(byte Function, byte slaveAddress, ushort startAddress, T data, DataProcessor<T> processor)
{
ushort[] resultValue = null;
if (!_serialPort.IsOpen)
byte[] resultValue = null;
var sendByteList = new List<byte>
{
MessageBox.Show("串口没有链接,请先链接");
return resultValue;
}
List<byte> sendByteList = new List<byte>();
//设置从站地址
sendByteList.Add(slaveAddress);
//设置功能码
sendByteList.Add(0x03);
//设置起始地址的高位和低位
sendByteList.Add(BitConverter.GetBytes(startAddress)[1]);
sendByteList.Add(BitConverter.GetBytes(startAddress)[0]);
//设置读取几个线圈的高位和低位
sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[1]);
sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[0]);
slaveAddress, // 从站地址
Function // 功能码
};

// 添加起始地址(高字节在前,低字节在后)
sendByteList.AddRange(BitConverter.GetBytes(startAddress).Reverse());
// 调用处理器处理具体数据
processor(sendByteList, data);

//获取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]);

byte[] sendByte = sendByteList.ToArray();
int maxRetries = 3; // 最大重试次数
int attempt = 0;
List<byte> responseData = new List<byte>();
byte[] response = null;
_serialPort.Write(sendByte, 0, sendByte.Length);
while (attempt < maxRetries)
{
try
{

//使用时间去轮询查询数据
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资源
Task.Delay(20);
}
response = responseData.ToArray();
if (response[0] == 0x00)
{
continue;
}
resultValue = ParseRegistersresponse(sendByte, response, numberOfPoints);
return resultValue;
}
catch (Exception)
{

attempt++;
}

}
//将操作失败的指令,插入数据库
string RequestStr = ByteArrayToString(sendByte);

ModbusLog m = new ModbusLog();
m.OperationType = "读寄存器";
m.ResponseData = "";
m.RequestData = RequestStr;
_modbusDbContext.Add(m);
_modbusDbContext.SaveChanges();
resultValue = sendByteList.ToArray();
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)
/// <param name="sendByteList"></param>
/// <param name="data"></param>
private void ProcessCoilData(List<byte> sendByteList, bool[] data)
{
//将操作的指令,插入数据库
string RequestStr = ByteArrayToString(sendByte);
string responseStr = ByteArrayToString(response);
ModbusLog m = new ModbusLog();
m.OperationType = "读寄存器";
m.ResponseData = responseStr;
m.RequestData = responseStr;
_modbusDbContext.Add(m);
_modbusDbContext.SaveChanges();
// 添加线圈数量(高字节在前,低字节在后)
ushort coilCount = (ushort)data.Length;
sendByteList.AddRange(BitConverter.GetBytes(coilCount).Reverse());

//判断发送回来的数据是否正确
if (CheckData.CheckResponse(response))
// 计算所需字节数(每8个线圈占1字节)
int byteCount = (coilCount + 7) / 8;
sendByteList.Add((byte)byteCount);

// 将布尔数组转换为字节数组(每个位表示一个线圈状态)
byte[] result = new byte[byteCount];

for (int i = 0; i < data.Length; i++)
{
ushort[] registers = new ushort[numberOfPoints];
for (int i = 0; i < numberOfPoints; i++)
if (data[i])
{
int pos = 3 + i * 2;
// 大端序转换 (高位在前)
registers[i] = (ushort)((response[pos] << 8) | response[pos + 1]);
int byteIndex = i / 8;
int bitIndex = i % 8;
result[byteIndex] |= (byte)(1 << bitIndex);
}

return registers;
}
else
{
return null;
}
sendByteList.AddRange(result);
}




/// <summary>
/// 处理读取几个线圈或者寄存器的转换
/// </summary>
/// <param name="sendByteList"></param>
/// <param name="data"></param>
private void ProcessReadData(List<byte> sendByteList, ushort data)
{
//设置读取几个线圈的高位和低位
sendByteList.AddRange(BitConverter.GetBytes(data).Reverse());
}
/// <summary>
/// 写寄存器
/// 解析写寄存器的值
/// </summary>
/// <param name="slaveAddress"></param>
/// <param name="startAddress"></param>
/// <param name="sendByteList"></param>
/// <param name="data"></param>
public void WriteRegisters(byte slaveAddress, ushort startAddress, ushort[] data)
private void ProcessRegisterData(List<byte> sendByteList, ushort[] data)
{
if (!_serialPort.IsOpen)
{
MessageBox.Show("串口没有链接,请先链接");
return;
}
List<byte> sendByteList = new List<byte>();
//设置从站地址
sendByteList.Add(slaveAddress);
//设置功能码
sendByteList.Add(0x10);
//设置起始地址的高位和低位
sendByteList.Add(BitConverter.GetBytes(startAddress)[1]);
sendByteList.Add(BitConverter.GetBytes(startAddress)[0]);
//设置写入几个线圈
sendByteList.Add(BitConverter.GetBytes(data.Length)[1]);
sendByteList.Add(BitConverter.GetBytes(data.Length)[0]);
// 添加寄存器数量
sendByteList.AddRange(BitConverter.GetBytes((ushort)data.Length).Reverse());

// 计算字节数
int byteCount = (data.Length + 7) / 8;
int byteCount = data.Length * 2;
sendByteList.Add((byte)byteCount);

// 添加寄存器数据
foreach (ushort value in data)
{
byte[] valueBytes = BitConverter.GetBytes(value);
// 大端序:高字节在前,低字节在后
sendByteList.Add(valueBytes[1]);
sendByteList.Add(valueBytes[0]);
sendByteList.AddRange(BitConverter.GetBytes(value).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();
// 自定义锁
private static readonly object _Lock = new object();

/// <summary>
/// 将构建好的报文发送出去
/// </summary>
/// <param name="sendByte"></param>
/// <returns></returns>
public byte[] SendMessage(byte[] sendByte)
{

int maxRetries = 3; // 最大重试次数
int attempt = 0;
byte[] response = null;
List<byte> responseData = new List<byte>();
_serialPort.Write(sendByte, 0, sendByte.Length);
while (attempt < maxRetries)
{
try
lock (_Lock)
{
//使用时间去轮询查询数据
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)
{
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();
}
response = responseData.ToArray();
if (response.Length == 0)
{
byte[] buffer = new byte[bytesToRead];
int bytesRead = _serialPort.Read(buffer, 0, bytesToRead);
responseData.AddRange(buffer.Take(bytesRead));
attempt++;
continue;
}
//延迟20毫秒,节约cpu资源
Task.Delay(20);
return response;
}
response = responseData.ToArray();
if (response[0] != 0x00)
catch (Exception)
{
break;
}
}
catch (Exception)
{
attempt++;
finally
{

attempt++;
}
}
}
return null;
}

//将操作的指令,插入数据库
string RequestStr = ByteArrayToString(sendByte);
string responseStr = ByteArrayToString(response);
ModbusLog m = new ModbusLog();
m.OperationType = "写寄存器";
m.ResponseData = responseStr;
m.RequestData = RequestStr;
_modbusDbContext.Add(m);
_modbusDbContext.SaveChanges();

//判断发送回来的数据是否正确
CheckData.CheckResponse(response);

/// <summary>
/// 将这次报文信息插入到数据库中
/// </summary>
/// <param name="sendByte"></param>
/// <param name="response"></param>
private void InsertDatabase(byte[] sendByte, byte[] response, string operationType)
{
try
{
//将操作的指令,插入数据库
string RequestStr = ByteArrayToString(sendByte);
string responseStr = ByteArrayToString(response);
ModbusLog modbusLog = new ModbusLog();
modbusLog.OperationType = operationType;
modbusLog.ResponseData = responseStr;
modbusLog.RequestData = RequestStr;
_modbusDbContext.Add(modbusLog);
_modbusDbContext.SaveChanges();
}
catch (Exception)
{

MessageBox.Show(ErrorCode.ErrorCode.DatabaseException.ToString());
}
}

/// <summary>
/// 将数组转换为字符串
/// </summary>
@@ -506,7 +362,7 @@ namespace ModbusDemo.Device
if (byteArray == null || byteArray.Length == 0)
return string.Empty;

System.Text.StringBuilder sb = new System.Text.StringBuilder();
StringBuilder sb = new StringBuilder();

// 处理第一个元素(无前导空格)
sb.Append(byteArray[0].ToString("X2"));
@@ -520,5 +376,185 @@ namespace ModbusDemo.Device

return sb.ToString();
}


/// <summary>
/// 检测返回的数据格式是否正确
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
public static bool CheckResponse(byte[] response)
{
if (response == null || response.Length == 0)
{
MessageBox.Show("返回数据为空", "error", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
// 检查数组长度是否足够
if (response.Length > 3)
{
byte secondByte = response[1]; // 获取第二个字节(索引为1)

// 使用掩码0xF0获取高4位,并右移4位
int highNibble = (secondByte & 0xF0) >> 4;

// 判断高4位是否等于8
if (highNibble == 8)
{
var error = ErrorCode.ErrorCode.FromByte(response[2]);

MessageBox.Show(error.ToString());
return false;
}
}
else
{
return false;
}


if (!ValidateCRC(response))
{
MessageBox.Show(ErrorCode.ErrorCode.CrcCheckError.ToString());
return false;
}

return true;
}


//查表法计算CRC
private static readonly byte[] aucCRCHi = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40
};
private static readonly byte[] aucCRCLo = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
0x41, 0x81, 0x80, 0x40
};

/// <summary>
/// CRC校验
/// </summary>
/// <param name="pucFrame">字节数组</param>
/// <param name="usLen">验证长度</param>
/// <returns>2个字节</returns>
public static byte[] CalculateCRC(byte[] pucFrame, int usLen)
{
int i = 0;
byte[] res = new byte[2] { 0xFF, 0xFF };
ushort iIndex;
while (usLen-- > 0)
{
iIndex = (ushort)(res[0] ^ pucFrame[i++]);
res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
res[1] = aucCRCLo[iIndex];
}
return res;
}
/// <summary>
/// 用来计算CRC是否合理
/// </summary>
/// <param name="fullFrame"></param>
/// <returns></returns>
public static bool ValidateCRC(byte[] fullFrame)
{
// 报文必须至少包含2个字节的CRC校验码
if (fullFrame.Length < 2)
return false;

// 计算数据部分的长度(排除最后2个CRC字节)
int dataLength = fullFrame.Length - 2;

// 提取报文中包含的CRC校验码(传输顺序:低字节在前,高字节在后)
byte receivedCrcLo = fullFrame[fullFrame.Length - 2];
byte receivedCrcHi = fullFrame[fullFrame.Length - 1];

// 计算数据部分的CRC(返回顺序:高字节在前,低字节在后)
byte[] calculatedCrc = CalculateCRC(fullFrame, dataLength);

// 比较计算出的CRC和接收到的CRC
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; // 标记为已释放
}

}
}

+ 71
- 0
ModbusDemo/Device/SerialPortAdapter.cs Wyświetl plik

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ModbusDemo.Device
{
public class SerialPortAdapter:IStreamResource
{
private string NewLine = Environment.NewLine;
private SerialPort _serialPort;

public SerialPortAdapter(SerialPort serialPort)
{
Debug.Assert(serialPort != null, "Argument serialPort cannot be null.");

_serialPort = serialPort;
_serialPort.NewLine = NewLine;
}

public int InfiniteTimeout
{
get { return SerialPort.InfiniteTimeout; }
}

public int ReadTimeout
{
get { return _serialPort.ReadTimeout; }
set { _serialPort.ReadTimeout = value; }
}

public int WriteTimeout
{
get { return _serialPort.WriteTimeout; }
set { _serialPort.WriteTimeout = value; }
}

public void DiscardInBuffer()
{
_serialPort.DiscardInBuffer();
}

public int Read(byte[] buffer, int offset, int count)
{
return _serialPort.Read(buffer, offset, count);
}

public void Write(byte[] buffer, int offset, int count)
{
_serialPort.Write(buffer, offset, count);
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_serialPort?.Dispose();
_serialPort = null;
}
}
}
}

+ 51
- 0
ModbusDemo/ErrorCode/ErrorCode.cs Wyświetl plik

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ModbusDemo.ErrorCode
{
public class ErrorCode
{

// 静态列表:存储所有错误码
public static readonly List<ErrorCode> AllErrors = new List<ErrorCode>();

// 错误码值
public byte Code { get; }

// 错误描述
public string Description { get; }

// 私有构造函数,只能通过静态方法创建错误码
private ErrorCode(byte code, string description)
{
Code = code;
Description = description;

// 自动添加到错误码列表
AllErrors.Add(this);
}

// 重写 ToString 方法
public override string ToString() => $"Error 0x{Code:X2}: {Description}";


// 根据错误码值查找错误码
public static ErrorCode FromByte(byte code) => AllErrors.FirstOrDefault(e => e.Code == code);

// 定义所有错误码
public static readonly ErrorCode FunctionCodeError = new ErrorCode(0x01, "功能码错误");
public static readonly ErrorCode DomainStartAddressError = new ErrorCode(0x02, "域数据首地址错误");
public static readonly ErrorCode DataContentError = new ErrorCode(0x03, "数据内容错误");
public static readonly ErrorCode DataTypeError = new ErrorCode(0x10, "数据类型错误");
public static readonly ErrorCode DomainDataCountError = new ErrorCode(0x11, "域数据个数错误");
public static readonly ErrorCode DataCountMismatch = new ErrorCode(0x12, "数据个数与内部区域数据个数不符");
public static readonly ErrorCode WriteOperationDenied = new ErrorCode(0x13, "不能进行写操作");
public static readonly ErrorCode CrcCheckError = new ErrorCode(0x14, "CRC校验错误");
public static readonly ErrorCode DatabaseException = new ErrorCode(0x15, "数据库异常");
}

}

+ 14
- 0
ModbusDemo/Event/SerialPortSettingsChangedEvent .cs Wyświetl plik

@@ -0,0 +1,14 @@
using Prism.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ModbusDemo.Event
{
public class SerialPortSettingsChangedEvent: PubSubEvent
{

}
}

+ 10
- 2
ModbusDemo/Extension/EnumBindingSourceExtension.cs Wyświetl plik

@@ -4,7 +4,8 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Markup;

using System;
using System.Reflection;
namespace ModbusDemo.Extension
{
/// <summary>
@@ -12,6 +13,7 @@ namespace ModbusDemo.Extension
/// </summary>
class EnumBindingSourceExtension : MarkupExtension
{
//枚举字段
private Type _enumType;

public Type EnumType
@@ -39,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)


+ 2
- 2
ModbusDemo/MainWindow.xaml Wyświetl plik

@@ -10,8 +10,8 @@
xmlns:prism="http://prismlibrary.com/"
xmlns:viewmodel="clr-namespace:ModbusDemo.VIewModel"
Title="MainWindow"
Width="900"
Height="600"
Width="1000"
Height="800"
d:DataContext="{d:DesignInstance Type=viewmodel:MainWindowViewModel,
IsDesignTimeCreatable=True}"
WindowStartupLocation="CenterScreen"


+ 20
- 1
ModbusDemo/ModbusDemo.csproj Wyświetl plik

@@ -6,14 +6,29 @@
<Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<ApplicationIcon>ModbusRTU_64x64.ico</ApplicationIcon>
</PropertyGroup>

<ItemGroup>
<Compile Remove="Device\IStreamResource.cs" />
<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>
<Content Include="ModbusRTU_64x64.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="MaterialDesignThemes" Version="5.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.27" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.7" />
<PackageReference Include="NModbus4" Version="2.1.0" />
<PackageReference Include="Prism.DryIoc" Version="8.1.97" />
<PackageReference Include="System.IO.Ports" Version="9.0.7" />
</ItemGroup>
@@ -24,4 +39,8 @@
</None>
</ItemGroup>

<ItemGroup>
<Folder Include="bin\Debug\net8.0-windows\" />
</ItemGroup>

</Project>

BIN
ModbusDemo/ModbusRTU_64x64.ico Wyświetl plik

Przed Po

+ 7
- 1
ModbusDemo/Model/ModbusDbContext.cs Wyświetl plik

@@ -7,11 +7,17 @@ using System.Threading.Tasks;

namespace ModbusDemo.Model
{
/// <summary>
/// 操作数据库的上下文
/// </summary>
public class ModbusDbContext:DbContext
{
protected ModbusDbContext()

public ModbusDbContext()
{
}

public ModbusDbContext(DbContextOptions<ModbusDbContext> options) :base(options)
{


+ 4
- 0
ModbusDemo/Model/ModbusLog.cs Wyświetl plik

@@ -7,6 +7,9 @@ using System.Threading.Tasks;

namespace ModbusDemo.Model
{
/// <summary>
/// 表的映射
/// </summary>
public class ModbusLog
{
//数据库主键
@@ -17,6 +20,7 @@ namespace ModbusDemo.Model
//请求的字节信息
public string RequestData { get; set; }
//返回的字节信息
public string ResponseData { get; set; }
//操作的时间


+ 16
- 6
ModbusDemo/Uitls/CheckData.cs Wyświetl plik

@@ -4,21 +4,25 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using ModbusDemo.ErrorCode;
namespace ModbusDemo.Uitls
{
public static class CheckData
{

/// <summary>
/// 检测返回的数据格式是否正确
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
public static bool CheckResponse(byte[] response)
{
if (response.Length == 0)
if (response == null || response.Length == 0)
{
MessageBox.Show("返回数据为空", "error", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
// 检查数组长度是否足够
if (response.Length > 1)
if (response.Length > 3)
{
byte secondByte = response[1]; // 获取第二个字节(索引为1)

@@ -28,15 +32,21 @@ namespace ModbusDemo.Uitls
// 判断高4位是否等于8
if (highNibble == 8)
{
MessageBox.Show($"数据错误:错误码为:0x{response[2]:X2}");
var error = ErrorCode.ErrorCode.FromByte(response[2]);
MessageBox.Show(error.ToString());
return false;
}
}
else
{
return false;
}

if (!CRCUitl.ValidateCRC(response))
{
MessageBox.Show("0x14:CRC校验错误");
MessageBox.Show(ErrorCode.ErrorCode.CrcCheckError.ToString());
return false;
}



+ 82
- 0
ModbusDemo/Uitls/CheckInputUitl.cs Wyświetl plik

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

+ 27
- 0
ModbusDemo/Uitls/DatabaseHelper.cs Wyświetl plik

@@ -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(); // 执行查询
//}
}
}

+ 30
- 3
ModbusDemo/VIew/AttachUC.xaml Wyświetl plik

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

+ 22
- 13
ModbusDemo/VIew/CoilUC.xaml Wyświetl plik

@@ -10,8 +10,8 @@
d:DataContext="{d:DesignInstance Type=viewmodel:CoilUCViewModel,
IsDesignTimeCreatable=True}"
d:Background="White"
d:DesignHeight="450"
d:DesignWidth="800"
d:DesignHeight="800"
d:DesignWidth="1000"
mc:Ignorable="d">
<UserControl.Resources>
<Style TargetType="TextBox">
@@ -25,7 +25,7 @@
</RowDefinition>
<RowDefinition>
</RowDefinition>
<RowDefinition>
<RowDefinition Height="120">
</RowDefinition>
<RowDefinition>
</RowDefinition>
@@ -54,9 +54,10 @@
<Grid.RowDefinitions>
<RowDefinition>
</RowDefinition>
<RowDefinition Height="60">
<RowDefinition>
</RowDefinition>
</Grid.RowDefinitions>
<!-- 线圈的读取 -->
<StackPanel HorizontalAlignment="Center" Orientation="Vertical">
<TextBlock Margin="20" Text="从站地址">
</TextBlock>
@@ -78,18 +79,25 @@
</TextBox>
</StackPanel>

<StackPanel Grid.Row="2" Grid.ColumnSpan="4" HorizontalAlignment="Center" Orientation="Horizontal">
<StackPanel Grid.Row="2" Grid.ColumnSpan="5" HorizontalAlignment="Center" Orientation="Horizontal">

<TextBlock Margin="0,20,0,0" Text="读取结果:" FontWeight="Black" />
<TextBlock Margin="0,50,0,0" Text="读取结果:" FontWeight="Black" />

<TextBox MinWidth="120" Margin="20,0,0,0" Text="{Binding ReadResult}" Style="{StaticResource MaterialDesignOutlinedTextBox}" />
<TextBox
MinWidth="900"
MaxWidth="900"
Margin="20,0,0,0"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
Text="{Binding ReadResult}"
Style="{StaticResource MaterialDesignOutlinedTextBox}" />
</StackPanel>
<Button
Grid.Column="4"
Width="80"
Height="40"
IsEnabled="{Binding IsEnable}"
materialDesign:ButtonAssist.CornerRadius="20"
IsEnabled="{Binding IsEnable}"
Command="{Binding ReadCoilCmm}"
Content="读取"
Style="{StaticResource MaterialDesignRaisedDarkButton}">
@@ -109,6 +117,7 @@
<ColumnDefinition>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- 线圈的写入 -->
<StackPanel HorizontalAlignment="Center" Orientation="Vertical">
<TextBlock Margin="20" Text="从站地址">
</TextBlock>
@@ -134,7 +143,7 @@
Margin="0,-50,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="1代表Ture,0代表flase">
Text="1为true,0为flase">
</TextBlock>


@@ -149,21 +158,21 @@
Style="{StaticResource MaterialDesignRaisedDarkButton}">
</Button>
</Grid>
<!-- 展示历史读取数据 -->
<Grid Grid.Row="3">
<DataGrid ItemsSource="{Binding ModbusLogList}" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="*">
<DataGrid.Columns>
<!-- 操作类型列 -->
<DataGridTextColumn Header="操作类型" Binding="{Binding OperationType}" Width="100" />
<DataGridTextColumn Width="100" Header="操作类型" Binding="{Binding OperationType}" />

<!-- 请求数据列 -->
<DataGridTextColumn Header="请求数据" Binding="{Binding RequestData}" />
<DataGridTextColumn Header="请求数据" Binding="{Binding RequestData}" />

<!-- 响应数据列 -->
<DataGridTextColumn Header="响应数据" Binding="{Binding ResponseData}" />

<!-- 操作时间列 -->
<DataGridTextColumn Header="操作时间" Binding="{Binding Time, StringFormat='yyyy-MM-dd HH:mm:ss'}" Width="200"/>
<DataGridTextColumn Width="200" Header="操作时间" Binding="{Binding Time, StringFormat='yyyy-MM-dd HH:mm:ss'}" />
</DataGrid.Columns>
</DataGrid>
</Grid>


+ 19
- 9
ModbusDemo/VIew/RegisterUC.xaml Wyświetl plik

@@ -9,9 +9,9 @@
xmlns:viewmodel="clr-namespace:ModbusDemo.VIewModel"
d:DataContext="{d:DesignInstance Type=viewmodel:RegisterUCViewModel,
IsDesignTimeCreatable=True}"
d:DesignHeight="450"
d:DesignHeight="800"
d:Background="White"
d:DesignWidth="800"
d:DesignWidth="1000"
mc:Ignorable="d">
<UserControl.Resources>
<Style TargetType="TextBox">
@@ -25,7 +25,7 @@
</RowDefinition>
<RowDefinition>
</RowDefinition>
<RowDefinition>
<RowDefinition Height="120">
</RowDefinition>
<RowDefinition>
</RowDefinition>
@@ -54,9 +54,10 @@
<Grid.RowDefinitions>
<RowDefinition>
</RowDefinition>
<RowDefinition Height="60">
<RowDefinition>
</RowDefinition>
</Grid.RowDefinitions>
<!-- 读寄存器 -->
<StackPanel HorizontalAlignment="Center" Orientation="Vertical">
<TextBlock Margin="20" Text="从站地址">
</TextBlock>
@@ -78,18 +79,25 @@
</TextBox>
</StackPanel>

<StackPanel Grid.Row="2" Grid.ColumnSpan="4" HorizontalAlignment="Center" Orientation="Horizontal">
<StackPanel Grid.Row="2" Grid.ColumnSpan="5" HorizontalAlignment="Center" Orientation="Horizontal">

<TextBlock Margin="0,20,0,0" Text="读取结果:" FontWeight="Black" />
<TextBlock Margin="0,50,0,0" Text="读取结果:" FontWeight="Black" />

<TextBox MinWidth="120" Margin="20,0,0,0" Text="{Binding ReadResult}" Style="{StaticResource MaterialDesignOutlinedTextBox}" />
<TextBox
MinWidth="900"
MaxWidth="900"
Margin="20,0,0,0"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
Text="{Binding ReadResult}"
Style="{StaticResource MaterialDesignOutlinedTextBox}" />
</StackPanel>
<Button
Grid.Column="4"
Width="80"
Height="40"
IsEnabled="{Binding IsEnable}"
materialDesign:ButtonAssist.CornerRadius="20"
IsEnabled="{Binding IsEnable}"
Command="{Binding ReadRegisterCmm}"
Content="读取"
Style="{StaticResource MaterialDesignRaisedDarkButton}">
@@ -109,6 +117,7 @@
<ColumnDefinition>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- 写寄存器 -->
<StackPanel HorizontalAlignment="Center" Orientation="Vertical">
<TextBlock Margin="20" Text="从站地址">
</TextBlock>
@@ -142,14 +151,15 @@
Grid.Column="4"
Width="80"
Height="40"
IsEnabled="{Binding IsEnable}"
materialDesign:ButtonAssist.CornerRadius="20"
IsEnabled="{Binding IsEnable}"
Command="{Binding WriteRegisterCmm}"
Content="写入"
Style="{StaticResource MaterialDesignRaisedDarkButton}">
</Button>
</Grid>
<Grid Grid.Row="3">
<!-- 数据展示 -->
<DataGrid ItemsSource="{Binding ModbusLogList}" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="*">
<DataGrid.Columns>
<!-- 操作类型列 -->


+ 30
- 2
ModbusDemo/VIew/SettingsUC.xaml Wyświetl plik

@@ -31,7 +31,6 @@
<RowDefinition>
</RowDefinition>
</Grid.RowDefinitions>

<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition>
@@ -44,7 +43,8 @@
</ColumnDefinition>
<ColumnDefinition>
</ColumnDefinition>

<ColumnDefinition>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- 这是设置串口的选择 -->
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
@@ -61,6 +61,8 @@
<ComboBoxItem Content="COM2" />
<ComboBoxItem Content="COM3" />
<ComboBoxItem Content="COM4" />
<ComboBoxItem Content="COM5" />
<ComboBoxItem Content="COM6" />
</ComboBox>
</StackPanel>
<!-- 设置波特率 -->
@@ -129,6 +131,32 @@
</ComboBox>
</StackPanel>

<!-- 超时时间 -->
<StackPanel Grid.Column="5" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Margin="10,0,0,5" Text="超时时间">
</TextBlock>
<ComboBox
Width="80"
Height="30"
Margin="10,0,0,5"
HorizontalAlignment="Left"
materialDesign:HintAssist.Hint="200"
SelectedItem="{Binding TimeOut}">
<ComboBoxItem Content="20">
</ComboBoxItem>
<ComboBoxItem Content="50">
</ComboBoxItem>
<ComboBoxItem Content="100">
</ComboBoxItem>
<ComboBoxItem Content="200">
</ComboBoxItem>
<ComboBoxItem Content="300">
</ComboBoxItem>
<ComboBoxItem Content="500">
</ComboBoxItem>
</ComboBox>
</StackPanel>

</Grid>
<!-- 两个按钮的定义 -->
<Grid Grid.Row="2">


+ 8
- 63
ModbusDemo/VIewModel/AttachUCViewModel.cs Wyświetl plik

@@ -1,4 +1,6 @@
using Modbus.Device;
//using Modbus.Device;
using ModbusDemo.Model;
using ModbusDemo.Uitls;
using Prism.Commands;
using Prism.Mvvm;
using System;
@@ -15,7 +17,7 @@ namespace ModbusDemo.VIewModel
{

private SerialPort _serialPort;
public DelegateCommand ReadOddRegisterCmm { get; set; }
public DelegateCommand WriteMoreRegisterCmm { get; set; }
//读取的结果
private string _readResult;

@@ -28,18 +30,7 @@ namespace ModbusDemo.VIewModel
RaisePropertyChanged();
}
}
//显示操作的时间
private int _time;

public int Time
{
get { return _time; }
set
{
_time = value;
RaisePropertyChanged();
}
}


public AttachUCViewModel()
@@ -48,59 +39,13 @@ namespace ModbusDemo.VIewModel
}
public AttachUCViewModel(SerialPort serialPort)
{
ReadOddRegisterCmm = new DelegateCommand(ReadOddRegister);
WriteMoreRegisterCmm = new DelegateCommand(WriteMoreRegister);
_serialPort = serialPort;
}

private void ReadOddRegister()
private void WriteMoreRegister()
{
// 创建 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)
{
Console.WriteLine($"线程 {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();

}
}



+ 90
- 49
ModbusDemo/VIewModel/CoilUCViewModel.cs Wyświetl plik

@@ -1,6 +1,9 @@
using ModbusDemo.Device;
using ModbusDemo.Event;
using ModbusDemo.Model;
using ModbusDemo.Uitls;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
@@ -15,8 +18,12 @@ namespace ModbusDemo.VIewModel
/// <summary>
/// 这是CoilUC的VM用来支持数据绑定
/// </summary>
public class CoilUCViewModel : BindableBase
public class CoilUCViewModel : BindableBase,IDisposable
{
// 标记是否已释放资源
private bool _disposed = false;
//事件聚合器
private readonly IEventAggregator _eventAggregator;
//定义数据库操作
private ModbusDbContext _modbusDbContext;
//获取读线圈的类
@@ -41,8 +48,8 @@ namespace ModbusDemo.VIewModel
RaisePropertyChanged();
}
}
public string SerialPortInfo
public string SerialPortInfo
{
get
{
@@ -51,6 +58,9 @@ namespace ModbusDemo.VIewModel
}

}


#region 读取定义

//读取的从站id
@@ -168,50 +178,46 @@ namespace ModbusDemo.VIewModel

}

public CoilUCViewModel(SerialPort serialPort, ModbusRTU modbusRTU, ModbusDbContext modbusDbContext)
public CoilUCViewModel(SerialPort serialPort, ModbusRTU modbusRTU, ModbusDbContext modbusDbContext, IEventAggregator eventAggregator)
{
_serialPort = serialPort;
ReadCoilCmm = new DelegateCommand(ReadCoil);
_modbusRTU = modbusRTU;
WriteCoilCmm = new DelegateCommand(WriteCoil);
_modbusDbContext = modbusDbContext;
_eventAggregator = eventAggregator;
// 订阅事件(使用弱引用,避免内存泄漏)
_eventAggregator.GetEvent<SerialPortSettingsChangedEvent>().Subscribe(
OnSerialPortSettingsChanged,
ThreadOption.UIThread, // 在UI线程执行回调
false);// 不使用强引用
//初始化查询操作
ModbusLogList = GetOperateCoil();
IsEnable = true;
}

// 事件回调方法:当串口设置变更时触发
private void OnSerialPortSettingsChanged()
{
// 强制刷新SerialPortInfo属性,触发UI更新
RaisePropertyChanged(nameof(SerialPortInfo));
}
/// <summary>
/// 线圈读取操作
/// </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)
{
@@ -220,12 +226,17 @@ namespace ModbusDemo.VIewModel
IsEnable = true;
return;
}

string temp = "";
int count = 0;
foreach (var item in result)
{
count++;
temp += item;
temp += " ";
if(count %25 == 0)
{
temp += "\n";
}
}
ReadResult = temp;
MessageBox.Show("读取成功");
@@ -240,24 +251,10 @@ namespace ModbusDemo.VIewModel

private void WriteCoil()
{
//将按钮设置为不可点击状态,并解析用户输入的信息
IsEnable = false;
if (!byte.TryParse(WriteSlaveAddress, out byte writeSlaveAddressValue))
//检测当前输入的信息是否正确
var validation = CheckInputUitl.ValidateWriteParameters(WriteSlaveAddress, WriteStartAddress,WriteData);
if (!validation.success)
{
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))
{
MessageBox.Show("WriteData 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning);
IsEnable = true;
return;
}
//去除字符串中的空格
@@ -268,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;
});

}
@@ -294,8 +292,51 @@ namespace ModbusDemo.VIewModel
return _modbusDbContext.ModbusLog
.Where(log => log.OperationType == "读线圈" || log.OperationType == "写线圈")
.OrderByDescending(log => log.Time) // 按时间倒序,最新的在前
.Take(20) // 只取前20条记录
.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
- 1
ModbusDemo/VIewModel/MainWindowViewModel.cs Wyświetl plik

@@ -66,7 +66,7 @@ namespace ModbusDemo.VIewModel
LeftMenusList.Add(new MenusInfo() { Icon = "AllInclusive", MenuName = "线圈操作", ViewName = "CoilUC" });
LeftMenusList.Add(new MenusInfo() { Icon = "BlurCircular", MenuName = "寄存器操作", ViewName = "RegisterUC" });
LeftMenusList.Add(new MenusInfo() { Icon = "Settings", MenuName = "串口设置", ViewName = "SettingsUC" });
LeftMenusList.Add(new MenusInfo() { Icon = "Settings", MenuName = "附加功能", ViewName = "AttachUC" });
//LeftMenusList.Add(new MenusInfo() { Icon = "AddCircle", MenuName = "附加功能", ViewName = "AttachUC" });
}

public void DefultNaigation()


+ 87
- 49
ModbusDemo/VIewModel/RegisterUCViewModel.cs Wyświetl plik

@@ -1,6 +1,9 @@
using ModbusDemo.Device;
using ModbusDemo.Event;
using ModbusDemo.Model;
using ModbusDemo.Uitls;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
@@ -12,8 +15,12 @@ using System.Windows;

namespace ModbusDemo.VIewModel
{
class RegisterUCViewModel : BindableBase
class RegisterUCViewModel : BindableBase,IDisposable
{
// 标记是否已释放资源
private bool _disposed = false;
//事件聚合器
private readonly IEventAggregator _eventAggregator;
//定义数据库操作类
private ModbusDbContext _modbusDbContext;
//获取读线圈的类
@@ -163,19 +170,31 @@ namespace ModbusDemo.VIewModel
{

}
public RegisterUCViewModel(SerialPort serialPort, ModbusRTU modbusRTU, ModbusDbContext modbusDbContext)
public RegisterUCViewModel(SerialPort serialPort, ModbusRTU modbusRTU, ModbusDbContext modbusDbContext, IEventAggregator eventAggregator)
{
_serialPort = serialPort;
ReadRegisterCmm = new DelegateCommand(ReadRegister);
_modbusRTU = modbusRTU;
WriteRegisterCmm = new DelegateCommand(WriteRegister);
_modbusDbContext = modbusDbContext;

_eventAggregator = eventAggregator;
// 订阅事件(使用弱引用,避免内存泄漏)
_eventAggregator.GetEvent<SerialPortSettingsChangedEvent>().Subscribe(
OnSerialPortSettingsChanged,
ThreadOption.UIThread, // 在UI线程执行回调
false);// 不使用强引用
ModbusLogList = GetOperateRegister();

IsEnable = true;
}
// 事件回调方法:当串口设置变更时触发
private void OnSerialPortSettingsChanged()
{
// 强制刷新SerialPortInfo属性,触发UI更新
RaisePropertyChanged(nameof(SerialPortInfo));
}




@@ -184,32 +203,16 @@ namespace ModbusDemo.VIewModel
/// </summary>
private void ReadRegister()
{
//将按钮设置为不可点击状态,并解析用户输入的信息
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))
{
MessageBox.Show("StartAddress 格式无效", "warning", MessageBoxButton.OK, MessageBoxImage.Warning);
IsEnable = true;
return;
}

if (!ushort.TryParse(NumberOfPoints, out ushort numberOfPointsValue))
//检测当前输入的信息是否正确
var validation = CheckInputUitl.ValidateReadParameters(SlaveAddress, StartAddress, NumberOfPoints);
if (!validation.success)
{
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);
@@ -218,10 +221,16 @@ namespace ModbusDemo.VIewModel
return;
}
string temp = "";
int count = 0;
foreach (var item in result)
{
count++;
temp += item;
temp += " ";
if (count % 25 == 0)
{
temp += "\n";
}
}
ReadResult = temp;
MessageBox.Show("读取成功");
@@ -235,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))
{
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;
}
string[] parts = WriteData.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// 3. 创建结果数组
// 创建结果数组
ushort[] data = new ushort[parts.Length];

// 4. 遍历并转换每个部分
// 遍历并转换每个部分
for (int i = 0; i < parts.Length; i++)
{
// 尝试解析为ushort
@@ -271,7 +266,7 @@ namespace ModbusDemo.VIewModel
{

MessageBox.Show($"无法将 '{parts[i]}' 转换为 ushort 类型");
IsEnable = true;
return;
}
}
@@ -279,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;
@@ -293,7 +289,7 @@ namespace ModbusDemo.VIewModel
IsEnable = true;
}
});



@@ -307,8 +303,50 @@ namespace ModbusDemo.VIewModel
return _modbusDbContext.ModbusLog
.Where(log => log.OperationType == "读寄存器" || log.OperationType == "写寄存器")
.OrderByDescending(log => log.Time) // 按时间倒序,最新的在前
.Take(20) // 只取前20条记录
.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; // 标记为已释放
}
}
}

+ 70
- 14
ModbusDemo/VIewModel/SettingsUCViewModel.cs Wyświetl plik

@@ -1,5 +1,7 @@
using ModbusDemo.Uitls;
using ModbusDemo.Event;
using ModbusDemo.Uitls;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using Prism.Regions;
using System;
@@ -9,18 +11,26 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

using System.Timers;
using Timer = System.Timers.Timer;
using ModbusDemo.Device;
namespace ModbusDemo.VIewModel
{
class SettingsUCViewModel : BindableBase
{
//
private ModbusRTU _modbusRTU;
//定时器
private readonly Timer _checkTimer;
//事件聚合器
private readonly IEventAggregator _eventAggregator;
//控制页面跳转
private readonly IRegionManager _regionManager;
//断开连接的命令
public DelegateCommand DisConnectionCmm { get; set; }
public DelegateCommand DisConnectionCmm { get; set; }
//链接窗口的命令
public DelegateCommand ConnectionCmm { get; set; }
public DelegateCommand ConnectionCmm { get; set; }

//获取串口
private SerialPort _serialPort;
@@ -89,29 +99,63 @@ namespace ModbusDemo.VIewModel
}
}

//设置读取和写入时间
private string _timeOut = "200";

public string TimeOut
{
get { return _timeOut; }
set
{
_timeOut = value;
RaisePropertyChanged();
}
}


public SettingsUCViewModel()
{
}
/// <summary>
/// 从容器中获取创建的窗口
/// </summary>
/// <param name="serialPort"></param>
public SettingsUCViewModel(SerialPort serialPort, IRegionManager regionManager)
public SettingsUCViewModel(SerialPort serialPort, IRegionManager regionManager, IEventAggregator eventAggregator, ModbusRTU modbusRTU)
{
_serialPort = serialPort;
ConnectionCmm = new DelegateCommand(Connection);
DisConnectionCmm = new DelegateCommand(DisConnection);
_regionManager = regionManager;
_eventAggregator = eventAggregator;
_modbusRTU = modbusRTU;
_checkTimer = new Timer(5000); // 每秒检查一次
_checkTimer.Elapsed += CheckPortStatus;
_checkTimer.AutoReset = true;

}
private void CheckPortStatus(object sender, ElapsedEventArgs e)
{
//bool isConnected = SerialPort.GetPortNames().Contains(PortName);
byte[] bytes = new byte[] { 0x01, 0x01, 0x01, 0x2C, 0x00, 0x01, 0x3D, 0xFF };
byte[] response = _modbusRTU.SendMessage(bytes);
if (response ==null)
{
_checkTimer.Stop();
_serialPort.Close();
MessageBox.Show("当前连接已经断开");
}
}

private void DisConnection()
{
if (_serialPort.IsOpen)
{
_checkTimer.Stop();
_serialPort.Close();
MessageBox.Show("连接已经断开");
}
MessageBox.Show("连接已经断开");
}

/// <summary>
@@ -126,26 +170,38 @@ namespace ModbusDemo.VIewModel
_serialPort.PortName = GetComboBoxItemValue(this.PortName);
_serialPort.BaudRate = int.Parse(GetComboBoxItemValue(this.BaudRate));
_serialPort.Parity = this.Parity;
if (this.StopBits == StopBits.None)
{
this.StopBits = StopBits.One;
MessageBox.Show("当前设备不支持无停止位(None)模式,已自动切换为 1 个停止位。");
}
_serialPort.StopBits = this.StopBits;
_serialPort.DataBits = int.Parse(GetComboBoxItemValue(this.DataBits));

//读取超时时间
_serialPort.ReadTimeout = 500;
_serialPort.ReadTimeout = int.Parse(GetComboBoxItemValue(this.TimeOut));
//写入超时时间
_serialPort.WriteTimeout = 500;
_serialPort.WriteTimeout = int.Parse(GetComboBoxItemValue(this.TimeOut));
try
{
_serialPort.Open();
// 发布事件通知其他ViewModel
_eventAggregator.GetEvent<SerialPortSettingsChangedEvent>().Publish();
MessageBox.Show("串口链接成功");
_regionManager.Regions["ModbusRegion"].RequestNavigate("CoilUC");
_checkTimer.Start();
}
catch (Exception)
{

MessageBox.Show("串口已经链接,请先断开链接在尝试","warning",MessageBoxButton.OK,MessageBoxImage.Warning);
MessageBox.Show("串口已经链接或者断开,请重新检查在尝试", "warning", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
else
{
MessageBox.Show("当前串口已经连接,请先断开,再连接");
}

}
/// <summary>
/// 处理下拉框的选择信息
@@ -159,7 +215,7 @@ namespace ModbusDemo.VIewModel
string cleanText = displayText.Replace("System.Windows.Controls.ComboBoxItem: ", "");
return cleanText;
}

}
}

+ 1
- 1
ModbusDemo/appsettings.json Wyświetl plik

@@ -10,5 +10,5 @@
"node_modules",
"wwwroot"
],
"ConnectionStrings": { "ConnStr": "server=.;uid=sa;pwd=123456;database=Modbus;TrustServerCertificate=true" }
"ConnectionStrings": { "ConnStr": "3UiB0x91qCfBGGZJPtN2+6jjYWJL+PmUBwDGk07o3SzhkKx7GbsAJYoj44iejWaPIXfrZZvsvR30gpEgoVxYqVXexcruhQSPCW+B/msrunY=" }
}

+ 124
- 17
ModbusTest/ModbusRTUTest.cs Wyświetl plik

@@ -14,7 +14,7 @@ namespace ModbusTest
{
public class ModbusRTUTest
{
//设计缺陷,应该使用适配器模式重写SerialPort
private SerialPort _serialport;
private Mock<ModbusDbContext> _mockDbContext;
private ModbusRTU _modbusRtu;
@@ -29,7 +29,7 @@ namespace ModbusTest
_serialport.StopBits = StopBits.One;
_serialport.ReadTimeout = 500;
_serialport.WriteTimeout = 500;
_serialport.Open();
_serialport.Open();
_mockDbContext = new Mock<ModbusDbContext>();
_modbusRtu = new ModbusRTU(_serialport, _mockDbContext.Object);
}
@@ -38,6 +38,7 @@ namespace ModbusTest
{
// 确保在测试结束后释放资源
_serialport.Dispose();
_modbusRtu.Dispose();
}
/// <summary>
/// 测试线圈读取
@@ -47,7 +48,6 @@ namespace ModbusTest
{

var result = _modbusRtu.ReadCoil(1, 300, 5);
// 创建包含5个true的预期数组
var expected = new bool[5];
Array.Fill(expected, false);
CollectionAssert.AreEqual(expected, result);
@@ -57,21 +57,23 @@ namespace ModbusTest
public void ReadCoil2()
{
var result = _modbusRtu.ReadCoil(1, 305, 5);
// 创建包含5个flase的预期数组
var expected = new bool[5];
Array.Fill(expected, true);
CollectionAssert.AreEqual(expected, result);

}


/// <summary>
/// 测试写入线圈
/// </summary>
[Test]
public void WriteCoil1()
{
var data = new bool[5];
Array.Fill(data, true);
_modbusRtu.WriteCoil(1, 310, data);
// 创建包含5个true的预期数组
var expected = _modbusRtu.ReadCoil(1, 310, 5);
CollectionAssert.AreEqual(data, expected);

@@ -85,19 +87,21 @@ namespace ModbusTest
var data = new bool[5];
Array.Fill(data, false);
_modbusRtu.WriteCoil(1, 315, data);
// 创建包含5个flase的预期数组
var expected = _modbusRtu.ReadCoil(1, 315, 5);
CollectionAssert.AreEqual(data, expected);

}


/// <summary>
/// 测试读取寄存器
/// </summary>
[Test]
public void ReadRegister1()
{
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);
}

@@ -108,21 +112,23 @@ 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);
}



/// <summary>
/// 测试写入寄存器
/// </summary>
[Test]
public void WriteRegister1()
{
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);
}

@@ -133,9 +139,110 @@ 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>
//[Test]
//public void ParseCoilresponse1()
//{
// var data = new bool[5];
// Array.Fill(data, true);
// byte[] response = new byte[] { 0x01, 0x01, 0x01, 0x7f, 0x10, 0x68 };
// bool[] bools = _modbusRtu.ParseCoilresponse(new byte[] { }, response, 5);

// CollectionAssert.AreEqual(data, bools);
//}

//[Test]
//public void ParseCoilresponse2()
//{
// var data = new bool[5];
// Array.Fill(data, false);
// byte[] response = new byte[] { 0x01, 0x01, 0x01, 0x00, 0x51, 0x88 };
// bool[] result = _modbusRtu.ParseCoilresponse(new byte[] { }, response, 5);

// CollectionAssert.AreEqual(data, result);
//}

///// <summary>
///// 解析寄存器的返回值
///// </summary>
//[Test]
//public void ParseRegistersresponse1()
//{
// ushort[] data = new ushort[5];
// Array.Fill(data, (ushort)1);
// byte[] response = new byte[] {0x01, 0x03, 0x0a, 0x00, 0x01, 0x00, 0x01, 0x00,
// 0x01, 0x00, 0x01, 0x00, 0x01, 0x94, 0x26 };

// ushort[] result = _modbusRtu.ParseRegistersresponse(new byte[] {},response, 5);

// CollectionAssert.AreEqual(data, result);
//}

//[Test]
//public void ParseRegistersresponse2()
//{
// ushort[] data = new ushort[2];
// Array.Fill(data, (ushort)10);
// byte[] response = new byte[] {0x01, 0x03, 0x04, 0x00,0x0a, 0x00, 0x0a, 0x5a, 0x36 };

// ushort[] result = _modbusRtu.ParseRegistersresponse(new byte[] { }, response, 2);

// CollectionAssert.AreEqual(data, result);
//}

}


+ 6
- 1
ModbusTest/UnitTest1.cs Wyświetl plik

@@ -1,3 +1,5 @@
//using ModbusDemo.Enum;

namespace ModbusTest
{
public class Tests
@@ -10,7 +12,10 @@ namespace ModbusTest
[Test]
public void Test1()
{
Assert.Pass();
byte[] byteStream = new byte[] { 0x01, 0x81, 0x01, 0x10, 0x00, 0x00, 0xAB, 0x17 };
byte errorCodeByte = byteStream[2]; // 0x01
//ErrorCode errorCode = (ErrorCode)errorCodeByte; // ת»»Îª ErrorCode.FunctionCodeError
//string message = errorCode.GetMessage();
}
}
}

Ładowanie…
Anuluj
Zapisz