OPC UA (二)
重头戏,捞取数据,才是该干的事。想获取数据,先有数据源DataPrivade,DataPrivade的数据集合不能和BaseDataVariableState的集合存储同一地址,或者称为浅副本
需要提出下面类重新设计,按照自己的idea来做
public class ReferenceNodeManager : CustomNodeManager2
UA-.NETStandard设计的数据锁效果好,点数一多,多Client就比较卡。后来发现是Lock问题,Lock时间一长,其他Client就只能等待,目前想到的解决办法就是深拷贝数据,尽量缩短取值时间。就是上述说的不是同一片内存空间。Server的数据和DataPrivade数据不是同一个。
private BaseDataVariableState CreateVariable(NodeState parent, string path, string name, NodeId dataType, int valueRank) { BaseDataVariableState variable = new BaseDataVariableState(parent); variable.SymbolicName = name; variable.ReferenceTypeId = ReferenceTypes.Organizes; variable.TypeDefinitionId = VariableTypeIds.BaseDataVariableType; variable.NodeId = new NodeId(path, NamespaceIndex); variable.BrowseName = new QualifiedName(path, NamespaceIndex); variable.DisplayName = new LocalizedText("en", name); variable.WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description; variable.UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description; variable.DataType = dataType; variable.ValueRank = valueRank; variable.AccessLevel = AccessLevels.CurrentReadOrWrite; variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite; variable.Historizing = false; variable.Value = GetNewValue(variable); variable.StatusCode = StatusCodes.Good; variable.Timestamp = DateTime.UtcNow;
四、DataPrivade
a) BaseDataVariableState 的属性
SymbolicName: A symbolic name for the node that is not expected to be globally unique.(节点的符号名,不应是全局唯一的。)(百度翻译)
NodeId:The identifier for the node.(节点的标识符)
BrowseName:The browse name of the node.(节点的浏览名称)
DisplayName:The display name for the node.(节点的显示名称)
DataType:The data type for the variable value.(变量值的数据类型)
ValueRank:The number of array dimensions permitted for the variable value.(变量值允许的数组维数)
Value:The value of the variable.(变量的值)(变量:或者称为节点)
个人认为上面属性比较重要,加黑字体的是用到的,其他暂时没用到。
b) 数据存储在不同区域,下面定义
可能有的用到,有的没用到
1 #region Read
2 // Real BaseDataVariableState
3 public Dictionary<string, BaseDataVariableState> BaseDataVariableStateDic = new Dictionary<string, BaseDataVariableState>(); 4 // temp BaseDataVariableState
5 public Dictionary<string, BaseDataVariableState> BaseDataVariableStateTempDic = new Dictionary<string, BaseDataVariableState>(); 6
7 // for example ***_***_*** ,OPC
8 private readonly Dictionary<string, String> baseDataVariableToXXXXDic = new Dictionary<string, String>(); 9 // for example ***.***.*** ,IOServer
10 private Dictionary<string, String> XXXXToBaseDataVariableDic = new Dictionary<string, String>(); 11 #endregion
12
13 #region Write
14 private List<InterfaceSample.Model.ValueItem> writeDataList = new List<InterfaceSample.Model.ValueItem>(); 15 private Dictionary<string, BaseDataVariableState> baseDataVariableDicWrite = new Dictionary<string, BaseDataVariableState>(); 16 #endregion
17
18 public InterfaceSample.OPCUADataProvide OpcuaDataPrivade = null; 19
20 private object _obj = new object(); 21 #endregion
c) 获取与设置数据Value
1. 定时器更新数据,不要因为数据太多卡住,多次增加线程,争抢资源。放在了CreateAddressSpace里,在OPC UA(一)可以看到。Timer:System.Threading.Timer。
1 m_simulationTimer = new Timer(DoSimulation, cts, 1000, 1000); 2
回调DoSimulation(TimerCallback),cts(CancellationTokenSource cts)后面解释,DoSimulation里要使用Change,至于为什么需要自己查,不怎么用,用的不好。
1 private void DoSimulation(object state) 2 { 3 try
4 { 5 #region CancellationTokenSource
6 var source = state as CancellationTokenSource; 7
8 if (source == null || cts == null) 9 { 10 return; 11 } 12 if (source.Token.IsCancellationRequested && cts.Token.IsCancellationRequested) 13 { 14 return; 15 } 16 else
17 { 18 if (!cts.Token.IsCancellationRequested) 19 { 20 source = cts; 21 } 22 } 23 #endregion
24
25 if (m_dynamicNodes_temp == null) 26 { 27 return; 28 } 29 m_simulationTimer.Change(0, Timeout.Infinite); 30
31 #region
32 lock (_obj) 33 { 34 string[] strs = new string[baseDataVariableToXXXXDic.Count]; 35 baseDataVariableToXXXXDic.Values.CopyTo(strs, 0); 36 List<string> li = new List<string>(); 37 foreach (var str in strs) 38 { 39 if (source.Token.IsCancellationRequested) 40 { 41 m_simulationTimer.Change(1000, 1000); 42 return; 43 } 44 li.Add(str); 45 } 46 var obsli = OpcuaDataPrivade.GetItemDatas(li); 47
48 if (obsli == null) 49 { 50 m_simulationTimer.Change(1000, 1000); 51 return; 52 } 53 for (int i = 0; i < obsli.Count; i++) 54 { 55 if (source.Token.IsCancellationRequested) 56 { 57 m_simulationTimer.Change(1000, 1000); 58 return; 59 } 60 m_dynamicNodes_temp[i].Value = obsli[i]; 61 } 62 } 63
64 #endregion
65
66 lock (Lock) 67 { 68 int count = 0; 69 foreach (BaseDataVariableState variable in m_dynamicNodes_temp) 70 { 71 if (source.Token.IsCancellationRequested) 72 { 73 m_simulationTimer.Change(1000, 1000); 74 return; 75 } 76 77 if (BaseDataVariableStateDic.ContainsKey(variable.NodeId.Identifier.ToString())) 78 { 79 m_dynamicNodes[count].Value = variable.Value; 80 m_dynamicNodes[count].Timestamp = DateTime.UtcNow; 81 m_dynamicNodes[count].ClearChangeMasks(SystemContext, false); 82 ++count; 83
84 } 85 } 86 m_simulationTimer.Change(1000, 1000); 87 //m_simulationTimer = new Timer(DoSimulation, null, 1000, 1000);
88 } 89 } 90 catch (Exception e) 91 { 92 Utils.Trace(e, "Unexpected error doing simulation."); 93 } 94 }
2. 一般地,Client用户端对Value的赋值(输入)在优先级上要高于读取,所以CancellationTokenSource的用处体现在这。
UA-.NETStandard里的BaseDataVariableState的Write集合,自己设计的,里面能到什么OnWrite或Write之类的,直接把CustomNodeManager2的Write直接拿过来
public override void Write( OperationContext context,IList<WriteValue> nodesToWrite,IList<ServiceResult> errors)
不整段代码,贴插入部分,里面有前后,找的话会找到,(override不一定成功,呵)
1 CheckIfSemanticsHaveChanged(systemContext, propertyState, propertyValue, previousPropertyValue); 2 } 3 var baseNode = handle.Node as BaseDataVariableState; 4 if(baseNode != null) nodesToWriteList.Add(baseNode); 5
6 handle.Node.ClearChangeMasks(systemContext, false); 7
8 } 9
10 // check for nothing to do.
11 if (nodesToValidate.Count == 0) 12 { 13 return; 14 }
另外的代码,遥相呼应一下,OPC UA(一) 里的CreateAddressSpace里的Task。
1 public void WriteProcHandle(CancellationTokenSource source) 2 { 3 //CancellationTokenSource source = new CancellationTokenSource();
4
5 while (true) 6 { 7 Thread.Sleep(500); 8 try
9 { 10 #region
11 lock (Lock) 12 { 13 if (baseDataVariableDicWrite.Count > 0) 14 { 15 foreach (var item in baseDataVariableDicWrite) 16 { 17 writeDataList.Add(new InterfaceSample.Model.ValueItem() { Key = baseDataVariableToRTDBDic[item.Key], Value = item.Value.Value }); 18 } 19
20 baseDataVariableDicWrite.Clear(); 21 } 22 } 23
24 lock (_obj) 25 { 26 if (writeDataList.Count <= 0) 27 { 28 continue; 29 } 30
31 source.Cancel(); 32 List<string> keys = new List<string>(); 33 List<object> values = new List<object>(); 34 foreach (var item in writeDataList) 35 { 36 keys.Add(item.Key); 37 values.Add(item.Value); 38 } 39
40 if (writeDataList.Count > 0) 41 { 42 OpcuaDataPrivade.SetItemDatas(keys, values); 43 writeDataList.Clear(); 44 cts = new CancellationTokenSource(); 45 } 46
47 } 48
49 #endregion
50
51 } 52 catch (Exception) 53 { 54 //source = new CancellationTokenSource();
55 } 56 finally
57 { 58 if (cts.Token.IsCancellationRequested) 59 { 60 cts = new CancellationTokenSource(); 61 } 62 } 63 } 64 }
大概就是这样。UA-.NETStandard方案分享出来