using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace GeneralGommunication { /// /// 完全不用, 时间间隔不能确保对,导致经常数据异常。 ASCII 好很多 /// public class ModbusRtu : IModbusBase { /// /// 通讯中 /// public bool IsInComm { get; private set; } /// /// 指令成功回复时,回复的耗时 /// public TimeSpan Elapsed { get; private set; } /// /// 10s 内最大回复的耗时 /// public int MaxElapsedms10s { get; private set; } public ErrorType Error { get; private set; } public bool IsError => !string.IsNullOrEmpty(ErrMsg); public string ErrMsg { get; private set; } /// /// 接收的数据 /// public byte[] RXDs { get; private set; } public IGeneralComm serial; /// /// 数据包间隔时间,必须确保 接收包 与 发送包之间等待 3.5T以上时间 /// public int PackIntervalms { get; private set; } = 10; public bool IsCommConnected { get; private set; } public string CommErrMsg { get; private set; } Stopwatch stopwatch_lastRecPack = new Stopwatch(); TimeSpan maxElapsed; Stopwatch stopwatch_maxElapsed = new Stopwatch(); public ModbusRtu() { } public void Init(IGeneralComm serial) { this.serial = serial; this.serial.DataReceived += Serial_DataReceived; Misc.BindingOperations.SetBinding(serial, nameof(serial.IsConnected), this, nameof(IsCommConnected)); Misc.BindingOperations.SetBinding(serial, nameof(serial.ErrMsg), this, nameof(CommErrMsg)); } #region 功能 /// /// 读多个 COIL /// /// 设备号 /// 参数首地址 /// 线圈个数 /// 输出线圈状态 /// public bool Do_01(byte device_no, int addr, int cnt, out IEnumerable values) { values = null; if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } Pack_Proto p = new Pack_Proto() { device_no = device_no, func = 0x01, addr = addr, cnt = cnt, expRecBytes = (int)Math.Ceiling(cnt / 8.0) + 5 }; if (!SendAndWaitForReponse(p)) return false; List blist = new List(); int byte_len = p.sendBuf[2]; int index = 3; for (int i = 0; i < byte_len; i++) { byte d = p.sendBuf[index]; for (int j = 0; j < 8; j++) { if ((d & (1 << j)) != 0) blist.Add(true); else blist.Add(false); if (blist.Count() >= cnt) { //完成 break; } } index++; } values = blist; return true; } Pack_Proto currProto; public event PropertyChangedEventHandler PropertyChanged; private void Serial_DataReceived(IGeneralComm sender, byte[] buf) { stopwatch_lastRecPack.Restart(); if (!IsInComm) { //异常,不应该接收到东西 return; } Pack_Proto pack = currProto; pack.recBuf.AddRange(buf); if (pack.recBuf.Count() >= 3) { if (pack.recBuf[0] != pack.device_no) { pack.error = ErrorType.Unknown; pack.errMsg = $"接收的SLAVE地址{pack.recBuf[0]}与发送的{pack.device_no}不一致"; IsInComm = false; pack.cancellationTokenSource.Cancel(); return; } if (pack.recBuf[1] != pack.func) { pack.error = ErrorType.Unknown; pack.errMsg = $"错误码: {pack.recBuf[1]} 异常码: {pack.recBuf[2]}"; IsInComm = false; pack.cancellationTokenSource.Cancel(); return; } if (pack.recBuf.Count() >= pack.expRecBytes) { UInt16 crc = pack.recBuf.CRC16(0, pack.expRecBytes - 2); UInt16 rec_crc = pack.recBuf.ToUInt16_Little_Endian(pack.expRecBytes - 2); if (crc != rec_crc) { pack.error = ErrorType.CRCError; pack.errMsg = $"指令码:{pack.func:X2} CRC 校验出错 接收:{rec_crc:X4} 计算:{crc:X4}"; } else { pack.funcData.AddRange(pack.recBuf.Skip(2).Take(pack.expRecBytes - 4)); pack.error = ErrorType.OK; } IsInComm = false; pack.cancellationTokenSource.Cancel(); return; } } } bool SendAndWaitForReponse(Pack_Proto p) { currProto = p; var data = p.sendBuf; data.Add(p.device_no); data.Add(p.func); data.AddRange(p.funcData); UInt16 crc = data.CRC16(0, data.Count()); data.AddRange(crc.GetBytes_Little_Endian()); p.funcData.Clear(); while (stopwatch_lastRecPack.IsRunning) { //上次接收到现在距离不到 3.5T 间隔,不能发送 //等待 int delayms = (int)(PackIntervalms - stopwatch_lastRecPack.ElapsedMilliseconds); if (delayms > 0) Task.Delay(delayms).Wait(); else break; } stopwatch_lastRecPack.Stop(); serial.Write(p.sendBuf.ToArray()); if (!serial.IsConnected) { Error = ErrorType.Disconnected; ErrMsg = serial.ErrMsg; return false; } IsInComm = true; p.stopwatch.Start(); try { int ms = 300;// PackIntervalms * 2; Task.Delay(ms, p.cancellationTokenSource.Token).Wait(); } catch (Exception e) { //被打断了,肯定通讯完成 } p.stopwatch.Stop(); IsInComm = false; RXDs = p.recBuf.ToArray(); if (p.error == ErrorType.Idle) { //通讯超时 Error = ErrorType.TimeOut; ErrMsg = "通讯超时"; return false; } else { Error = p.error; ErrMsg = p.errMsg; if (p.error == ErrorType.OK) { Elapsed = p.stopwatch.Elapsed; if (Elapsed > maxElapsed) { maxElapsed = Elapsed; } if (!stopwatch_maxElapsed.IsRunning) { stopwatch_maxElapsed.Start(); } else if (stopwatch_maxElapsed.Elapsed > TimeSpan.FromSeconds(10)) { stopwatch_maxElapsed.Restart(); MaxElapsedms10s = (int)maxElapsed.TotalMilliseconds; maxElapsed = Elapsed; } return true; } else return false; } } public class Pack_Proto { public byte device_no; public byte func; public int addr; public int cnt; public int expRecBytes; public List sendBuf = new List(); public List recBuf = new List(); public List funcData = new List(); public ErrorType error; public string errMsg = ""; public Stopwatch stopwatch = new Stopwatch(); public System.Threading.CancellationTokenSource cancellationTokenSource = new System.Threading.CancellationTokenSource(); } /// /// 读多个Holding REGs /// /// /// /// /// public bool Do_03(byte device_no, int addr, int cnt, out List values) { values = null; if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } Pack_Proto p = new Pack_Proto() { device_no = device_no, func = 0x03, addr = addr, cnt = cnt, expRecBytes = cnt * 2 + 5 }; p.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian()); p.funcData.AddRange(((UInt16)cnt).GetBytes_Big_endian()); if (!SendAndWaitForReponse(p)) return false; { List blist = new List(); int index = 1; while (index < p.funcData.Count()) { blist.Add(p.funcData.ToUInt16_Big_Endian(index)); index += 2; } values = blist; } return true; } /// /// Write Single Coil /// /// /// /// public bool Do_05(byte device_no, int addr, bool dat) { if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } Pack_Proto p = new Pack_Proto() { device_no = device_no, addr = addr, func = 0x05, expRecBytes = 6 }; p.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian()); UInt16 d = (dat) ? (UInt16)0xff00 : (UInt16)0; p.funcData.AddRange(d.GetBytes_Big_endian()); return SendAndWaitForReponse(p); } /// /// Write Single REG /// /// /// /// public bool Do_06(byte device_no, int addr, UInt16 dat) { if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } Pack_Proto p = new Pack_Proto() { device_no = device_no, addr = addr, func = 0x06, expRecBytes = 8 }; p.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian()); p.funcData.AddRange(((UInt16)dat).GetBytes_Big_endian()); return SendAndWaitForReponse(p); } /// /// 写多个coil /// /// /// /// public bool Do_0F(byte device_no, int addr, List datas) { if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } Pack_Proto p = new Pack_Proto() { device_no = device_no, addr = addr, func = 0x0F, expRecBytes = 6 }; p.funcData.AddRange(((UInt16)p.addr).GetBytes_Big_endian()); p.funcData.AddRange(((UInt16)datas.Count()).GetBytes_Big_endian()); p.funcData.Add((byte)(Math.Ceiling(datas.Count() / 8.0))); byte b = 0; int j = 0; for (int i = 0; i < datas.Count(); i++) { if (datas.ElementAt(i)) { b |= (byte)(1 << i); } j++; if (j == 8) { p.funcData.Add(b); b = 0; j = 0; } } if (j != 0) p.funcData.Add(b); return SendAndWaitForReponse(p); } /// /// 写多个REG /// /// /// /// public bool Do_10(byte device_no, int addr, IEnumerable datas) { if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } Pack_Proto p = new Pack_Proto() { device_no = device_no, addr = addr, func = 0x10, expRecBytes = 8 }; p.funcData.AddRange(((UInt16)p.addr).GetBytes_Big_endian()); p.funcData.AddRange(((UInt16)datas.Count()).GetBytes_Big_endian()); p.funcData.Add((byte)(datas.Count() * 2)); for (int i = 0; i < datas.Count(); i++) { p.funcData.AddRange(datas.ElementAt(i).GetBytes_Big_endian()); } return SendAndWaitForReponse(p); } #endregion } }