using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; namespace GeneralGommunication { public class ModbusAscii : IModbusBase { /// <summary> /// 通讯中 /// </summary> public bool IsInComm { get; private set; } /// <summary> /// 指令成功回复时,回复的耗时 /// </summary> public TimeSpan Elapsed { get; private set; } /// <summary> /// 10s 内最大回复的耗时 /// </summary> public int MaxElapsedms10s { get; private set; } public DateTime LastCommTime { get; private set; } public ErrorType Error { get; private set; } public bool IsError => !string.IsNullOrEmpty(ErrMsg); public string ErrMsg { get; private set; } /// <summary> /// 接收的数据 /// </summary> public byte[] RXDs { get; private set; } public IGeneralComm serial; /// <summary> /// 数据包间隔时间,必须确保 接收包 与 发送包之间等待 3.5T以上时间 /// </summary> 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 ModbusAscii() { } 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 功能 /// <summary> /// 读多个 COIL /// </summary> /// <param name="device_no">设备号</param> /// <param name="addr">参数首地址</param> /// <param name="cnt">线圈个数</param> /// <param name="values">输出线圈状态</param> /// <returns></returns> public bool Do_01(byte device_no, int addr, int cnt, out IEnumerable<bool> values) { values = null; if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } int expRecBytes = (int)Math.Ceiling(cnt / 8.0) + 5; expRecBytes -= 1;// lrc 比 crc 少一位 expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa Pack_Proto p = new Pack_Proto() { device_no = device_no, func = 0x01, addr = addr, cnt = cnt, expRecBytes = expRecBytes }; if (!SendAndWaitForReponse(p)) return false; List<bool> blist = new List<bool>(); 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; int index = pack.recBuf.Count(); pack.recBuf.AddRange(buf); for (int i = 0; i < buf.Count(); i++) { if (Modbus_Rec(index + i)) { //处理完,删除全部接收 pack.recBuf.Clear(); return; } } } bool Modbus_Rec(int index) { Pack_Proto pack = currProto; var buf = pack.recBuf; if (index == 0) { if (buf[index] != ':') { //包头错,可以删除 pack.error = ErrorType.Unknown; pack.errMsg = $"指令码:{pack.func:X2} 包头错"; //呼醒 等待!! pack.cancellationTokenSource.Cancel(); return true; } } else if (index == 2) { byte? addr = Ascii2Hex(buf, 1); if (addr == null) { //异常,无法识别 pack.error = ErrorType.Unknown; pack.errMsg = $"指令码:{pack.func:X2} 地址错"; //呼醒 等待!! pack.cancellationTokenSource.Cancel(); return true; } if (addr != pack.device_no) { //地址错,可以删除 pack.error = ErrorType.Unknown; pack.errMsg = $"指令码:{pack.func:X2} 地址错"; //呼醒 等待!! pack.cancellationTokenSource.Cancel(); return true; } } else if (index == pack.expRecBytes - 1) { if (buf[index - 1] != 0x0d || buf[index] != 0x0a) { //结尾出错, 可以删除 pack.error = ErrorType.Unknown; pack.errMsg = $"指令码:{pack.func:X2} 包尾错"; //呼醒 等待!! pack.cancellationTokenSource.Cancel(); return true; } //可以解析了 //转为hex byte[] recBuf = Ascii2Hex(pack.recBuf, 1, pack.expRecBytes - 3); pack.recBuf.Clear(); pack.recBuf.AddRange(recBuf); byte lrc = recBuf.LRC8(0, recBuf.Count() - 1); byte rec_lrc = recBuf[recBuf.Count() - 1]; if (lrc != rec_lrc) { pack.error = ErrorType.CRCError; pack.errMsg = $"指令码:{pack.func:X2} LRC 校验出错 接收:{rec_lrc:X4} 计算:{lrc:X2}"; } else { pack.funcData.AddRange(pack.recBuf.Skip(2).Take(recBuf.Count() - 3)); pack.error = ErrorType.OK; } //呼醒 等待!! pack.cancellationTokenSource.Cancel(); return true; } return false; } byte? Ascii2Hex(IEnumerable<byte> ascii, int index) { string msg = System.Text.Encoding.ASCII.GetString(new byte[] { ascii.ElementAt(index), ascii.ElementAt(index + 1) }); try { return (byte)int.Parse(msg, System.Globalization.NumberStyles.HexNumber); } catch { return null; } } byte[] Ascii2Hex(IEnumerable<byte> ascii, int index, int len) { if (len % 2 != 0) return null; List<byte> hex = new List<byte>(); for (int i = 0; i < len / 2; i++) { string msg = System.Text.Encoding.ASCII.GetString(new byte[] { ascii.ElementAt(index + i * 2), ascii.ElementAt(index + i * 2 + 1) }); byte d = (byte)int.Parse(msg, System.Globalization.NumberStyles.HexNumber); hex.Add(d); } return hex.ToArray(); } byte[] HexToAscii(IEnumerable<byte> hex) { return HexToAscii(hex, 0, hex.Count()); } byte[] HexToAscii(IEnumerable<byte> hex, int index, int len) { string msg = ""; for (int i = 0; i < len; i++) { msg += hex.ElementAt(index + i).ToString("X2"); } return System.Text.Encoding.ASCII.GetBytes(msg); } bool SendAndWaitForReponse(Pack_Proto p) { currProto = p; var data = new List<byte>(); data.Add(p.device_no); data.Add(p.func); data.AddRange(p.funcData); byte lrc = data.LRC8(0, data.Count()); data.Add(lrc); //转为 ascii p.sendBuf.Add((byte)':'); p.sendBuf.AddRange(HexToAscii(data)); p.sendBuf.Add(0x0d); p.sendBuf.Add(0x0a); ; 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; LastCommTime = DateTime.Now; 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<byte> sendBuf = new List<byte>(); public List<byte> recBuf = new List<byte>(); public List<byte> funcData = new List<byte>(); public ErrorType error; public string errMsg = ""; public Stopwatch stopwatch = new Stopwatch(); public System.Threading.CancellationTokenSource cancellationTokenSource = new System.Threading.CancellationTokenSource(); } /// <summary> /// 读多个Holding REGs /// </summary> /// <param name="addr"></param> /// <param name="cnt"></param> /// <param name="values"></param> /// <returns></returns> public bool Do_03(byte device_no, int addr, int cnt, out List<UInt16> values) { values = null; if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } int expRecBytes = cnt * 2 + 5; expRecBytes -= 1;// lrc 比 crc 少一位 expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa Pack_Proto p = new Pack_Proto() { device_no = device_no, func = 0x03, addr = addr, cnt = cnt, expRecBytes = expRecBytes }; p.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian()); p.funcData.AddRange(((UInt16)cnt).GetBytes_Big_endian()); if (!SendAndWaitForReponse(p)) return false; { List<UInt16> blist = new List<UInt16>(); int index = 1; while (index < p.funcData.Count()) { blist.Add(p.funcData.ToUInt16_Big_Endian(index)); index += 2; } values = blist; } return true; } /// <summary> /// Write Single Coil /// </summary> /// <param name="addr"></param> /// <param name="data"></param> /// <returns></returns> public bool Do_05(byte device_no, int addr, bool dat) { if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } int expRecBytes = 6; expRecBytes -= 1;// lrc 比 crc 少一位 expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa Pack_Proto p = new Pack_Proto() { device_no = device_no, addr = addr, func = 0x05, expRecBytes = expRecBytes }; 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); } /// <summary> /// Write Single REG /// </summary> /// <param name="addr"></param> /// <param name="data"></param> /// <returns></returns> public bool Do_06(byte device_no, int addr, UInt16 dat) { if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } int expRecBytes = 8; expRecBytes -= 1;// lrc 比 crc 少一位 expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa Pack_Proto p = new Pack_Proto() { device_no = device_no, addr = addr, func = 0x06, expRecBytes = expRecBytes }; p.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian()); p.funcData.AddRange(((UInt16)dat).GetBytes_Big_endian()); return SendAndWaitForReponse(p); } /// <summary> /// 写多个coil /// </summary> /// <param name="addr"></param> /// <param name="datas"></param> /// <returns></returns> public bool Do_0F(byte device_no, int addr, List<bool> datas) { if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } int expRecBytes = 6; expRecBytes -= 1;// lrc 比 crc 少一位 expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa Pack_Proto p = new Pack_Proto() { device_no = device_no, addr = addr, func = 0x0F, expRecBytes = expRecBytes }; 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); } /// <summary> /// 写多个REG /// </summary> /// <param name="addr"></param> /// <param name="datas"></param> /// <returns></returns> public bool Do_10(byte device_no, int addr, IEnumerable<UInt16> datas) { if (IsInComm) { Error = ErrorType.Busy; ErrMsg = "线路忙"; return false; } int expRecBytes = 8; expRecBytes -= 1;// lrc 比 crc 少一位 expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa Pack_Proto p = new Pack_Proto() { device_no = device_no, addr = addr, func = 0x10, expRecBytes = expRecBytes }; 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 } }