Author | SHA1 | Message | Date |
---|---|---|---|
|
b797ed4caf | 重构Modbus和功能修改 | 1 week ago |
|
9399645edf | 重构代码 | 1 week ago |
|
3179d77dcc | 修改页面显示bug,还要字符串换行 | 1 week ago |
|
85f81b9817 | 添加功能码,并添加可以选择读写耗时时间 | 2 weeks ago |
|
c7e5997eb3 | 添加全局事件委托器,并重构modbus线圈和寄存器的读写 | 2 weeks ago |
|
569cbca300 | 重构方案一 | 2 weeks ago |
@@ -12,6 +12,8 @@ using System.Data; | |||||
using System.DirectoryServices; | using System.DirectoryServices; | ||||
using System.IO; | using System.IO; | ||||
using System.IO.Ports; | using System.IO.Ports; | ||||
using System.Security.Cryptography; | |||||
using System.Text; | |||||
using System.Windows; | using System.Windows; | ||||
namespace ModbusDemo | namespace ModbusDemo | ||||
@@ -56,13 +58,14 @@ namespace ModbusDemo | |||||
.SetBasePath(Directory.GetCurrentDirectory()) | .SetBasePath(Directory.GetCurrentDirectory()) | ||||
.AddJsonFile("appsettings.json") | .AddJsonFile("appsettings.json") | ||||
.Build(); | .Build(); | ||||
// 2. 配置 DbContextOptions | |||||
// 2.解密连接字符串 | |||||
string decryptedConnection = Decrypt(configuration.GetConnectionString("ConnStr")); | |||||
// 3. 配置 DbContextOptions | |||||
var options = new DbContextOptionsBuilder<ModbusDbContext>() | var options = new DbContextOptionsBuilder<ModbusDbContext>() | ||||
.UseSqlServer(configuration.GetConnectionString("ConnStr")) | |||||
.UseSqlServer(decryptedConnection) | |||||
.Options; | .Options; | ||||
// 3. 注册 DbContext | |||||
// 4. 注册 DbContext | |||||
containerRegistry.Register<ModbusDbContext>(() => new ModbusDbContext(options)); | containerRegistry.Register<ModbusDbContext>(() => new ModbusDbContext(options)); | ||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -74,15 +77,40 @@ namespace ModbusDemo | |||||
mainWindowViewModel!.DefultNaigation(); | mainWindowViewModel!.DefultNaigation(); | ||||
base.OnInitialized(); | 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(); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -6,17 +6,17 @@ using System.Threading.Tasks; | |||||
namespace ModbusDemo.Device | namespace ModbusDemo.Device | ||||
{ | { | ||||
public interface IModbusRTU | |||||
public interface IModbusRTU:IDisposable | |||||
{ | { | ||||
public bool[] ReadCoil(byte slaveAddress, ushort startAddress, ushort numberOfPoints); | 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); | |||||
} | } | ||||
} | } |
@@ -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); | |||||
} | |||||
} |
@@ -1,28 +1,37 @@ | |||||
using ModbusDemo.Model; | |||||
using DryIoc; | |||||
using ModbusDemo.Model; | |||||
using ModbusDemo.Uitls; | using ModbusDemo.Uitls; | ||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.IO.Ports; | using System.IO.Ports; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Runtime.InteropServices; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Windows; | using System.Windows; | ||||
namespace ModbusDemo.Device | namespace ModbusDemo.Device | ||||
{ | { | ||||
public class ModbusRTU : IModbusRTU | public class ModbusRTU : IModbusRTU | ||||
{ | { | ||||
// 标记是否已释放资源 | |||||
private bool _disposed = false; | |||||
//串口 | //串口 | ||||
private SerialPort _serialPort; | private SerialPort _serialPort; | ||||
//用于操作数据库 | //用于操作数据库 | ||||
private ModbusDbContext _modbusDbContext; | private ModbusDbContext _modbusDbContext; | ||||
//TODO,修改 | |||||
//private SerialPortAdapter _portAdapter; | |||||
public ModbusRTU(SerialPort serialPort, ModbusDbContext modbusDbContext) | public ModbusRTU(SerialPort serialPort, ModbusDbContext modbusDbContext) | ||||
{ | { | ||||
_serialPort = serialPort; | _serialPort = serialPort; | ||||
_modbusDbContext = modbusDbContext; | _modbusDbContext = modbusDbContext; | ||||
//_portAdapter = new SerialPortAdapter(serialPort); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// 用来发送读写线圈的指令 | /// 用来发送读写线圈的指令 | ||||
/// </summary> | /// </summary> | ||||
@@ -39,463 +48,310 @@ namespace ModbusDemo.Device | |||||
MessageBox.Show("串口没有链接,请先链接"); | MessageBox.Show("串口没有链接,请先链接"); | ||||
return resultValue; | 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; | 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> | ||||
/// 写入多个线圈操作 | /// 写入多个线圈操作 | ||||
/// </summary> | /// </summary> | ||||
/// <param name="slaveAddress">从站地址</param> | /// <param name="slaveAddress">从站地址</param> | ||||
/// <param name="startAddress">起始地址</param> | /// <param name="startAddress">起始地址</param> | ||||
/// <param name="data">写入的数据</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) | if (!_serialPort.IsOpen) | ||||
{ | { | ||||
MessageBox.Show("串口没有链接,请先链接"); | 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> | ||||
/// 读寄存器操作 | |||||
/// 生成发送报文 | |||||
/// </summary> | /// </summary> | ||||
/// <typeparam name="T"></typeparam> | |||||
/// <param name="Function"></param> | |||||
/// <param name="slaveAddress"></param> | /// <param name="slaveAddress"></param> | ||||
/// <param name="startAddress"></param> | /// <param name="startAddress"></param> | ||||
/// <param name="numberOfPoints"></param> | |||||
/// <param name="data"></param> | |||||
/// <param name="processor"></param> | |||||
/// <returns></returns> | /// <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校验码 | //获取CRC校验码 | ||||
byte[] getCRC = sendByteList.ToArray(); | byte[] getCRC = sendByteList.ToArray(); | ||||
byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length); | |||||
byte[] resultCRC = CalculateCRC(getCRC, getCRC.Length); | |||||
sendByteList.Add(resultCRC[0]); | sendByteList.Add(resultCRC[0]); | ||||
sendByteList.Add(resultCRC[1]); | 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; | return resultValue; | ||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// 解析读取到的操作 | |||||
/// 处理写入线圈数据 | |||||
/// </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> | ||||
/// 写寄存器 | |||||
/// 解析写寄存器的值 | |||||
/// </summary> | /// </summary> | ||||
/// <param name="slaveAddress"></param> | |||||
/// <param name="startAddress"></param> | |||||
/// <param name="sendByteList"></param> | |||||
/// <param name="data"></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); | sendByteList.Add((byte)byteCount); | ||||
// 添加寄存器数据 | |||||
foreach (ushort value in data) | 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 maxRetries = 3; // 最大重试次数 | ||||
int attempt = 0; | int attempt = 0; | ||||
byte[] response = null; | byte[] response = null; | ||||
List<byte> responseData = new List<byte>(); | List<byte> responseData = new List<byte>(); | ||||
_serialPort.Write(sendByte, 0, sendByte.Length); | |||||
while (attempt < maxRetries) | 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> | ||||
/// 将数组转换为字符串 | /// 将数组转换为字符串 | ||||
/// </summary> | /// </summary> | ||||
@@ -506,7 +362,7 @@ namespace ModbusDemo.Device | |||||
if (byteArray == null || byteArray.Length == 0) | if (byteArray == null || byteArray.Length == 0) | ||||
return string.Empty; | return string.Empty; | ||||
System.Text.StringBuilder sb = new System.Text.StringBuilder(); | |||||
StringBuilder sb = new StringBuilder(); | |||||
// 处理第一个元素(无前导空格) | // 处理第一个元素(无前导空格) | ||||
sb.Append(byteArray[0].ToString("X2")); | sb.Append(byteArray[0].ToString("X2")); | ||||
@@ -520,5 +376,185 @@ namespace ModbusDemo.Device | |||||
return sb.ToString(); | 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; // 标记为已释放 | |||||
} | |||||
} | } | ||||
} | } |
@@ -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; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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, "数据库异常"); | |||||
} | |||||
} |
@@ -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 | |||||
{ | |||||
} | |||||
} |
@@ -4,7 +4,8 @@ using System.Linq; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Windows.Markup; | using System.Windows.Markup; | ||||
using System; | |||||
using System.Reflection; | |||||
namespace ModbusDemo.Extension | namespace ModbusDemo.Extension | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
@@ -12,6 +13,7 @@ namespace ModbusDemo.Extension | |||||
/// </summary> | /// </summary> | ||||
class EnumBindingSourceExtension : MarkupExtension | class EnumBindingSourceExtension : MarkupExtension | ||||
{ | { | ||||
//枚举字段 | |||||
private Type _enumType; | private Type _enumType; | ||||
public Type EnumType | public Type EnumType | ||||
@@ -39,7 +41,13 @@ namespace ModbusDemo.Extension | |||||
{ | { | ||||
EnumType = enumType; | EnumType = enumType; | ||||
} | } | ||||
/// <summary> | |||||
/// 将枚举转换为数组,返回出去 | |||||
/// 可以绑定可空枚举 | |||||
/// </summary> | |||||
/// <param name="serviceProvider">xmal上下文</param> | |||||
/// <returns></returns> | |||||
/// <exception cref="InvalidOperationException"></exception> | |||||
public override object ProvideValue(IServiceProvider serviceProvider) | public override object ProvideValue(IServiceProvider serviceProvider) | ||||
{ | { | ||||
if (_enumType == null) | if (_enumType == null) | ||||
@@ -10,8 +10,8 @@ | |||||
xmlns:prism="http://prismlibrary.com/" | xmlns:prism="http://prismlibrary.com/" | ||||
xmlns:viewmodel="clr-namespace:ModbusDemo.VIewModel" | xmlns:viewmodel="clr-namespace:ModbusDemo.VIewModel" | ||||
Title="MainWindow" | Title="MainWindow" | ||||
Width="900" | |||||
Height="600" | |||||
Width="1000" | |||||
Height="800" | |||||
d:DataContext="{d:DesignInstance Type=viewmodel:MainWindowViewModel, | d:DataContext="{d:DesignInstance Type=viewmodel:MainWindowViewModel, | ||||
IsDesignTimeCreatable=True}" | IsDesignTimeCreatable=True}" | ||||
WindowStartupLocation="CenterScreen" | WindowStartupLocation="CenterScreen" | ||||
@@ -6,14 +6,29 @@ | |||||
<Nullable>disable</Nullable> | <Nullable>disable</Nullable> | ||||
<ImplicitUsings>enable</ImplicitUsings> | <ImplicitUsings>enable</ImplicitUsings> | ||||
<UseWPF>true</UseWPF> | <UseWPF>true</UseWPF> | ||||
<ApplicationIcon>ModbusRTU_64x64.ico</ApplicationIcon> | |||||
</PropertyGroup> | </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> | <ItemGroup> | ||||
<PackageReference Include="MaterialDesignThemes" Version="5.2.1" /> | <PackageReference Include="MaterialDesignThemes" Version="5.2.1" /> | ||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.27" /> | <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.27" /> | ||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.7" /> | <PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.7" /> | ||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" 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="Prism.DryIoc" Version="8.1.97" /> | ||||
<PackageReference Include="System.IO.Ports" Version="9.0.7" /> | <PackageReference Include="System.IO.Ports" Version="9.0.7" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -24,4 +39,8 @@ | |||||
</None> | </None> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<Folder Include="bin\Debug\net8.0-windows\" /> | |||||
</ItemGroup> | |||||
</Project> | </Project> |
@@ -7,11 +7,17 @@ using System.Threading.Tasks; | |||||
namespace ModbusDemo.Model | namespace ModbusDemo.Model | ||||
{ | { | ||||
/// <summary> | |||||
/// 操作数据库的上下文 | |||||
/// </summary> | |||||
public class ModbusDbContext:DbContext | public class ModbusDbContext:DbContext | ||||
{ | { | ||||
protected ModbusDbContext() | |||||
public ModbusDbContext() | |||||
{ | { | ||||
} | } | ||||
public ModbusDbContext(DbContextOptions<ModbusDbContext> options) :base(options) | public ModbusDbContext(DbContextOptions<ModbusDbContext> options) :base(options) | ||||
{ | { | ||||
@@ -7,6 +7,9 @@ using System.Threading.Tasks; | |||||
namespace ModbusDemo.Model | namespace ModbusDemo.Model | ||||
{ | { | ||||
/// <summary> | |||||
/// 表的映射 | |||||
/// </summary> | |||||
public class ModbusLog | public class ModbusLog | ||||
{ | { | ||||
//数据库主键 | //数据库主键 | ||||
@@ -17,6 +20,7 @@ namespace ModbusDemo.Model | |||||
//请求的字节信息 | //请求的字节信息 | ||||
public string RequestData { get; set; } | public string RequestData { get; set; } | ||||
//返回的字节信息 | //返回的字节信息 | ||||
public string ResponseData { get; set; } | public string ResponseData { get; set; } | ||||
//操作的时间 | //操作的时间 | ||||
@@ -4,21 +4,25 @@ using System.Linq; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Windows; | using System.Windows; | ||||
using ModbusDemo.ErrorCode; | |||||
namespace ModbusDemo.Uitls | namespace ModbusDemo.Uitls | ||||
{ | { | ||||
public static class CheckData | public static class CheckData | ||||
{ | { | ||||
/// <summary> | |||||
/// 检测返回的数据格式是否正确 | |||||
/// </summary> | |||||
/// <param name="response"></param> | |||||
/// <returns></returns> | |||||
public static bool CheckResponse(byte[] response) | public static bool CheckResponse(byte[] response) | ||||
{ | { | ||||
if (response.Length == 0) | |||||
if (response == null || response.Length == 0) | |||||
{ | { | ||||
MessageBox.Show("返回数据为空", "error", MessageBoxButton.OK, MessageBoxImage.Error); | MessageBox.Show("返回数据为空", "error", MessageBoxButton.OK, MessageBoxImage.Error); | ||||
return false; | return false; | ||||
} | } | ||||
// 检查数组长度是否足够 | // 检查数组长度是否足够 | ||||
if (response.Length > 1) | |||||
if (response.Length > 3) | |||||
{ | { | ||||
byte secondByte = response[1]; // 获取第二个字节(索引为1) | byte secondByte = response[1]; // 获取第二个字节(索引为1) | ||||
@@ -28,15 +32,21 @@ namespace ModbusDemo.Uitls | |||||
// 判断高4位是否等于8 | // 判断高4位是否等于8 | ||||
if (highNibble == 8) | if (highNibble == 8) | ||||
{ | { | ||||
MessageBox.Show($"数据错误:错误码为:0x{response[2]:X2}"); | |||||
var error = ErrorCode.ErrorCode.FromByte(response[2]); | |||||
MessageBox.Show(error.ToString()); | |||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
else | |||||
{ | |||||
return false; | |||||
} | |||||
if (!CRCUitl.ValidateCRC(response)) | if (!CRCUitl.ValidateCRC(response)) | ||||
{ | { | ||||
MessageBox.Show("0x14:CRC校验错误"); | |||||
MessageBox.Show(ErrorCode.ErrorCode.CrcCheckError.ToString()); | |||||
return false; | return false; | ||||
} | } | ||||
@@ -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:DesignHeight="450" | ||||
d:DesignWidth="800" | d:DesignWidth="800" | ||||
mc:Ignorable="d"> | mc:Ignorable="d"> | ||||
<UserControl.Resources> | |||||
<Style TargetType="TextBox"> | |||||
<Setter Property="Margin" Value="0,5" /> | |||||
<Setter Property="FontSize" Value="14" /> | |||||
</Style> | |||||
</UserControl.Resources> | |||||
<Grid> | <Grid> | ||||
<Grid.RowDefinitions> | <Grid.RowDefinitions> | ||||
<RowDefinition Height="80"> | <RowDefinition Height="80"> | ||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition> | <RowDefinition> | ||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition Height="80"> | |||||
</RowDefinition> | |||||
<RowDefinition> | <RowDefinition> | ||||
</RowDefinition> | </RowDefinition> | ||||
</Grid.RowDefinitions> | </Grid.RowDefinitions> | ||||
<Button | <Button | ||||
@@ -33,8 +40,28 @@ | |||||
</Button> | </Button> | ||||
<TextBox Grid.Row="1" Margin="5" Text="{Binding ReadResult}" Style="{StaticResource MaterialDesignOutlinedTextBox}"> | <TextBox Grid.Row="1" Margin="5" Text="{Binding ReadResult}" Style="{StaticResource MaterialDesignOutlinedTextBox}"> | ||||
</TextBox> | </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> | </Grid> | ||||
</UserControl> | </UserControl> |
@@ -10,8 +10,8 @@ | |||||
d:DataContext="{d:DesignInstance Type=viewmodel:CoilUCViewModel, | d:DataContext="{d:DesignInstance Type=viewmodel:CoilUCViewModel, | ||||
IsDesignTimeCreatable=True}" | IsDesignTimeCreatable=True}" | ||||
d:Background="White" | d:Background="White" | ||||
d:DesignHeight="450" | |||||
d:DesignWidth="800" | |||||
d:DesignHeight="800" | |||||
d:DesignWidth="1000" | |||||
mc:Ignorable="d"> | mc:Ignorable="d"> | ||||
<UserControl.Resources> | <UserControl.Resources> | ||||
<Style TargetType="TextBox"> | <Style TargetType="TextBox"> | ||||
@@ -25,7 +25,7 @@ | |||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition> | <RowDefinition> | ||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition> | |||||
<RowDefinition Height="120"> | |||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition> | <RowDefinition> | ||||
</RowDefinition> | </RowDefinition> | ||||
@@ -54,9 +54,10 @@ | |||||
<Grid.RowDefinitions> | <Grid.RowDefinitions> | ||||
<RowDefinition> | <RowDefinition> | ||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition Height="60"> | |||||
<RowDefinition> | |||||
</RowDefinition> | </RowDefinition> | ||||
</Grid.RowDefinitions> | </Grid.RowDefinitions> | ||||
<!-- 线圈的读取 --> | |||||
<StackPanel HorizontalAlignment="Center" Orientation="Vertical"> | <StackPanel HorizontalAlignment="Center" Orientation="Vertical"> | ||||
<TextBlock Margin="20" Text="从站地址"> | <TextBlock Margin="20" Text="从站地址"> | ||||
</TextBlock> | </TextBlock> | ||||
@@ -78,18 +79,25 @@ | |||||
</TextBox> | </TextBox> | ||||
</StackPanel> | </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> | </StackPanel> | ||||
<Button | <Button | ||||
Grid.Column="4" | Grid.Column="4" | ||||
Width="80" | Width="80" | ||||
Height="40" | Height="40" | ||||
IsEnabled="{Binding IsEnable}" | |||||
materialDesign:ButtonAssist.CornerRadius="20" | materialDesign:ButtonAssist.CornerRadius="20" | ||||
IsEnabled="{Binding IsEnable}" | |||||
Command="{Binding ReadCoilCmm}" | Command="{Binding ReadCoilCmm}" | ||||
Content="读取" | Content="读取" | ||||
Style="{StaticResource MaterialDesignRaisedDarkButton}"> | Style="{StaticResource MaterialDesignRaisedDarkButton}"> | ||||
@@ -109,6 +117,7 @@ | |||||
<ColumnDefinition> | <ColumnDefinition> | ||||
</ColumnDefinition> | </ColumnDefinition> | ||||
</Grid.ColumnDefinitions> | </Grid.ColumnDefinitions> | ||||
<!-- 线圈的写入 --> | |||||
<StackPanel HorizontalAlignment="Center" Orientation="Vertical"> | <StackPanel HorizontalAlignment="Center" Orientation="Vertical"> | ||||
<TextBlock Margin="20" Text="从站地址"> | <TextBlock Margin="20" Text="从站地址"> | ||||
</TextBlock> | </TextBlock> | ||||
@@ -134,7 +143,7 @@ | |||||
Margin="0,-50,0,0" | Margin="0,-50,0,0" | ||||
HorizontalAlignment="Center" | HorizontalAlignment="Center" | ||||
VerticalAlignment="Center" | VerticalAlignment="Center" | ||||
Text="1代表Ture,0代表flase"> | |||||
Text="1为true,0为flase"> | |||||
</TextBlock> | </TextBlock> | ||||
@@ -149,21 +158,21 @@ | |||||
Style="{StaticResource MaterialDesignRaisedDarkButton}"> | Style="{StaticResource MaterialDesignRaisedDarkButton}"> | ||||
</Button> | </Button> | ||||
</Grid> | </Grid> | ||||
<!-- 展示历史读取数据 --> | |||||
<Grid Grid.Row="3"> | <Grid Grid.Row="3"> | ||||
<DataGrid ItemsSource="{Binding ModbusLogList}" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="*"> | <DataGrid ItemsSource="{Binding ModbusLogList}" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="*"> | ||||
<DataGrid.Columns> | <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 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.Columns> | ||||
</DataGrid> | </DataGrid> | ||||
</Grid> | </Grid> | ||||
@@ -9,9 +9,9 @@ | |||||
xmlns:viewmodel="clr-namespace:ModbusDemo.VIewModel" | xmlns:viewmodel="clr-namespace:ModbusDemo.VIewModel" | ||||
d:DataContext="{d:DesignInstance Type=viewmodel:RegisterUCViewModel, | d:DataContext="{d:DesignInstance Type=viewmodel:RegisterUCViewModel, | ||||
IsDesignTimeCreatable=True}" | IsDesignTimeCreatable=True}" | ||||
d:DesignHeight="450" | |||||
d:DesignHeight="800" | |||||
d:Background="White" | d:Background="White" | ||||
d:DesignWidth="800" | |||||
d:DesignWidth="1000" | |||||
mc:Ignorable="d"> | mc:Ignorable="d"> | ||||
<UserControl.Resources> | <UserControl.Resources> | ||||
<Style TargetType="TextBox"> | <Style TargetType="TextBox"> | ||||
@@ -25,7 +25,7 @@ | |||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition> | <RowDefinition> | ||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition> | |||||
<RowDefinition Height="120"> | |||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition> | <RowDefinition> | ||||
</RowDefinition> | </RowDefinition> | ||||
@@ -54,9 +54,10 @@ | |||||
<Grid.RowDefinitions> | <Grid.RowDefinitions> | ||||
<RowDefinition> | <RowDefinition> | ||||
</RowDefinition> | </RowDefinition> | ||||
<RowDefinition Height="60"> | |||||
<RowDefinition> | |||||
</RowDefinition> | </RowDefinition> | ||||
</Grid.RowDefinitions> | </Grid.RowDefinitions> | ||||
<!-- 读寄存器 --> | |||||
<StackPanel HorizontalAlignment="Center" Orientation="Vertical"> | <StackPanel HorizontalAlignment="Center" Orientation="Vertical"> | ||||
<TextBlock Margin="20" Text="从站地址"> | <TextBlock Margin="20" Text="从站地址"> | ||||
</TextBlock> | </TextBlock> | ||||
@@ -78,18 +79,25 @@ | |||||
</TextBox> | </TextBox> | ||||
</StackPanel> | </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> | </StackPanel> | ||||
<Button | <Button | ||||
Grid.Column="4" | Grid.Column="4" | ||||
Width="80" | Width="80" | ||||
Height="40" | Height="40" | ||||
IsEnabled="{Binding IsEnable}" | |||||
materialDesign:ButtonAssist.CornerRadius="20" | materialDesign:ButtonAssist.CornerRadius="20" | ||||
IsEnabled="{Binding IsEnable}" | |||||
Command="{Binding ReadRegisterCmm}" | Command="{Binding ReadRegisterCmm}" | ||||
Content="读取" | Content="读取" | ||||
Style="{StaticResource MaterialDesignRaisedDarkButton}"> | Style="{StaticResource MaterialDesignRaisedDarkButton}"> | ||||
@@ -109,6 +117,7 @@ | |||||
<ColumnDefinition> | <ColumnDefinition> | ||||
</ColumnDefinition> | </ColumnDefinition> | ||||
</Grid.ColumnDefinitions> | </Grid.ColumnDefinitions> | ||||
<!-- 写寄存器 --> | |||||
<StackPanel HorizontalAlignment="Center" Orientation="Vertical"> | <StackPanel HorizontalAlignment="Center" Orientation="Vertical"> | ||||
<TextBlock Margin="20" Text="从站地址"> | <TextBlock Margin="20" Text="从站地址"> | ||||
</TextBlock> | </TextBlock> | ||||
@@ -142,14 +151,15 @@ | |||||
Grid.Column="4" | Grid.Column="4" | ||||
Width="80" | Width="80" | ||||
Height="40" | Height="40" | ||||
IsEnabled="{Binding IsEnable}" | |||||
materialDesign:ButtonAssist.CornerRadius="20" | materialDesign:ButtonAssist.CornerRadius="20" | ||||
IsEnabled="{Binding IsEnable}" | |||||
Command="{Binding WriteRegisterCmm}" | Command="{Binding WriteRegisterCmm}" | ||||
Content="写入" | Content="写入" | ||||
Style="{StaticResource MaterialDesignRaisedDarkButton}"> | Style="{StaticResource MaterialDesignRaisedDarkButton}"> | ||||
</Button> | </Button> | ||||
</Grid> | </Grid> | ||||
<Grid Grid.Row="3"> | <Grid Grid.Row="3"> | ||||
<!-- 数据展示 --> | |||||
<DataGrid ItemsSource="{Binding ModbusLogList}" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="*"> | <DataGrid ItemsSource="{Binding ModbusLogList}" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="*"> | ||||
<DataGrid.Columns> | <DataGrid.Columns> | ||||
<!-- 操作类型列 --> | <!-- 操作类型列 --> | ||||
@@ -31,7 +31,6 @@ | |||||
<RowDefinition> | <RowDefinition> | ||||
</RowDefinition> | </RowDefinition> | ||||
</Grid.RowDefinitions> | </Grid.RowDefinitions> | ||||
<Grid Grid.Row="1"> | <Grid Grid.Row="1"> | ||||
<Grid.ColumnDefinitions> | <Grid.ColumnDefinitions> | ||||
<ColumnDefinition> | <ColumnDefinition> | ||||
@@ -44,7 +43,8 @@ | |||||
</ColumnDefinition> | </ColumnDefinition> | ||||
<ColumnDefinition> | <ColumnDefinition> | ||||
</ColumnDefinition> | </ColumnDefinition> | ||||
<ColumnDefinition> | |||||
</ColumnDefinition> | |||||
</Grid.ColumnDefinitions> | </Grid.ColumnDefinitions> | ||||
<!-- 这是设置串口的选择 --> | <!-- 这是设置串口的选择 --> | ||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> | <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> | ||||
@@ -61,6 +61,8 @@ | |||||
<ComboBoxItem Content="COM2" /> | <ComboBoxItem Content="COM2" /> | ||||
<ComboBoxItem Content="COM3" /> | <ComboBoxItem Content="COM3" /> | ||||
<ComboBoxItem Content="COM4" /> | <ComboBoxItem Content="COM4" /> | ||||
<ComboBoxItem Content="COM5" /> | |||||
<ComboBoxItem Content="COM6" /> | |||||
</ComboBox> | </ComboBox> | ||||
</StackPanel> | </StackPanel> | ||||
<!-- 设置波特率 --> | <!-- 设置波特率 --> | ||||
@@ -129,6 +131,32 @@ | |||||
</ComboBox> | </ComboBox> | ||||
</StackPanel> | </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 Grid.Row="2"> | <Grid Grid.Row="2"> | ||||
@@ -1,4 +1,6 @@ | |||||
using Modbus.Device; | |||||
//using Modbus.Device; | |||||
using ModbusDemo.Model; | |||||
using ModbusDemo.Uitls; | |||||
using Prism.Commands; | using Prism.Commands; | ||||
using Prism.Mvvm; | using Prism.Mvvm; | ||||
using System; | using System; | ||||
@@ -15,7 +17,7 @@ namespace ModbusDemo.VIewModel | |||||
{ | { | ||||
private SerialPort _serialPort; | private SerialPort _serialPort; | ||||
public DelegateCommand ReadOddRegisterCmm { get; set; } | |||||
public DelegateCommand WriteMoreRegisterCmm { get; set; } | |||||
//读取的结果 | //读取的结果 | ||||
private string _readResult; | private string _readResult; | ||||
@@ -28,18 +30,7 @@ namespace ModbusDemo.VIewModel | |||||
RaisePropertyChanged(); | RaisePropertyChanged(); | ||||
} | } | ||||
} | } | ||||
//显示操作的时间 | |||||
private int _time; | |||||
public int Time | |||||
{ | |||||
get { return _time; } | |||||
set | |||||
{ | |||||
_time = value; | |||||
RaisePropertyChanged(); | |||||
} | |||||
} | |||||
public AttachUCViewModel() | public AttachUCViewModel() | ||||
@@ -48,59 +39,13 @@ namespace ModbusDemo.VIewModel | |||||
} | } | ||||
public AttachUCViewModel(SerialPort serialPort) | public AttachUCViewModel(SerialPort serialPort) | ||||
{ | { | ||||
ReadOddRegisterCmm = new DelegateCommand(ReadOddRegister); | |||||
WriteMoreRegisterCmm = new DelegateCommand(WriteMoreRegister); | |||||
_serialPort = serialPort; | _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(); | |||||
} | } | ||||
} | } | ||||
@@ -1,6 +1,9 @@ | |||||
using ModbusDemo.Device; | using ModbusDemo.Device; | ||||
using ModbusDemo.Event; | |||||
using ModbusDemo.Model; | using ModbusDemo.Model; | ||||
using ModbusDemo.Uitls; | |||||
using Prism.Commands; | using Prism.Commands; | ||||
using Prism.Events; | |||||
using Prism.Mvvm; | using Prism.Mvvm; | ||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
@@ -15,8 +18,12 @@ namespace ModbusDemo.VIewModel | |||||
/// <summary> | /// <summary> | ||||
/// 这是CoilUC的VM用来支持数据绑定 | /// 这是CoilUC的VM用来支持数据绑定 | ||||
/// </summary> | /// </summary> | ||||
public class CoilUCViewModel : BindableBase | |||||
public class CoilUCViewModel : BindableBase,IDisposable | |||||
{ | { | ||||
// 标记是否已释放资源 | |||||
private bool _disposed = false; | |||||
//事件聚合器 | |||||
private readonly IEventAggregator _eventAggregator; | |||||
//定义数据库操作 | //定义数据库操作 | ||||
private ModbusDbContext _modbusDbContext; | private ModbusDbContext _modbusDbContext; | ||||
//获取读线圈的类 | //获取读线圈的类 | ||||
@@ -41,8 +48,8 @@ namespace ModbusDemo.VIewModel | |||||
RaisePropertyChanged(); | RaisePropertyChanged(); | ||||
} | } | ||||
} | } | ||||
public string SerialPortInfo | |||||
public string SerialPortInfo | |||||
{ | { | ||||
get | get | ||||
{ | { | ||||
@@ -51,6 +58,9 @@ namespace ModbusDemo.VIewModel | |||||
} | } | ||||
} | } | ||||
#region 读取定义 | #region 读取定义 | ||||
//读取的从站id | //读取的从站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; | _serialPort = serialPort; | ||||
ReadCoilCmm = new DelegateCommand(ReadCoil); | ReadCoilCmm = new DelegateCommand(ReadCoil); | ||||
_modbusRTU = modbusRTU; | _modbusRTU = modbusRTU; | ||||
WriteCoilCmm = new DelegateCommand(WriteCoil); | WriteCoilCmm = new DelegateCommand(WriteCoil); | ||||
_modbusDbContext = modbusDbContext; | _modbusDbContext = modbusDbContext; | ||||
_eventAggregator = eventAggregator; | |||||
// 订阅事件(使用弱引用,避免内存泄漏) | |||||
_eventAggregator.GetEvent<SerialPortSettingsChangedEvent>().Subscribe( | |||||
OnSerialPortSettingsChanged, | |||||
ThreadOption.UIThread, // 在UI线程执行回调 | |||||
false);// 不使用强引用 | |||||
//初始化查询操作 | //初始化查询操作 | ||||
ModbusLogList = GetOperateCoil(); | ModbusLogList = GetOperateCoil(); | ||||
IsEnable = true; | IsEnable = true; | ||||
} | } | ||||
// 事件回调方法:当串口设置变更时触发 | |||||
private void OnSerialPortSettingsChanged() | |||||
{ | |||||
// 强制刷新SerialPortInfo属性,触发UI更新 | |||||
RaisePropertyChanged(nameof(SerialPortInfo)); | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// 线圈读取操作 | /// 线圈读取操作 | ||||
/// </summary> | /// </summary> | ||||
private void ReadCoil() | 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; | return; | ||||
} | } | ||||
//使用子线程,防止ui线程卡顿 | //使用子线程,防止ui线程卡顿 | ||||
Task.Run(() => | Task.Run(() => | ||||
{ | { | ||||
bool[] result = _modbusRTU.ReadCoil(slaveAddressValue, startAddressValue, numberOfPointsValue); | |||||
IsEnable = false; | |||||
bool[] result = _modbusRTU.ReadCoil(validation.slaveAddress, validation.startAddress, validation.numberOfPoints); | |||||
if (result == null) | if (result == null) | ||||
{ | { | ||||
@@ -220,12 +226,17 @@ namespace ModbusDemo.VIewModel | |||||
IsEnable = true; | IsEnable = true; | ||||
return; | return; | ||||
} | } | ||||
string temp = ""; | string temp = ""; | ||||
int count = 0; | |||||
foreach (var item in result) | foreach (var item in result) | ||||
{ | { | ||||
count++; | |||||
temp += item; | temp += item; | ||||
temp += " "; | temp += " "; | ||||
if(count %25 == 0) | |||||
{ | |||||
temp += "\n"; | |||||
} | |||||
} | } | ||||
ReadResult = temp; | ReadResult = temp; | ||||
MessageBox.Show("读取成功"); | MessageBox.Show("读取成功"); | ||||
@@ -240,24 +251,10 @@ namespace ModbusDemo.VIewModel | |||||
private void WriteCoil() | 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; | return; | ||||
} | } | ||||
//去除字符串中的空格 | //去除字符串中的空格 | ||||
@@ -268,19 +265,20 @@ namespace ModbusDemo.VIewModel | |||||
//使用子线程执行,避免ui线程卡顿 | //使用子线程执行,避免ui线程卡顿 | ||||
Task.Run(() => | Task.Run(() => | ||||
{ | { | ||||
_modbusRTU.WriteCoil(writeSlaveAddressValue, WriteStartAddressValue, data); | |||||
IsEnable = false; | |||||
bool issucceed = _modbusRTU.WriteCoil(validation.slaveAddress, validation.startAddress, data); | |||||
ModbusLogList = GetOperateCoil(); | ModbusLogList = GetOperateCoil(); | ||||
ModbusLog modbusLog = ModbusLogList.FirstOrDefault(); | |||||
if (modbusLog != null && modbusLog.ResponseData != string.Empty && modbusLog.ResponseData[3] !='8') | |||||
if (issucceed) | |||||
{ | { | ||||
MessageBox.Show("写入成功"); | MessageBox.Show("写入成功"); | ||||
IsEnable = true; | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
MessageBox.Show("写入失败", "error", MessageBoxButton.OK, MessageBoxImage.Error); | MessageBox.Show("写入失败", "error", MessageBoxButton.OK, MessageBoxImage.Error); | ||||
IsEnable = true; | |||||
} | } | ||||
IsEnable = true; | |||||
}); | }); | ||||
} | } | ||||
@@ -294,8 +292,51 @@ namespace ModbusDemo.VIewModel | |||||
return _modbusDbContext.ModbusLog | return _modbusDbContext.ModbusLog | ||||
.Where(log => log.OperationType == "读线圈" || log.OperationType == "写线圈") | .Where(log => log.OperationType == "读线圈" || log.OperationType == "写线圈") | ||||
.OrderByDescending(log => log.Time) // 按时间倒序,最新的在前 | .OrderByDescending(log => log.Time) // 按时间倒序,最新的在前 | ||||
.Take(20) // 只取前20条记录 | |||||
.Take(30) // 只取前20条记录 | |||||
.ToList(); // 执行查询 | .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; // 标记为已释放 | |||||
} | |||||
} | } | ||||
} | } |
@@ -66,7 +66,7 @@ namespace ModbusDemo.VIewModel | |||||
LeftMenusList.Add(new MenusInfo() { Icon = "AllInclusive", MenuName = "线圈操作", ViewName = "CoilUC" }); | LeftMenusList.Add(new MenusInfo() { Icon = "AllInclusive", MenuName = "线圈操作", ViewName = "CoilUC" }); | ||||
LeftMenusList.Add(new MenusInfo() { Icon = "BlurCircular", MenuName = "寄存器操作", ViewName = "RegisterUC" }); | 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 = "SettingsUC" }); | ||||
LeftMenusList.Add(new MenusInfo() { Icon = "Settings", MenuName = "附加功能", ViewName = "AttachUC" }); | |||||
//LeftMenusList.Add(new MenusInfo() { Icon = "AddCircle", MenuName = "附加功能", ViewName = "AttachUC" }); | |||||
} | } | ||||
public void DefultNaigation() | public void DefultNaigation() | ||||
@@ -1,6 +1,9 @@ | |||||
using ModbusDemo.Device; | using ModbusDemo.Device; | ||||
using ModbusDemo.Event; | |||||
using ModbusDemo.Model; | using ModbusDemo.Model; | ||||
using ModbusDemo.Uitls; | |||||
using Prism.Commands; | using Prism.Commands; | ||||
using Prism.Events; | |||||
using Prism.Mvvm; | using Prism.Mvvm; | ||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
@@ -12,8 +15,12 @@ using System.Windows; | |||||
namespace ModbusDemo.VIewModel | namespace ModbusDemo.VIewModel | ||||
{ | { | ||||
class RegisterUCViewModel : BindableBase | |||||
class RegisterUCViewModel : BindableBase,IDisposable | |||||
{ | { | ||||
// 标记是否已释放资源 | |||||
private bool _disposed = false; | |||||
//事件聚合器 | |||||
private readonly IEventAggregator _eventAggregator; | |||||
//定义数据库操作类 | //定义数据库操作类 | ||||
private ModbusDbContext _modbusDbContext; | 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; | _serialPort = serialPort; | ||||
ReadRegisterCmm = new DelegateCommand(ReadRegister); | ReadRegisterCmm = new DelegateCommand(ReadRegister); | ||||
_modbusRTU = modbusRTU; | _modbusRTU = modbusRTU; | ||||
WriteRegisterCmm = new DelegateCommand(WriteRegister); | WriteRegisterCmm = new DelegateCommand(WriteRegister); | ||||
_modbusDbContext = modbusDbContext; | _modbusDbContext = modbusDbContext; | ||||
_eventAggregator = eventAggregator; | |||||
// 订阅事件(使用弱引用,避免内存泄漏) | |||||
_eventAggregator.GetEvent<SerialPortSettingsChangedEvent>().Subscribe( | |||||
OnSerialPortSettingsChanged, | |||||
ThreadOption.UIThread, // 在UI线程执行回调 | |||||
false);// 不使用强引用 | |||||
ModbusLogList = GetOperateRegister(); | ModbusLogList = GetOperateRegister(); | ||||
IsEnable = true; | IsEnable = true; | ||||
} | } | ||||
// 事件回调方法:当串口设置变更时触发 | |||||
private void OnSerialPortSettingsChanged() | |||||
{ | |||||
// 强制刷新SerialPortInfo属性,触发UI更新 | |||||
RaisePropertyChanged(nameof(SerialPortInfo)); | |||||
} | |||||
@@ -184,32 +203,16 @@ namespace ModbusDemo.VIewModel | |||||
/// </summary> | /// </summary> | ||||
private void ReadRegister() | 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; | return; | ||||
} | } | ||||
//使用子线程进行读取,防止ui线程卡顿 | //使用子线程进行读取,防止ui线程卡顿 | ||||
Task.Run(() => | Task.Run(() => | ||||
{ | |||||
ushort[] result = _modbusRTU.ReadRegisters(slaveAddressValue, startAddressValue, numberOfPointsValue); | |||||
{ IsEnable = false; | |||||
ushort[] result = _modbusRTU.ReadRegister(validation.slaveAddress, validation.startAddress, validation.numberOfPoints); | |||||
if (result == null) | if (result == null) | ||||
{ | { | ||||
MessageBox.Show("读取失败", "error", MessageBoxButton.OK, MessageBoxImage.Error); | MessageBox.Show("读取失败", "error", MessageBoxButton.OK, MessageBoxImage.Error); | ||||
@@ -218,10 +221,16 @@ namespace ModbusDemo.VIewModel | |||||
return; | return; | ||||
} | } | ||||
string temp = ""; | string temp = ""; | ||||
int count = 0; | |||||
foreach (var item in result) | foreach (var item in result) | ||||
{ | { | ||||
count++; | |||||
temp += item; | temp += item; | ||||
temp += " "; | temp += " "; | ||||
if (count % 25 == 0) | |||||
{ | |||||
temp += "\n"; | |||||
} | |||||
} | } | ||||
ReadResult = temp; | ReadResult = temp; | ||||
MessageBox.Show("读取成功"); | MessageBox.Show("读取成功"); | ||||
@@ -235,31 +244,17 @@ namespace ModbusDemo.VIewModel | |||||
private void WriteRegister() | 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; | return; | ||||
} | } | ||||
string[] parts = WriteData.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); | string[] parts = WriteData.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); | ||||
// 3. 创建结果数组 | |||||
// 创建结果数组 | |||||
ushort[] data = new ushort[parts.Length]; | ushort[] data = new ushort[parts.Length]; | ||||
// 4. 遍历并转换每个部分 | |||||
// 遍历并转换每个部分 | |||||
for (int i = 0; i < parts.Length; i++) | for (int i = 0; i < parts.Length; i++) | ||||
{ | { | ||||
// 尝试解析为ushort | // 尝试解析为ushort | ||||
@@ -271,7 +266,7 @@ namespace ModbusDemo.VIewModel | |||||
{ | { | ||||
MessageBox.Show($"无法将 '{parts[i]}' 转换为 ushort 类型"); | MessageBox.Show($"无法将 '{parts[i]}' 转换为 ushort 类型"); | ||||
IsEnable = true; | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
@@ -279,10 +274,11 @@ namespace ModbusDemo.VIewModel | |||||
//使用子线程来去执行操作,防止ui线程卡顿 | //使用子线程来去执行操作,防止ui线程卡顿 | ||||
Task.Run(() => | Task.Run(() => | ||||
{ | { | ||||
_modbusRTU.WriteRegisters(writeSlaveAddressValue, WriteStartAddressValue, data); | |||||
IsEnable = false; | |||||
bool issucceed = _modbusRTU.WriteRegister(validation.slaveAddress, validation.startAddress, data); | |||||
ModbusLogList = GetOperateRegister(); | ModbusLogList = GetOperateRegister(); | ||||
ModbusLog modbusLog = ModbusLogList.FirstOrDefault(); | |||||
if (modbusLog != null && modbusLog.ResponseData != string.Empty && modbusLog.ResponseData[3] != '8') | |||||
if (issucceed) | |||||
{ | { | ||||
MessageBox.Show("写入成功"); | MessageBox.Show("写入成功"); | ||||
IsEnable = true; | IsEnable = true; | ||||
@@ -293,7 +289,7 @@ namespace ModbusDemo.VIewModel | |||||
IsEnable = true; | IsEnable = true; | ||||
} | } | ||||
}); | }); | ||||
@@ -307,8 +303,50 @@ namespace ModbusDemo.VIewModel | |||||
return _modbusDbContext.ModbusLog | return _modbusDbContext.ModbusLog | ||||
.Where(log => log.OperationType == "读寄存器" || log.OperationType == "写寄存器") | .Where(log => log.OperationType == "读寄存器" || log.OperationType == "写寄存器") | ||||
.OrderByDescending(log => log.Time) // 按时间倒序,最新的在前 | .OrderByDescending(log => log.Time) // 按时间倒序,最新的在前 | ||||
.Take(20) // 只取前20条记录 | |||||
.Take(30) // 只取前20条记录 | |||||
.ToList(); // 执行查询 | .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,5 +1,7 @@ | |||||
using ModbusDemo.Uitls; | |||||
using ModbusDemo.Event; | |||||
using ModbusDemo.Uitls; | |||||
using Prism.Commands; | using Prism.Commands; | ||||
using Prism.Events; | |||||
using Prism.Mvvm; | using Prism.Mvvm; | ||||
using Prism.Regions; | using Prism.Regions; | ||||
using System; | using System; | ||||
@@ -9,18 +11,26 @@ using System.Linq; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Windows; | using System.Windows; | ||||
using System.Timers; | |||||
using Timer = System.Timers.Timer; | |||||
using ModbusDemo.Device; | |||||
namespace ModbusDemo.VIewModel | namespace ModbusDemo.VIewModel | ||||
{ | { | ||||
class SettingsUCViewModel : BindableBase | class SettingsUCViewModel : BindableBase | ||||
{ | { | ||||
// | |||||
private ModbusRTU _modbusRTU; | |||||
//定时器 | |||||
private readonly Timer _checkTimer; | |||||
//事件聚合器 | |||||
private readonly IEventAggregator _eventAggregator; | |||||
//控制页面跳转 | //控制页面跳转 | ||||
private readonly IRegionManager _regionManager; | 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; | 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() | public SettingsUCViewModel() | ||||
{ | { | ||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// 从容器中获取创建的窗口 | /// 从容器中获取创建的窗口 | ||||
/// </summary> | /// </summary> | ||||
/// <param name="serialPort"></param> | /// <param name="serialPort"></param> | ||||
public SettingsUCViewModel(SerialPort serialPort, IRegionManager regionManager) | |||||
public SettingsUCViewModel(SerialPort serialPort, IRegionManager regionManager, IEventAggregator eventAggregator, ModbusRTU modbusRTU) | |||||
{ | { | ||||
_serialPort = serialPort; | _serialPort = serialPort; | ||||
ConnectionCmm = new DelegateCommand(Connection); | ConnectionCmm = new DelegateCommand(Connection); | ||||
DisConnectionCmm = new DelegateCommand(DisConnection); | DisConnectionCmm = new DelegateCommand(DisConnection); | ||||
_regionManager = regionManager; | _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() | private void DisConnection() | ||||
{ | { | ||||
if (_serialPort.IsOpen) | if (_serialPort.IsOpen) | ||||
{ | { | ||||
_checkTimer.Stop(); | |||||
_serialPort.Close(); | _serialPort.Close(); | ||||
MessageBox.Show("连接已经断开"); | |||||
} | } | ||||
MessageBox.Show("连接已经断开"); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -126,26 +170,38 @@ namespace ModbusDemo.VIewModel | |||||
_serialPort.PortName = GetComboBoxItemValue(this.PortName); | _serialPort.PortName = GetComboBoxItemValue(this.PortName); | ||||
_serialPort.BaudRate = int.Parse(GetComboBoxItemValue(this.BaudRate)); | _serialPort.BaudRate = int.Parse(GetComboBoxItemValue(this.BaudRate)); | ||||
_serialPort.Parity = this.Parity; | _serialPort.Parity = this.Parity; | ||||
if (this.StopBits == StopBits.None) | |||||
{ | |||||
this.StopBits = StopBits.One; | |||||
MessageBox.Show("当前设备不支持无停止位(None)模式,已自动切换为 1 个停止位。"); | |||||
} | |||||
_serialPort.StopBits = this.StopBits; | _serialPort.StopBits = this.StopBits; | ||||
_serialPort.DataBits = int.Parse(GetComboBoxItemValue(this.DataBits)); | _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 | try | ||||
{ | { | ||||
_serialPort.Open(); | _serialPort.Open(); | ||||
// 发布事件通知其他ViewModel | |||||
_eventAggregator.GetEvent<SerialPortSettingsChangedEvent>().Publish(); | |||||
MessageBox.Show("串口链接成功"); | MessageBox.Show("串口链接成功"); | ||||
_regionManager.Regions["ModbusRegion"].RequestNavigate("CoilUC"); | _regionManager.Regions["ModbusRegion"].RequestNavigate("CoilUC"); | ||||
_checkTimer.Start(); | |||||
} | } | ||||
catch (Exception) | catch (Exception) | ||||
{ | { | ||||
MessageBox.Show("串口已经链接,请先断开链接在尝试","warning",MessageBoxButton.OK,MessageBoxImage.Warning); | |||||
MessageBox.Show("串口已经链接或者断开,请重新检查在尝试", "warning", MessageBoxButton.OK, MessageBoxImage.Warning); | |||||
} | } | ||||
} | } | ||||
else | |||||
{ | |||||
MessageBox.Show("当前串口已经连接,请先断开,再连接"); | |||||
} | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// 处理下拉框的选择信息 | /// 处理下拉框的选择信息 | ||||
@@ -159,7 +215,7 @@ namespace ModbusDemo.VIewModel | |||||
string cleanText = displayText.Replace("System.Windows.Controls.ComboBoxItem: ", ""); | string cleanText = displayText.Replace("System.Windows.Controls.ComboBoxItem: ", ""); | ||||
return cleanText; | return cleanText; | ||||
} | } | ||||
} | } | ||||
} | } |
@@ -10,5 +10,5 @@ | |||||
"node_modules", | "node_modules", | ||||
"wwwroot" | "wwwroot" | ||||
], | ], | ||||
"ConnectionStrings": { "ConnStr": "server=.;uid=sa;pwd=123456;database=Modbus;TrustServerCertificate=true" } | |||||
"ConnectionStrings": { "ConnStr": "3UiB0x91qCfBGGZJPtN2+6jjYWJL+PmUBwDGk07o3SzhkKx7GbsAJYoj44iejWaPIXfrZZvsvR30gpEgoVxYqVXexcruhQSPCW+B/msrunY=" } | |||||
} | } |
@@ -14,7 +14,7 @@ namespace ModbusTest | |||||
{ | { | ||||
public class ModbusRTUTest | public class ModbusRTUTest | ||||
{ | { | ||||
//设计缺陷,应该使用适配器模式重写SerialPort | |||||
private SerialPort _serialport; | private SerialPort _serialport; | ||||
private Mock<ModbusDbContext> _mockDbContext; | private Mock<ModbusDbContext> _mockDbContext; | ||||
private ModbusRTU _modbusRtu; | private ModbusRTU _modbusRtu; | ||||
@@ -29,7 +29,7 @@ namespace ModbusTest | |||||
_serialport.StopBits = StopBits.One; | _serialport.StopBits = StopBits.One; | ||||
_serialport.ReadTimeout = 500; | _serialport.ReadTimeout = 500; | ||||
_serialport.WriteTimeout = 500; | _serialport.WriteTimeout = 500; | ||||
_serialport.Open(); | |||||
_serialport.Open(); | |||||
_mockDbContext = new Mock<ModbusDbContext>(); | _mockDbContext = new Mock<ModbusDbContext>(); | ||||
_modbusRtu = new ModbusRTU(_serialport, _mockDbContext.Object); | _modbusRtu = new ModbusRTU(_serialport, _mockDbContext.Object); | ||||
} | } | ||||
@@ -38,6 +38,7 @@ namespace ModbusTest | |||||
{ | { | ||||
// 确保在测试结束后释放资源 | // 确保在测试结束后释放资源 | ||||
_serialport.Dispose(); | _serialport.Dispose(); | ||||
_modbusRtu.Dispose(); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// 测试线圈读取 | /// 测试线圈读取 | ||||
@@ -47,7 +48,6 @@ namespace ModbusTest | |||||
{ | { | ||||
var result = _modbusRtu.ReadCoil(1, 300, 5); | var result = _modbusRtu.ReadCoil(1, 300, 5); | ||||
// 创建包含5个true的预期数组 | |||||
var expected = new bool[5]; | var expected = new bool[5]; | ||||
Array.Fill(expected, false); | Array.Fill(expected, false); | ||||
CollectionAssert.AreEqual(expected, result); | CollectionAssert.AreEqual(expected, result); | ||||
@@ -57,21 +57,23 @@ namespace ModbusTest | |||||
public void ReadCoil2() | public void ReadCoil2() | ||||
{ | { | ||||
var result = _modbusRtu.ReadCoil(1, 305, 5); | var result = _modbusRtu.ReadCoil(1, 305, 5); | ||||
// 创建包含5个flase的预期数组 | |||||
var expected = new bool[5]; | var expected = new bool[5]; | ||||
Array.Fill(expected, true); | Array.Fill(expected, true); | ||||
CollectionAssert.AreEqual(expected, result); | CollectionAssert.AreEqual(expected, result); | ||||
} | } | ||||
/// <summary> | |||||
/// 测试写入线圈 | |||||
/// </summary> | |||||
[Test] | [Test] | ||||
public void WriteCoil1() | public void WriteCoil1() | ||||
{ | { | ||||
var data = new bool[5]; | var data = new bool[5]; | ||||
Array.Fill(data, true); | Array.Fill(data, true); | ||||
_modbusRtu.WriteCoil(1, 310, data); | _modbusRtu.WriteCoil(1, 310, data); | ||||
// 创建包含5个true的预期数组 | |||||
var expected = _modbusRtu.ReadCoil(1, 310, 5); | var expected = _modbusRtu.ReadCoil(1, 310, 5); | ||||
CollectionAssert.AreEqual(data, expected); | CollectionAssert.AreEqual(data, expected); | ||||
@@ -85,19 +87,21 @@ namespace ModbusTest | |||||
var data = new bool[5]; | var data = new bool[5]; | ||||
Array.Fill(data, false); | Array.Fill(data, false); | ||||
_modbusRtu.WriteCoil(1, 315, data); | _modbusRtu.WriteCoil(1, 315, data); | ||||
// 创建包含5个flase的预期数组 | |||||
var expected = _modbusRtu.ReadCoil(1, 315, 5); | var expected = _modbusRtu.ReadCoil(1, 315, 5); | ||||
CollectionAssert.AreEqual(data, expected); | CollectionAssert.AreEqual(data, expected); | ||||
} | } | ||||
/// <summary> | |||||
/// 测试读取寄存器 | |||||
/// </summary> | |||||
[Test] | [Test] | ||||
public void ReadRegister1() | public void ReadRegister1() | ||||
{ | { | ||||
ushort[] data = new ushort[5]; | ushort[] data = new ushort[5]; | ||||
Array.Fill(data, (ushort)0); | Array.Fill(data, (ushort)0); | ||||
var expected = _modbusRtu.ReadRegisters(1, 300, 5); | |||||
var expected = _modbusRtu.ReadRegister(1, 300, 5); | |||||
CollectionAssert.AreEqual(data, expected); | CollectionAssert.AreEqual(data, expected); | ||||
} | } | ||||
@@ -108,21 +112,23 @@ namespace ModbusTest | |||||
{ | { | ||||
ushort[] data = new ushort[5]; | ushort[] data = new ushort[5]; | ||||
Array.Fill(data, (ushort)1); | Array.Fill(data, (ushort)1); | ||||
var expected = _modbusRtu.ReadRegisters(1, 305, 5); | |||||
var expected = _modbusRtu.ReadRegister(1, 305, 5); | |||||
CollectionAssert.AreEqual(data, expected); | CollectionAssert.AreEqual(data, expected); | ||||
} | } | ||||
/// <summary> | |||||
/// 测试写入寄存器 | |||||
/// </summary> | |||||
[Test] | [Test] | ||||
public void WriteRegister1() | public void WriteRegister1() | ||||
{ | { | ||||
ushort[] data = new ushort[5]; | ushort[] data = new ushort[5]; | ||||
Array.Fill(data, (ushort)1); | 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); | CollectionAssert.AreEqual(data, expected); | ||||
} | } | ||||
@@ -133,9 +139,110 @@ namespace ModbusTest | |||||
ushort[] data = new ushort[5]; | ushort[] data = new ushort[5]; | ||||
Array.Fill(data, (ushort)2); | 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); | 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); | |||||
//} | |||||
} | |||||
@@ -1,3 +1,5 @@ | |||||
//using ModbusDemo.Enum; | |||||
namespace ModbusTest | namespace ModbusTest | ||||
{ | { | ||||
public class Tests | public class Tests | ||||
@@ -10,7 +12,10 @@ namespace ModbusTest | |||||
[Test] | [Test] | ||||
public void Test1() | 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(); | |||||
} | } | ||||
} | } | ||||
} | } |