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
}
}