PLCHelper.cs
14.6 KB
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
using RCS.Model.Comm;
using RCS.Model.Entity;
using HslCommunication.Profinet.Siemens;
using HHECS.Communication;
using System.Collections.Concurrent;
namespace RCS.Communication
{
/// <summary>
/// PLC操作类
/// </summary>
public class PLCHelper
{
/// <summary>
/// SQL异常日志
/// </summary>
public static Action<string, string, bool> PLCHelperLog;
/// <summary>
/// 开启并行读取的限制数目,于此相关的均需要实际测试性能情况。
/// </summary>
const int ParaInt = 1;
/// <summary>
/// 读写锁,因为里面已经是多线程处理了,不许外部再进行多线程调用
/// </summary>
public object Lock { get; set; } = new object();
/// <summary>
/// 西门子PLC的处理封装
/// </summary>
public List<IEquipmentCommunication> PLCList = new();
/// <summary>
/// PLC操作类的单例
/// </summary>
private static readonly PLCHelper plcHelper = new PLCHelper();
/// <summary>
/// 不允许外部直接实例化
/// </summary>
private PLCHelper() { }
/// <summary>
/// 单例模式来获取实例
/// </summary>
public static PLCHelper Instance { get { return plcHelper; } }
/// <summary>
/// 设备初始化
/// </summary>
/// <param name="equipmentList"></param>
/// <param name="equipmentPropList"></param>
/// <returns></returns>
public BllResult<List<Base_Equipment>> InitEquipment(List<Base_Equipment> equipmentList, List<Base_EquipmentProp> equipmentPropList)
{
try
{
var equipments = new List<Base_Equipment>();
//初始化属性,包括偏移量计算,占用字节计算,属性对应的设备等
foreach (var equipmentProp in equipmentPropList)
{
if (!Enum.TryParse(equipmentProp.DataType, out PLCDataType plcDataType))
{
return BllResult<List<Base_Equipment>>.Error($"初始化设备信息失败,设备的【{equipmentProp.Equipment.Name}】设备属性【{equipmentProp.Code}】数据类型【{equipmentProp.DataType}】不支持!");
}
var equipment = equipmentList.FirstOrDefault(t => t.Code == equipmentProp.EquipmentCode);
if (equipment == null)
{
return BllResult<List<Base_Equipment>>.Error($"初始化设备信息失败,找不到属性对应的设备【{equipmentProp.EquipmentCode}】,请检查基础数据");
}
equipmentProp.Equipment = equipment;
equipmentProp.EquipmentName = equipmentProp.Equipment.Name;
equipmentProp.BatchAddress = equipmentProp.Address;
//计算数据占用的字节数
if (!SiemensPLC.CheckByteSize(plcDataType, equipmentProp.ByteSize))
{
return BllResult<List<Base_Equipment>>.Error($"初始化设备信息失败,设备的【{equipmentProp.Equipment.Name}】的属性【{equipmentProp.Address}】对应的字节长度不对【{equipmentProp.ByteSize}】,请检查基础数据");
}
//如果是BOOL类型要计算位移量,批量读取后要做位移运算来取值
//if (plcDataType == PLCDataType.BOOL)
//{
// var AddressArray = equipmentProp.Address.Split('.');
// if (AddressArray.Count() != 3)
// {
// return BllResult<List<Base_Equipment>>.Error($"初始化失败,设备的【{equipmentProp.Equipment.Name}】的属性【{equipmentProp.Code}】的地址【{equipmentProp.Address}】格式不对,Bool类型的地址应该只有2个英文句号!");
// }
// equipmentProp.BitOffset = Convert.ToInt32(AddressArray[2]);
// if (equipmentProp.BitOffset < 0 || equipmentProp.BitOffset > 7)
// {
// return BllResult<List<Base_Equipment>>.Error($"初始化失败,设备的【{equipmentProp.Equipment.Name}】的属性【{equipmentProp.Code}】的地址【{equipmentProp.Address}】格式不对,偏移位应该就是0-7之间!");
// }
// //由于批量读取时候,是读取整个字节,所以地址不包含.后的位
// equipmentProp.BatchAddress = AddressArray[0] + "." + AddressArray[1];
//}
//else
//{
// var AddressArray = equipmentProp.Address.Split('.');
// if (AddressArray.Count() != 2)
// {
// return BllResult<List<Base_Equipment>>.Error($"初始化失败,设备的【{equipmentProp.Equipment.Name}】的属性【{equipmentProp.Code}】的地址【{equipmentProp.Address}】格式不对,非Bool类型的地址应该只有1个英文句号!");
// }
//}
}
//初始化设备的属性
foreach (var equipment in equipmentList)
{
equipment.EquipmentProps = new ConcurrentBag<Base_EquipmentProp>(equipmentPropList.Where(t => t.EquipmentCode == equipment.Code).Reverse().ToList());
if (equipment.EquipmentProps.Count == 0)
{
return BllResult<List<Base_Equipment>>.Error($"初始化设备信息失败,找不到设备【{equipment.Code}】【{equipment.Name}】的属性,请检查基础数据");
}
equipments.Add(equipment);
}
return BllResult<List<Base_Equipment>>.Success(equipments);
}
catch (Exception ex)
{
var msg = $"初始化设备信息发生异常,原因:{ex.Message},堆栈:{ex.StackTrace}";
PLCHelperLog?.Invoke("Init", msg, false);
return BllResult<List<Base_Equipment>>.Error(msg);
}
}
/// <summary>
/// PLC的初始化,现在只支持西门子。因为这里调用使用的接口,后面需要可以扩展其他的PLC
/// </summary>
/// <returns></returns>
public BllResult InitPLC(List<IEquipmentCommunication> equipmentList)
{
try
{
////HSL授权
//HslCommunication.Authorization.SetAuthorizationCode("b03c5b8e-5d38-45e0-8c8d-d42ae005a0be");
//按照IP来创建PLC连接
// var EquipmentGroupList = equipmentList.GroupBy(t => t.Ip).Select(t => t.FirstOrDefault()).ToList();
//西门子的S7通信
//foreach (var equipmentItem in EquipmentGroupList)
//{
// if (Enum.TryParse(equipmentItem.PlcType, out SiemensPLCS siemensPLCS))
// {
// SiemensPLC siemensPLC = new SiemensPLC(siemensPLCS, equipmentItem.Ip);
// PLCList.Add(siemensPLC);
// }
// else
// {
// var msg = $"PLC类型【{equipmentItem.PlcType}】无法支持!";
// PLCHelperLog?.Invoke("PLCHelper", msg);
// return BllResult.Error(msg);
// }
//}
PLCList = equipmentList;
foreach (var plc in PLCList)
{
//开启长连接,失败也不退出
plc.Connect();
}
return BllResult.Success();
}
catch (Exception ex)
{
return BllResult.Error($"PLC初始化出现异常,原因:{ex.Message},堆栈:{ex.StackTrace}");
}
}
/// <summary>
/// 读取一个PLC的地址
/// </summary>
/// <param name="equipmentProp"></param>
/// <returns></returns>
public BllResult Reads(Base_EquipmentProp equipmentProp)
{
lock (Lock)
{
return Reads(new List<Base_EquipmentProp> { equipmentProp });
}
}
/// <summary>
/// 读取多个PLC的地址
/// </summary>
/// <returns></returns>
public BllResult Reads(List<Base_EquipmentProp> equipmentProps)
{
lock (Lock)
{
try
{
var equipmentPropGroupList = equipmentProps.GroupBy(t => t.Equipment.Ip).ToList();
foreach (var equipmentPropGroup in equipmentPropGroupList)
{
if (!PLCList.Exists(t => t.IP == equipmentPropGroup.Key))
{
var msg = $"读取PLC列表错误,未找到IP为{equipmentPropGroup.Key}的PLC的配置";
PLCHelperLog?.Invoke("PLCHelper", msg, false);
return BllResult.Error(msg);
}
}
if (equipmentPropGroupList.Count > ParaInt)
{
var taskList = new List<Task<BllResult>>();
foreach (var equipmentPropGroup in equipmentPropGroupList)
{
var equipmentPropList = equipmentPropGroup.ToList();
var plc = PLCList.FirstOrDefault(t => t.IP == equipmentPropGroup.Key);
taskList.Add(Task.Run<BllResult>(() =>
{
var temp = plc.Reads(equipmentPropList.ToArray());
if (temp.Success)
{
return BllResult.Success(temp);
}
return BllResult.Error(temp);
}));
}
Task.WaitAll(taskList.ToArray());
foreach (var task in taskList)
{
var result = task.Result;
if (result == null)
{
var msg = $"读取单个PLC错误,读取结果返回了null值,请检查PLC读取实现类";
PLCHelperLog?.Invoke("PLCHelper", msg, false);
return BllResult.Error(msg);
}
if (!result.IsSuccess)
{
return result;
}
}
return BllResult.Success();
}
else
{
foreach (var equipmentPropGroup in equipmentPropGroupList)
{
var equipmentPropList = equipmentPropGroup.ToList();
var plc = PLCList.FirstOrDefault(t => t.IP == equipmentPropGroup.Key);
var result = plc.Reads(equipmentPropList.ToArray());
if (!result.Success)
{
if (!string.IsNullOrEmpty(result.Msg))
{
PLCHelperLog?.Invoke("PLCHelper", result.Msg, false);
}
if (result.Success)
{
return BllResult.Success();
}
return BllResult.Error();
}
}
}
return BllResult.Success();
}
catch (Exception ex)
{
return BllResult.Error($"读取PLC列表错误,出现异常,原因:{ex.Message},堆栈:{ex.StackTrace}");
}
}
}
/// <summary>
/// 写入一个PLC的地址
/// </summary>
/// <param name="equipmentProp"></param>
/// <returns></returns>
public BllResult<List<string>> Writes(Base_EquipmentProp equipmentProp)
{
lock (Lock)
{
return Writes(new List<Base_EquipmentProp> { equipmentProp });
}
}
/// <summary>
/// 写入多个PLC的地址
/// 注意:写入值不做线程优化处理,请误大量频繁写入
/// </summary>
/// <returns></returns>
public BllResult<List<string>> Writes(List<Base_EquipmentProp> equipmentProps)
{
lock (Lock)
{
try
{
var addressGroup = equipmentProps.GroupBy(t => t.Equipment.Ip);
foreach (var item in addressGroup)
{
var plc = PLCList.FirstOrDefault(t => t.IP == item.Key);
if (plc == null)
{
var msg = $"写入PLC失败,未找到IP为{item.Key}的PLC的配置";
PLCHelperLog?.Invoke("PLCHelper", msg, false);
return BllResult<List<string>>.Error(msg);
}
var plcEquipmentProp = item.ToList();
var result = plc.Writes(plcEquipmentProp.ToArray());
//记录PLC写入记录到日志中
//result.Data.ForEach(t => PLCHelperLog?.Invoke("PLCHelper", t));
//if (!result.Success)
//{
// //如果写入失败,就把值清空,避免后面判断为写入成功
// plcEquipmentProp.ForEach(t => t.Value = "");
// return result;
//}
}
return BllResult<List<string>>.Success();
}
catch (Exception ex)
{
return BllResult<List<string>>.Error($"写入PLC列表失败,出现异常,原因:{ex.Message},堆栈:{ex.StackTrace}");
}
}
}
}
}