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;56cvariable.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 Read2 // Real BaseDataVariableState3 public Dictionary<string, BaseDataVariableState> BaseDataVariableStateDic = new Dictionary<string, BaseDataVariableState>();4 // temp BaseDataVariableState5 public Dictionary<string, BaseDataVariableState> BaseDataVariableStateTempDic = new Dictionary<string, BaseDataVariableState>();67 // for example ***_***_*** ,OPC8 private readonly Dictionary<string, String> baseDataVariableToXXXXDic = new Dictionary<string, String>();9 // for example ***.***.*** ,IOServer10 private Dictionary<string, String> XXXXToBaseDataVariableDic = new Dictionary<string, String>();11 #endregion1213 #region Write14 private List<InterfaceSample.Model.ValueItem> writeDataList = new List<InterfaceSample.Model.ValueItem>();15 private Dictionary<string, BaseDataVariableState> baseDataVariableDicWrite = new Dictionary<string, BaseDataVariableState>();16 #endregion1718 public InterfaceSample.OPCUADataProvide OpcuaDataPrivade = null;1920 private objectad8_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 try4 {5 #region CancellationTokenSource6 var source = state as CancellationTokenSource;78 if (source == null || cts == null)9 {10 return;11 }12 if (source.Token.IsCancellationRequested && cts.Token.IsCancellationRequested)13 {14 return;15 }16 else17 {18 if (!cts.Token.IsCancellationRequested)19 {20 source = cts;21 }22 }23 #endregion2425 if (m_dynamicNodes_temp == null)26 {27 return;28 }29 m_simulationTimer.Change(0, Timeout.Infinite);3031 #region32 lock (_obj)33 {34 string[] strs = new string[baseDataVariableToXXXXDic.Count];35 baseDataVariableToXXXXDic.Values.CopyTo(56cstrs, 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);4748 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(900, 1000);58 return;59 }60 m_dynamicNodes_temp[i].Value = obsli[i];61 }62 }6364 #endregion6566 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;8384 }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);56 handle.Node.ClearChangeMasks(systemContext, false);78 }91015b0// 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();45 while (true)6 {7 Thread.Sleep(500);8 try9 {10 #region11 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 }1920 baseDataVariableDicWrite.Clear();21 }22 }2324 lock (_obj)25 {26 if (writeDataList.Count <= 0)27 {28 continue;29 }3031 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 }3940 if (writeDataList.Count > 0)41 {42 OpcuaDataPrivade.SetItemDatas(keys, values);43 writeDataList.Clear();44 cts = new CancellationTokenSource();45 }4647 }4849 #endregion5051 }52 catch (Exception)53 {54 //source = new CancellationTokenSource();55 }56 finally57 {58 if (cts.Token.IsCancellationRequested)59 {60 cts = new CancellationTokenSource();61 }62 }63 }64 }
大概就是这样。