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
{
///
/// 通讯中
///
public bool IsInComm { get; private set; }
///
/// 指令成功回复时,回复的耗时
///
public TimeSpan Elapsed { get; private set; }
///
/// 10s 内最大回复的耗时
///
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; }
///
/// 接收的数据
///
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 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 功能
///
/// 读多个 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;
}
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 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;
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 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 ascii, int index, int len)
{
if (len % 2 != 0)
return null;
List hex = new List();
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 hex)
{
return HexToAscii(hex, 0, hex.Count());
}
byte[] HexToAscii(IEnumerable 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();
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 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;
}
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 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;
}
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);
}
///
/// Write Single REG
///
///
///
///
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);
}
///
/// 写多个coil
///
///
///
///
public bool Do_0F(byte device_no, int addr, List 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);
}
///
/// 写多个REG
///
///
///
///
public bool Do_10(byte device_no, int addr, IEnumerable 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
}
}