1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
using Newtonsoft.Json.Linq;
using NLog;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
using static GeneralGommunication.Modbus_Transaction;
namespace GeneralGommunication
{
/// <summary>
/// 异步modbus RTU
/// </summary>
public class ModbusRtuAsync:INotifyPropertyChanged
{
public Logger logger = NLog.LogManager.GetCurrentClassLogger();
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 包出错次数
/// </summary>
public int ErrCnt { get; protected set; }
/// <summary>
/// 通讯速度测量模块
/// </summary>
public CommSpeedMeasuring Csm => csm;
/// <summary>
/// 有数据需要发送
/// </summary>
public event SendDataEventHandler SendMsgEvent;
/// <summary>
/// 对于全部 有返回的函数调用,都使用Dispatcher,使线程同步
/// </summary>
public Dispatcher Dispatcher;
List<byte> currPack = new List<byte>();
/// <summary>
/// 指令队列,必须等上1条指令回复了,才能发下条指令
/// </summary>
List<Modbus_Transaction> Transactions;
/// <summary>
/// 当前正在等待回复的指令
/// </summary>
Modbus_Transaction currTran;
CommSpeedMeasuring csm = new CommSpeedMeasuring();
/// <summary>
/// currTran 重发次数
/// </summary>
int retryCnt = 0;
List<int> deviceConnected = new List<int>();
public event DeviceConnectEventHander DeviceConnectEvent;
/// <summary>
/// 通讯中
/// </summary>
public bool IsBusy
{
get {
if (currTran != null)
return true;
if (Transactions.Count() > 0)
return true;
return false;
}
}
System.Timers.Timer timer3d5t;//通讯包的 3.5T 时间间隔。 实际只是 15ms定时而已
System.Timers.Timer timerTimeOut;//等待回复超时
public ModbusRtuAsync()
{
Transactions = new List<Modbus_Transaction>();
timer3d5t = new System.Timers.Timer();
timer3d5t.Interval = 30;//设得再小也没有用。 它只是用系统的15ms定时器触发而已。
timer3d5t.AutoReset = false;//触发一次
timer3d5t.Elapsed += Timer3d5t_Elapsed;
timerTimeOut = new System.Timers.Timer();
timerTimeOut.Interval = 500;
timerTimeOut.AutoReset = false;
timerTimeOut.Elapsed += TimerTimeOut_Elapsed;
}
public bool IsConnected(byte deviceNo)
{
if (deviceConnected.Contains(deviceNo))
return true;
return false;
}
private void Timer3d5t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
ParsePack();
}
private void TimerTimeOut_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (currTran == null)//没有数据需要发送,异常
return;
//大于1秒也没回复,异常
//重试2次
retryCnt++;
logger.Error($"TIMEOUT 第{retryCnt}次超时");
if (retryCnt >= 2)
{
//已经发送了2次,放弃
if (deviceConnected.Contains(currTran.deviceNo))
{
//之前连接是好的。
deviceConnected.Remove(currTran.deviceNo);
//触发通知
DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs()
{
deviceNo = currTran.deviceNo,
isConnected = false
});
}
//删除发送给这个设备的全部指令
Transactions.RemoveAll(t => t.deviceNo == currTran.deviceNo);
currTran = null;
retryCnt = 0;
if (Transactions.Count() != 0) //还要其它需要发送
{
byte[] msg = GetSendMsg();
SendMsgEvent?.Invoke(this,msg);
}
return;
}
else
{
//重新把指令放回队列
Transactions.Insert(0, currTran);
currTran = null;
//再发一次指令
byte[] msg = GetSendMsg();
SendMsgEvent?.Invoke(this, msg);
}
}
public void RecMsg(byte[] recBuf)
{
//停止超时检查
timerTimeOut.Stop();
//15ms后再处理。
timer3d5t.Stop();
csm.IncRec(recBuf.Count());
currPack.AddRange(recBuf);
if (logger.IsDebugEnabled) {
string msg = recBuf.ToString_Bytes2Hex();
logger.Debug($"ACK {msg}");
}
timer3d5t.Start();
}
/// <summary>
/// 获取 发送队列 第1条msg
/// </summary>
/// <returns></returns>
byte[] GetSendMsg()
{
//TODO 要处理 TimeOut / ParseFuncPack / GetSendMsg 线性同步问题
if (currTran == null)
{
//当前没有指令正在发送
if (Transactions.Count() == 0)//队列没有需要发送的指令
return null;
currTran = Transactions.First();
Transactions.RemoveAt(0);
}
else
{
//发送出去中,等待回复
return null;
}
var tran = currTran;
//调试: 打印发送信息
if(logger.IsDebugEnabled)
{
string msg = tran.sendBuf.ToString_Bytes2Hex();
if (tran.desription == null)
logger.Debug($"REQ {msg}");
else
logger.Debug($"REQ {tran.desription} {msg}");
}
//开始计时
timerTimeOut.Start();
return tran.sendBuf.ToArray();
}
/// <summary>
/// 复位全部状态,通常由于通讯模块检测到连接断开导致的
/// </summary>
public void ResetMsg()
{
Transactions.Clear();
currTran = null;
retryCnt = 0;
timer3d5t.Stop();
timerTimeOut.Stop();
csm.Reset();
//触发通知
var isConneced = deviceConnected.ToArray();
deviceConnected.Clear();
foreach (var deviceNo in isConneced)
{
//触发通知
DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs()
{
deviceNo = deviceNo,
isConnected = false
});
}
}
/// <summary>
/// 已经收完一份数据,不会再有更多,包解析
/// </summary>
void ParsePack()
{
if (currTran == null)
{
//没有请求。。。
currPack.Clear();
return;
}
if (currPack.Count() < 3)
{
//异常 数据太少 通知重发
logger.Error($"ACK 接收数据小于3个byte");
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
return;
}
if (currPack[0] != currTran.deviceNo)
{
logger.Error($"ACK 接收的SLAVE地址{currPack[0]}与发送的{currTran.deviceNo}不一致");
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
return;
}
if (currPack[1] != currTran.func)
{
logger.Error($"ACK 错误码:{currPack[1]:X2} 异常码:{currPack[2]:X2}");
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
return;
}
if (currPack.Count() < currTran.expRecBytes)
{
logger.Error($"ACK 数据还没接收完");
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
return;
}
UInt16 crc = currPack.CRC16(0, currTran.expRecBytes - 2);
UInt16 rec_crc = currPack.ToUInt16_Little_Endian(currTran.expRecBytes - 2);
if (crc != rec_crc)
{
logger.Error($"ACK 指令码:{currTran.func:X2} CRC 校验出错 接收:{rec_crc:X4} 计算:{crc:X4}");
//DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs() { deviceNo = currTran.deviceNo, isConnected = false, errMsg = errMsg });
ErrCnt++;
currPack.Clear();
TimerTimeOut_Elapsed(null, null);
return;
}
//成功解析出一个包
currTran.funcData.AddRange(currPack.Skip(2).Take(currTran.expRecBytes - 4));
csm.IncPack(1);
currPack.Clear();
//触发连接通知
if (!deviceConnected.Contains(currTran.deviceNo))
{
deviceConnected.Add(currTran.deviceNo);
DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs() { deviceNo = currTran.deviceNo, isConnected = true });
}
switch (currTran.func)
{
case 0x03:
{
List<UInt16> values = new List<UInt16>();
int index = 1;
while (index < currTran.funcData.Count())
{
values.Add(currTran.funcData.ToUInt16_Big_Endian(index));
index += 2;
}
currTran.retData = currTran.parse16FuncPack(values);
}
break;
}
if (logger.IsDebugEnabled)
{
if (currTran.retData == null)
logger.Debug($"ACK");
else
logger.Debug($"ACK {Newtonsoft.Json.JsonConvert.SerializeObject(currTran.retData)}");
}
//有很多指令是没有回复数据的, 回调只是通知 指令已经执行了而已
//调用回调
if (currTran.asyncDelegate != null)
{
if (Dispatcher != null)//线程同步执行
{
Dispatcher.BeginInvoke(currTran.asyncDelegate, currTran.asyncContext, currTran.retData);
}
else
{
currTran.asyncDelegate.Invoke(currTran.asyncContext, currTran.retData);
}
}
//空出当前交易位置
currTran = null;
if (Transactions.Count() > 0)
{
//队列还有需要发送的指令,通知外部获取指令发送
byte[] msg = GetSendMsg();
SendMsgEvent?.Invoke(this,msg);
}
}
public void AddTran(Modbus_Transaction tran)
{
//放入 交易队列
Transactions.Add(tran);
if (currTran == null)
{
//当前没有指令正在发送
byte[] msg = GetSendMsg();
SendMsgEvent?.Invoke(this,msg);
}
}
/// <summary>
/// 读多个Holding REGs
/// </summary>
/// <param name="deviceNo"></param>
/// <param name="addr"></param>
/// <param name="cnt"></param>
/// <param name="desription"></param>
/// <param name="parseU16FuncPack"></param>
/// <param name="asyncDelegate"></param>
/// <param name="asyncContext"></param>
/// <returns></returns>
public Modbus_Transaction Do_03(byte deviceNo, int addr, int cnt,
string desription,
ParseU16FuncPackHandler parseU16FuncPack,
CallBackHandler asyncDelegate, object asyncContext)
{
Modbus_Transaction tran = new Modbus_Transaction()
{
deviceNo = deviceNo,
func = 0x03,
addr = addr,
cnt = cnt,
expRecBytes = cnt * 2 + 5,
desription = desription,
parse16FuncPack = parseU16FuncPack,
asyncDelegate = asyncDelegate,
asyncContext = asyncContext
};
tran.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian());
tran.funcData.AddRange(((UInt16)cnt).GetBytes_Big_endian());
ToSendBuf(tran);
return tran;
}
void ToSendBuf(Modbus_Transaction tran)
{
var data = tran.sendBuf;
data.Add((byte)tran.deviceNo);
data.Add(tran.func);
data.AddRange(tran.funcData);
UInt16 crc = data.CRC16(0, data.Count());
data.AddRange(crc.GetBytes_Little_Endian());
tran.funcData.Clear();
}
/// <summary>
/// 写单个 Holding REG
/// </summary>
/// <param name="deviceNo"></param>
/// <param name="addr"></param>
/// <param name="cnt"></param>
/// <param name="desription"></param>
/// <param name="asyncDelegate"></param>
/// <param name="asyncContext"></param>
/// <returns></returns>
public Modbus_Transaction Do_06(int deviceNo, int addr, UInt16 value,
string desription,
CallBackHandler asyncDelegate, object asyncContext)
{
Modbus_Transaction tran = new Modbus_Transaction()
{
deviceNo = deviceNo,
func = 0x06,
addr = addr,
expRecBytes = 8,
desription = desription,
asyncDelegate = asyncDelegate,
asyncContext = asyncContext
};
tran.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian());
tran.funcData.AddRange(((UInt16)value).GetBytes_Big_endian());
ToSendBuf(tran);
return tran;
}
/// <summary>
/// 写多个 Holding REG
/// </summary>
/// <param name="deviceNo"></param>
/// <param name="addr"></param>
/// <param name="cnt"></param>
/// <param name="desription"></param>
/// <param name="asyncDelegate"></param>
/// <param name="asyncContext"></param>
/// <returns></returns>
public Modbus_Transaction Do_10(int deviceNo, int addr, IEnumerable<UInt16> datas,
string desription,
CallBackHandler asyncDelegate, object asyncContext)
{
Modbus_Transaction tran = new Modbus_Transaction()
{
deviceNo = deviceNo,
func = 0x10,
addr = addr,
expRecBytes = 8,
desription = desription,
asyncDelegate = asyncDelegate,
asyncContext = asyncContext
};
tran.funcData.AddRange(((UInt16)tran.addr).GetBytes_Big_endian());
tran.funcData.AddRange(((UInt16)datas.Count()).GetBytes_Big_endian());
tran.funcData.Add((byte)(datas.Count() * 2));
for (int i = 0; i < datas.Count(); i++)
{
tran.funcData.AddRange(datas.ElementAt(i).GetBytes_Big_endian());
}
ToSendBuf(tran);
return tran;
}
}
}