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 基础数据结构 /// /// 485 设备地址 /// 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}"; } /// /// 485 命令帧 /// 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; } } /// /// 响应帧 /// 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; } } /// /// 通信任务 /// public class CommunicationTask { public DeviceAddress DeviceAddress { get; set; } public CommandFrame Command { get; set; } public TaskCompletionSource 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 主站实现 /// /// 485 主站实现 /// public class RS485MasterStation : IDisposable { private readonly SerialPortClient _serialPortClient; private readonly ConcurrentDictionary _deviceStates; private readonly ConcurrentQueue _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; /// /// 设备状态 /// 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(); _taskQueue = new ConcurrentQueue(); _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; } /// /// 启动主站 /// public void Start() { _serialPortClient.Connect(); _processingTask = Task.Run(ProcessQueue, _cancellationTokenSource.Token); Console.WriteLine("485主站已启动"); } /// /// 停止主站 /// public void Stop() { _cancellationTokenSource.Cancel(); _serialPortClient.Close(); Console.WriteLine("485主站已停止"); } /// /// 添加设备到监控列表 /// public void AddDevice(DeviceAddress address) { _deviceStates[address] = new DeviceState { Address = address, LastCommunication = DateTime.MinValue, IsOnline = false, TimeoutCount = 0 }; Console.WriteLine($"已添加设备: {address}"); } /// /// 从监控列表移除设备 /// public void RemoveDevice(DeviceAddress address) { _deviceStates.TryRemove(address, out _); Console.WriteLine($"已移除设备: {address}"); } /// /// 发送命令到设备 /// public Task 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(), TimeoutMs = timeoutMs, RetryCount = retryCount }; _taskQueue.Enqueue(task); return task.CompletionSource.Task; } /// /// 读取保持寄存器 /// public async Task 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; } /// /// 写入单个寄存器 /// 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); } /// /// 处理任务队列 /// 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); // 短暂休眠 } } } /// /// 处理单个通信任务 /// 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); } } } /// /// 轮询所有设备状态 /// 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); } } } /// /// 数据接收处理 /// 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; } /// /// 更新设备状态 /// 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} 已离线"); } } } } /// /// 获取所有设备状态 /// public Dictionary GetDeviceStatuses() { return _deviceStates.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.IsOnline); } /// /// 资源释放 /// 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 }