You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

617 lines
20 KiB

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TouchSocket.Core;
using TouchSocket.SerialPorts;
using TouchSocket.Sockets;
namespace SunlightCentralizedControlManagement_SCCM_.UserClass
{
#region 基础数据结构
/// <summary>
/// 485 设备地址
/// </summary>
public struct DeviceAddress
{
public byte Value { get; }
public DeviceAddress(byte address)
{
if (address == 0) throw new ArgumentException("设备地址不能为0(广播地址)");
Value = address;
}
public static implicit operator byte(DeviceAddress address) => address.Value;
public static implicit operator DeviceAddress(byte address) => new DeviceAddress(address);
public override string ToString() => $"0x{Value:X2}";
}
/// <summary>
/// 485 命令帧
/// </summary>
public class CommandFrame
{
public DeviceAddress DeviceAddress { get; set; }
public byte FunctionCode { get; set; }
public byte[] Data { get; set; }
public ushort CRC { get; set; }
public byte[] ToBytes()
{
// 实现帧序列化逻辑,包括CRC计算
var dataLength = Data?.Length ?? 0;
var frame = new byte[3 + dataLength + 2]; // 地址+功能码+数据+CRC
frame[0] = DeviceAddress;
frame[1] = FunctionCode;
if (dataLength > 0)
Array.Copy(Data, 0, frame, 2, dataLength);
// 计算CRC (这里使用Modbus CRC算法示例)
CRC = CalculateCRC(frame, 0, frame.Length - 2);
frame[frame.Length - 2] = (byte)(CRC & 0xFF);
frame[frame.Length - 1] = (byte)(CRC >> 8);
return frame;
}
private ushort CalculateCRC(byte[] data, int offset, int length)
{
// 简化的CRC计算示例,实际应根据协议实现
ushort crc = 0xFFFF;
for (int i = offset; i < offset + length; i++)
{
crc ^= data[i];
for (int j = 0; j < 8; j++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
}
/// <summary>
/// 响应帧
/// </summary>
public class ResponseFrame
{
public DeviceAddress DeviceAddress { get; set; }
public byte FunctionCode { get; set; }
public byte[] Data { get; set; }
public ushort CRC { get; set; }
public bool IsValid { get; set; }
public string Error { get; set; }
public static ResponseFrame FromBytes(byte[] data)
{
// 实现帧解析逻辑,包括CRC验证
var frame = new ResponseFrame();
if (data.Length < 4) // 最小帧长度
{
frame.IsValid = false;
frame.Error = "帧长度不足";
return frame;
}
frame.DeviceAddress = data[0];
frame.FunctionCode = data[1];
// 检查CRC
ushort receivedCRC = (ushort)(data[data.Length - 1] << 8 | data[data.Length - 2]);
ushort calculatedCRC = CalculateCRC(data, 0, data.Length - 2);
if (receivedCRC != calculatedCRC)
{
frame.IsValid = false;
frame.Error = "CRC校验失败";
return frame;
}
// 提取数据部分
if (data.Length > 4)
{
frame.Data = new byte[data.Length - 4];
Array.Copy(data, 2, frame.Data, 0, frame.Data.Length);
}
frame.IsValid = true;
return frame;
}
private static ushort CalculateCRC(byte[] data, int offset, int length)
{
// 与CommandFrame相同的CRC算法
ushort crc = 0xFFFF;
for (int i = offset; i < offset + length; i++)
{
crc ^= data[i];
for (int j = 0; j < 8; j++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
}
/// <summary>
/// 通信任务
/// </summary>
public class CommunicationTask
{
public DeviceAddress DeviceAddress { get; set; }
public CommandFrame Command { get; set; }
public TaskCompletionSource<ResponseFrame> CompletionSource { get; set; }
public int TimeoutMs { get; set; } = 1000;
public int RetryCount { get; set; } = 3;
}
#endregion
#region 异常类
public class RS485CommunicationException : Exception
{
public DeviceAddress DeviceAddress { get; }
public byte FunctionCode { get; }
public RS485CommunicationException(DeviceAddress address, byte functionCode, string message)
: base($"设备 {address} 功能码 0x{functionCode:X2} 通信失败: {message}")
{
DeviceAddress = address;
FunctionCode = functionCode;
}
}
public class RS485TimeoutException : RS485CommunicationException
{
public RS485TimeoutException(DeviceAddress address, byte functionCode)
: base(address, functionCode, "响应超时")
{
}
}
public class RS485CRCException : RS485CommunicationException
{
public RS485CRCException(DeviceAddress address, byte functionCode)
: base(address, functionCode, "CRC校验失败")
{
}
}
#endregion
#region 主站实现
/// <summary>
/// 485 主站实现
/// </summary>
public class RS485MasterStation : IDisposable
{
private readonly SerialPortClient _serialPortClient;
private readonly ConcurrentDictionary<DeviceAddress, DeviceState> _deviceStates;
private readonly ConcurrentQueue<CommunicationTask> _taskQueue;
private readonly CancellationTokenSource _cancellationTokenSource;
private Task _processingTask;
private bool _isDisposed = false;
// 响应超时时间(毫秒)
public int ResponseTimeout { get; set; } = 1000;
// 帧间延迟(毫秒)
public int InterFrameDelay { get; set; } = 10;
// 最大重试次数
public int MaxRetries { get; set; } = 3;
/// <summary>
/// 设备状态
/// </summary>
private class DeviceState
{
public DeviceAddress Address { get; set; }
public DateTime LastCommunication { get; set; }
public bool IsOnline { get; set; }
public int TimeoutCount { get; set; }
}
public RS485MasterStation(string portName, int baudRate = 9600)
{
_serialPortClient = new SerialPortClient();
_deviceStates = new ConcurrentDictionary<DeviceAddress, DeviceState>();
_taskQueue = new ConcurrentQueue<CommunicationTask>();
_cancellationTokenSource = new CancellationTokenSource();
// 配置串口
_serialPortClient.Setup(new TouchSocketConfig()
.SetSerialPortOption(new SerialPortOption()
{
PortName = portName,
BaudRate = baudRate,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One
}));
// 注册数据接收事件
_serialPortClient.Received = OnDataReceived;
}
/// <summary>
/// 启动主站
/// </summary>
public void Start()
{
_serialPortClient.Connect();
_processingTask = Task.Run(ProcessQueue, _cancellationTokenSource.Token);
Console.WriteLine("485主站已启动");
}
/// <summary>
/// 停止主站
/// </summary>
public void Stop()
{
_cancellationTokenSource.Cancel();
_serialPortClient.Close();
Console.WriteLine("485主站已停止");
}
/// <summary>
/// 添加设备到监控列表
/// </summary>
public void AddDevice(DeviceAddress address)
{
_deviceStates[address] = new DeviceState
{
Address = address,
LastCommunication = DateTime.MinValue,
IsOnline = false,
TimeoutCount = 0
};
Console.WriteLine($"已添加设备: {address}");
}
/// <summary>
/// 从监控列表移除设备
/// </summary>
public void RemoveDevice(DeviceAddress address)
{
_deviceStates.TryRemove(address, out _);
Console.WriteLine($"已移除设备: {address}");
}
/// <summary>
/// 发送命令到设备
/// </summary>
public Task<ResponseFrame> SendCommandAsync(DeviceAddress address, byte functionCode, byte[] data = null, int timeoutMs = 1000, int retryCount = 3)
{
var command = new CommandFrame
{
DeviceAddress = address,
FunctionCode = functionCode,
Data = data ?? new byte[0]
};
var task = new CommunicationTask
{
DeviceAddress = address,
Command = command,
CompletionSource = new TaskCompletionSource<ResponseFrame>(),
TimeoutMs = timeoutMs,
RetryCount = retryCount
};
_taskQueue.Enqueue(task);
return task.CompletionSource.Task;
}
/// <summary>
/// 读取保持寄存器
/// </summary>
public async Task<byte[]> ReadHoldingRegistersAsync(DeviceAddress address, ushort startAddress, ushort numberOfRegisters)
{
var data = new byte[4];
data[0] = (byte)(startAddress >> 8); // 起始地址高字节
data[1] = (byte)(startAddress & 0xFF); // 起始地址低字节
data[2] = (byte)(numberOfRegisters >> 8); // 寄存器数量高字节
data[3] = (byte)(numberOfRegisters & 0xFF); // 寄存器数量低字节
var response = await SendCommandAsync(address, 0x03, data);
if (!response.IsValid)
throw new RS485CommunicationException(address, 0x03, response.Error);
return response.Data;
}
/// <summary>
/// 写入单个寄存器
/// </summary>
public async Task WriteSingleRegisterAsync(DeviceAddress address, ushort registerAddress, ushort value)
{
var data = new byte[4];
data[0] = (byte)(registerAddress >> 8); // 寄存器地址高字节
data[1] = (byte)(registerAddress & 0xFF); // 寄存器地址低字节
data[2] = (byte)(value >> 8); // 值高字节
data[3] = (byte)(value & 0xFF); // 值低字节
var response = await SendCommandAsync(address, 0x06, data);
if (!response.IsValid)
throw new RS485CommunicationException(address, 0x06, response.Error);
}
/// <summary>
/// 处理任务队列
/// </summary>
private async Task ProcessQueue()
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
if (_taskQueue.TryDequeue(out var task))
{
await ProcessTask(task);
}
else
{
// 队列为空,执行设备轮询
await PollDevices();
await Task.Delay(100, _cancellationTokenSource.Token); // 短暂休眠
}
}
}
/// <summary>
/// 处理单个通信任务
/// </summary>
private async Task ProcessTask(CommunicationTask task)
{
int retryCount = 0;
ResponseFrame response = null;
while (retryCount < task.RetryCount && !_cancellationTokenSource.Token.IsCancellationRequested)
{
try
{
// 发送命令
var commandData = task.Command.ToBytes();
_serialPortClient.Send(commandData);
// 等待响应
var timeoutTask = Task.Delay(task.TimeoutMs, _cancellationTokenSource.Token);
var responseTask = task.CompletionSource.Task;
var completedTask = await Task.WhenAny(responseTask, timeoutTask);
if (completedTask == responseTask)
{
response = await responseTask;
if (response.IsValid)
{
UpdateDeviceState(task.DeviceAddress, true);
task.CompletionSource.SetResult(response);
return;
}
else
{
throw new RS485CRCException(task.DeviceAddress, task.Command.FunctionCode);
}
}
else
{
throw new RS485TimeoutException(task.DeviceAddress, task.Command.FunctionCode);
}
}
catch (RS485CommunicationException ex)
{
retryCount++;
UpdateDeviceState(task.DeviceAddress, false);
if (retryCount >= task.RetryCount)
{
task.CompletionSource.SetException(ex);
return;
}
// 等待一段时间后重试
await Task.Delay(InterFrameDelay, _cancellationTokenSource.Token);
}
}
}
/// <summary>
/// 轮询所有设备状态
/// </summary>
private async Task PollDevices()
{
foreach (var deviceState in _deviceStates.Values.ToList())
{
// 检查设备是否需要轮询(例如,超过一定时间未通信)
if ((DateTime.Now - deviceState.LastCommunication).TotalSeconds > 30)
{
try
{
// 发送诊断命令(如读取设备状态)
await ReadHoldingRegistersAsync(deviceState.Address, 0, 1);
Console.WriteLine($"设备 {deviceState.Address} 轮询成功");
}
catch (Exception ex)
{
Console.WriteLine($"设备 {deviceState.Address} 轮询失败: {ex.Message}");
}
// 添加帧间延迟
await Task.Delay(InterFrameDelay, _cancellationTokenSource.Token);
}
}
}
/// <summary>
/// 数据接收处理
/// </summary>
private Task OnDataReceived(ISerialPortClient client, ReceivedDataEventArgs e)
{
try
{
// 解析响应帧
var response = ResponseFrame.FromBytes(e.ByteBlock.ReadBytesPackage());
if (response.IsValid)
{
// 查找匹配的任务并设置结果
foreach (var task in _taskQueue.Where(t => t.DeviceAddress.Value == response.DeviceAddress.Value))
{
task.CompletionSource.TrySetResult(response);
}
UpdateDeviceState(response.DeviceAddress, true);
}
else
{
Console.WriteLine($"收到无效响应: {response.Error}");
}
}
catch (Exception ex)
{
Console.WriteLine($"处理接收数据时出错: {ex.Message}");
}
return Task.CompletedTask;
}
/// <summary>
/// 更新设备状态
/// </summary>
private void UpdateDeviceState(DeviceAddress address, bool communicationSuccess)
{
if (_deviceStates.TryGetValue(address, out var state))
{
state.LastCommunication = DateTime.Now;
if (communicationSuccess)
{
state.IsOnline = true;
state.TimeoutCount = 0;
}
else
{
state.TimeoutCount++;
if (state.TimeoutCount > MaxRetries)
{
state.IsOnline = false;
Console.WriteLine($"设备 {address} 已离线");
}
}
}
}
/// <summary>
/// 获取所有设备状态
/// </summary>
public Dictionary<DeviceAddress, bool> GetDeviceStatuses()
{
return _deviceStates.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.IsOnline);
}
/// <summary>
/// 资源释放
/// </summary>
public void Dispose()
{
if (!_isDisposed)
{
_isDisposed = true;
_cancellationTokenSource.Cancel();
_serialPortClient.SafeDispose();
_cancellationTokenSource.Dispose();
}
}
}
#endregion
#region 使用示例
class Program
{
static async Task Main(string[] args)
{
// 创建485主站实例
using (var master = new RS485MasterStation("COM1", 9600))
try
{
// 添加要监控的设备
master.AddDevice(0x01);
master.AddDevice(0x02);
master.AddDevice(0x03);
// 启动主站
master.Start();
// 等待一段时间让主站初始化
await Task.Delay(1000);
// 示例1: 读取设备1的保持寄存器
try
{
var data = await master.ReadHoldingRegistersAsync(0x01, 0, 10);
Console.WriteLine($"成功读取设备1的寄存器数据: {BitConverter.ToString(data)}");
}
catch (RS485CommunicationException ex)
{
Console.WriteLine(ex.Message);
}
// 示例2: 向设备2写入寄存器值
try
{
await master.WriteSingleRegisterAsync(0x02, 0x1000, 0x1234);
Console.WriteLine("成功向设备2写入寄存器值");
}
catch (RS485CommunicationException ex)
{
Console.WriteLine(ex.Message);
}
// 显示所有设备状态
var statuses = master.GetDeviceStatuses();
foreach (var status in statuses)
{
Console.WriteLine($"设备 {status.Key}: {(status.Value ? "线" : "线")}");
}
// 保持运行
Console.WriteLine("按任意键退出...");
Console.ReadKey();
}
finally
{
master.Stop();
}
}
}
#endregion
}