534 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			534 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| #region copyright
 | ||
| //------------------------------------------------------------------------------
 | ||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | ||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | ||
| //  源代码使用协议遵循本仓库的开源协议及附加协议
 | ||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | ||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | ||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | ||
| //  QQ群:605534569
 | ||
| //------------------------------------------------------------------------------
 | ||
| #endregion
 | ||
| 
 | ||
| using Furion;
 | ||
| 
 | ||
| using Mapster;
 | ||
| 
 | ||
| using Newtonsoft.Json.Linq;
 | ||
| 
 | ||
| using Opc.Ua;
 | ||
| using Opc.Ua.Server;
 | ||
| 
 | ||
| using System;
 | ||
| using System.Collections.Generic;
 | ||
| using System.Linq;
 | ||
| 
 | ||
| using ThingsGateway.Application;
 | ||
| using ThingsGateway.Application.Extensions;
 | ||
| using ThingsGateway.Foundation;
 | ||
| using ThingsGateway.Foundation.Adapter.OPCUA;
 | ||
| 
 | ||
| using TouchSocket.Core;
 | ||
| 
 | ||
| namespace ThingsGateway.OPCUA;
 | ||
| 
 | ||
| 
 | ||
| /// <summary>
 | ||
| /// 数据节点
 | ||
| /// </summary>
 | ||
| public class ThingsGatewayNodeManager : CustomNodeManager2
 | ||
| {
 | ||
|     private const string ReferenceServer = "https://diego2098.gitee.io/thingsgateway-docs/";
 | ||
|     private readonly TypeAdapterConfig _config;
 | ||
|     private readonly UploadDevice _device;
 | ||
|     private readonly GlobalDeviceData _globalDeviceData;
 | ||
|     private readonly RpcSingletonService _rpcCore;
 | ||
|     private readonly ILog LogMessage;
 | ||
|     /// <summary>
 | ||
|     /// OPC和网关对应表
 | ||
|     /// </summary>
 | ||
|     private readonly Dictionary<NodeId, OPCUATag> NodeIdTags = new();
 | ||
|     /// <inheritdoc cref="ThingsGatewayNodeManager"/>
 | ||
|     public ThingsGatewayNodeManager(UploadDevice device, ILog log, IServerInternal server, ApplicationConfiguration configuration) : base(server, configuration, ReferenceServer)
 | ||
|     {
 | ||
|         _device = device;
 | ||
|         LogMessage = log;
 | ||
|         _rpcCore = App.GetService<RpcSingletonService>();
 | ||
|         _globalDeviceData = App.GetService<GlobalDeviceData>();
 | ||
|         _config = new TypeAdapterConfig();
 | ||
|         _config.ForType<HistoryValue, DataValue>()
 | ||
| .Map(dest => dest.WrappedValue, (src) => new Variant(src.Value))
 | ||
| .Map(dest => dest.SourceTimestamp, src => DateTime.SpecifyKind(src.CollectTime, DateTimeKind.Utc))
 | ||
| .Map(dest => dest.StatusCode, (src) =>
 | ||
| src.IsOnline ? StatusCodes.Good : StatusCodes.Bad);
 | ||
|     }
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 创建服务目录结构
 | ||
|     /// </summary>
 | ||
|     /// <param name="externalReferences"></param>
 | ||
|     public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
 | ||
|     {
 | ||
|         lock (Lock)
 | ||
|         {
 | ||
|             if (!externalReferences.TryGetValue(ObjectIds.ObjectsFolder, out IList<IReference> references))
 | ||
|             {
 | ||
|                 externalReferences[ObjectIds.ObjectsFolder] = references = new List<IReference>();
 | ||
|             }
 | ||
|             //首节点
 | ||
|             FolderState rootFolder = CreateFolder(null, "ThingsGateway", "ThingsGateway");
 | ||
|             rootFolder.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
 | ||
|             references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, rootFolder.NodeId));
 | ||
|             rootFolder.EventNotifier = EventNotifiers.SubscribeToEvents;
 | ||
|             AddRootNotifier(rootFolder);
 | ||
| 
 | ||
|             //创建设备树
 | ||
|             var _geviceGroup = _globalDeviceData.CollectDevices.ToList().Adapt<List<CollectDevice>>().GetTree();
 | ||
|             // 开始寻找设备信息,并计算一些节点信息
 | ||
|             foreach (var item in _geviceGroup)
 | ||
|             {
 | ||
|                 //设备树会有两层
 | ||
|                 FolderState fs = CreateFolder(rootFolder, item.Name, item.Name);
 | ||
|                 fs.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
 | ||
|                 fs.EventNotifier = EventNotifiers.SubscribeToEvents;
 | ||
|                 if (item.Childrens?.Count > 0)
 | ||
|                 {
 | ||
|                     foreach (var item2 in item.Childrens)
 | ||
|                     {
 | ||
|                         AddTagNode(fs, item2.Name);
 | ||
|                     }
 | ||
|                 }
 | ||
|                 else
 | ||
|                 {
 | ||
|                     AddTagNode(fs, item.Name);
 | ||
|                 }
 | ||
| 
 | ||
|             }
 | ||
|             FolderState memoryfs = CreateFolder(null, "ThingsGateway中间变量", "ThingsGateway中间变量");
 | ||
|             var variableRunTimes = _globalDeviceData.MemoryVariables;
 | ||
|             foreach (var item in variableRunTimes)
 | ||
|             {
 | ||
|                 CreateVariable(memoryfs, item);
 | ||
|             }
 | ||
|             AddPredefinedNode(SystemContext, rootFolder);
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
|     }
 | ||
|     /// <summary>
 | ||
|     /// 获取变量的属性值
 | ||
|     /// </summary>
 | ||
|     public string GetPropertyValue(DeviceVariableRunTime variableRunTime, string propertyName)
 | ||
|     {
 | ||
|         if (variableRunTime.VariablePropertys.ContainsKey(_device.Id))
 | ||
|         {
 | ||
|             var data = variableRunTime.VariablePropertys[_device.Id].FirstOrDefault(a =>
 | ||
|                   a.PropertyName == propertyName);
 | ||
|             if (data != null)
 | ||
|             {
 | ||
|                 return data.Value;
 | ||
|             }
 | ||
|         }
 | ||
|         return null;
 | ||
|     }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 读取历史数据
 | ||
|     /// </summary>
 | ||
|     public override void HistoryRead(OperationContext context,
 | ||
|         HistoryReadDetails details,
 | ||
|         TimestampsToReturn timestampsToReturn,
 | ||
|         bool releaseContinuationPoints,
 | ||
|         IList<HistoryReadValueId> nodesToRead,
 | ||
|         IList<HistoryReadResult> results,
 | ||
|         IList<ServiceResult> errors)
 | ||
|     {
 | ||
|         base.HistoryRead(context, details, timestampsToReturn, releaseContinuationPoints, nodesToRead, results, errors);
 | ||
|         //必须带有时间范围
 | ||
|         if (details is not ReadRawModifiedDetails readDetail || readDetail.StartTime == DateTime.MinValue || readDetail.EndTime == DateTime.MinValue)
 | ||
|         {
 | ||
|             errors[0] = StatusCodes.BadHistoryOperationUnsupported;
 | ||
|             return;
 | ||
|         }
 | ||
|         var service = ServiceHelper.GetBackgroundService<HistoryValueWorker>();
 | ||
|         if (!service.StatuString.IsSuccess)
 | ||
|         {
 | ||
|             errors[0] = StatusCodes.BadHistoryOperationUnsupported;
 | ||
|             return;
 | ||
|         }
 | ||
| 
 | ||
|         var db = service.GetHisDbAsync().GetAwaiter().GetResult();
 | ||
|         if (!db.IsSuccess)
 | ||
|         {
 | ||
|             errors[0] = StatusCodes.BadHistoryOperationUnsupported;
 | ||
|             return;
 | ||
|         }
 | ||
|         var startTime = readDetail.StartTime;
 | ||
|         var endTime = readDetail.EndTime;
 | ||
| 
 | ||
|         for (int i = 0; i < nodesToRead.Count; i++)
 | ||
|         {
 | ||
|             var historyRead = nodesToRead[i];
 | ||
|             if (NodeIdTags.TryGetValue(historyRead.NodeId, out OPCUATag tag))
 | ||
|             {
 | ||
|                 var data = db.Content.Queryable<HistoryValue>()
 | ||
|                     .Where(a => a.Name == tag.SymbolicName)
 | ||
|                     .Where(a => a.CollectTime >= startTime)
 | ||
|                     .Where(a => a.CollectTime <= endTime)
 | ||
|                     .ToList();
 | ||
| 
 | ||
|                 if (data.Count > 0)
 | ||
|                 {
 | ||
| 
 | ||
|                     var hisDataValue = data.Adapt<List<DataValue>>(_config);
 | ||
|                     HistoryData hisData = new();
 | ||
|                     hisData.DataValues.AddRange(hisDataValue);
 | ||
|                     errors[i] = StatusCodes.Good;
 | ||
|                     //切记Processed设为true,否则客户端会报错
 | ||
|                     historyRead.Processed = true;
 | ||
|                     results[i] = new HistoryReadResult()
 | ||
|                     {
 | ||
|                         StatusCode = StatusCodes.Good,
 | ||
|                         HistoryData = new ExtensionObject(hisData)
 | ||
|                     };
 | ||
|                 }
 | ||
|                 else
 | ||
|                 {
 | ||
|                     results[i] = new HistoryReadResult()
 | ||
|                     {
 | ||
|                         StatusCode = StatusCodes.GoodNoData
 | ||
|                     };
 | ||
|                 }
 | ||
|             }
 | ||
|             else
 | ||
|             {
 | ||
|                 results[i] = new HistoryReadResult()
 | ||
|                 {
 | ||
|                     StatusCode = StatusCodes.BadNotFound
 | ||
|                 };
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     public override NodeId New(ISystemContext context, NodeState node)
 | ||
|     {
 | ||
|         if (node is BaseInstanceState instance && instance.Parent != null)
 | ||
|         {
 | ||
|             string id = instance.Parent.NodeId.Identifier?.ToString();
 | ||
|             if (id != null)
 | ||
|             {
 | ||
|                 //用下划线分割
 | ||
|                 return new NodeId(id + "_" + instance.SymbolicName, instance.Parent.NodeId.NamespaceIndex);
 | ||
|             }
 | ||
|         }
 | ||
|         return node.NodeId;
 | ||
|     }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 更新变量
 | ||
|     /// </summary>
 | ||
|     /// <param name="variable"></param>
 | ||
|     public void UpVariable(VariableData variable)
 | ||
|     {
 | ||
|         var uaTag = NodeIdTags.Values.FirstOrDefault(it => it.SymbolicName == variable.Name);
 | ||
|         if (uaTag == null) return;
 | ||
|         object initialItemValue = null;
 | ||
|         initialItemValue = variable.Value;
 | ||
|         if (initialItemValue != null)
 | ||
|         {
 | ||
|             var code = variable.IsOnline ? StatusCodes.Good : StatusCodes.Bad;
 | ||
|             if (code == StatusCodes.Good)
 | ||
|             {
 | ||
|                 ChangeNodeData(uaTag, initialItemValue, variable.ChangeTime);
 | ||
|             }
 | ||
| 
 | ||
|             if (uaTag.StatusCode != code)
 | ||
|             {
 | ||
|                 uaTag.StatusCode = code;
 | ||
|             }
 | ||
|             uaTag.ClearChangeMasks(SystemContext, false);
 | ||
| 
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// <inheritdoc/>
 | ||
|     /// </summary>
 | ||
|     /// <param name="disposing"></param>
 | ||
|     protected override void Dispose(bool disposing)
 | ||
|     {
 | ||
|         NodeIdTags.Clear();
 | ||
|         base.Dispose(disposing);
 | ||
|     }
 | ||
|     private static NodeId DataNodeType(Type tp)
 | ||
|     {
 | ||
|         if (tp == typeof(bool))
 | ||
|             return DataTypeIds.Boolean;
 | ||
|         if (tp == typeof(byte))
 | ||
|             return DataTypeIds.Byte;
 | ||
|         if (tp == typeof(sbyte))
 | ||
|             return DataTypeIds.SByte;
 | ||
|         if (tp == typeof(short))
 | ||
|             return DataTypeIds.Int16;
 | ||
|         if (tp == typeof(ushort))
 | ||
|             return DataTypeIds.UInt16;
 | ||
|         if (tp == typeof(int))
 | ||
|             return DataTypeIds.Int32;
 | ||
|         if (tp == typeof(uint))
 | ||
|             return DataTypeIds.UInt32;
 | ||
|         if (tp == typeof(long))
 | ||
|             return DataTypeIds.Int64;
 | ||
|         if (tp == typeof(ulong))
 | ||
|             return DataTypeIds.UInt64;
 | ||
|         if (tp == typeof(float))
 | ||
|             return DataTypeIds.Float;
 | ||
|         if (tp == typeof(double))
 | ||
|             return DataTypeIds.Double;
 | ||
|         if (tp == typeof(string))
 | ||
|             return DataTypeIds.String;
 | ||
|         if (tp == typeof(DateTime))
 | ||
|             return DataTypeIds.DateTime;
 | ||
|         return DataTypeIds.String;
 | ||
|     }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 添加变量节点
 | ||
|     /// </summary>
 | ||
|     /// <param name="fs">设备组节点</param>
 | ||
|     /// <param name="name">设备名称</param>
 | ||
|     private void AddTagNode(FolderState fs, string name)
 | ||
|     {
 | ||
|         var device = _globalDeviceData.CollectDevices.Where(a => a.Name == name).FirstOrDefault();
 | ||
|         if (device != null)
 | ||
|         {
 | ||
|             foreach (var item in device.DeviceVariableRunTimes)
 | ||
|             {
 | ||
|                 CreateVariable(fs, item);
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 在服务器端直接更改对应数据节点的值
 | ||
|     /// </summary>
 | ||
|     private void ChangeNodeData(OPCUATag tag, object value, DateTime dateTime)
 | ||
|     {
 | ||
|         object newValue;
 | ||
|         try
 | ||
|         {
 | ||
|             if (!tag.IsDataTypeInit)
 | ||
|             {
 | ||
|                 if (tag.DataType == DataTypeIds.String)
 | ||
|                 {
 | ||
|                     if (value != null)
 | ||
|                     {
 | ||
|                         SetDataType(tag, value);
 | ||
|                     }
 | ||
|                 }
 | ||
|                 else
 | ||
|                 {
 | ||
|                     SetRank(tag, value);
 | ||
|                 }
 | ||
|             }
 | ||
|             var jToken = JToken.FromObject((tag.DataType == DataTypeIds.String ? value?.ToString() : value));
 | ||
|             var dataValue = JsonUtils.DecoderObject(
 | ||
|                this.Server.MessageContext,
 | ||
|            tag.DataType,
 | ||
|                 TypeInfo.GetBuiltInType(tag.DataType, this.SystemContext.TypeTable),
 | ||
|                 jToken.CalculateActualValueRank(),
 | ||
|                 jToken
 | ||
|                 );
 | ||
|             newValue = dataValue;
 | ||
|         }
 | ||
|         catch (Exception ex)
 | ||
|         {
 | ||
|             LogMessage.LogWarning(ex, "转化值错误");
 | ||
|             newValue = value;
 | ||
|         }
 | ||
|         tag.Value = newValue;
 | ||
|         tag.Timestamp = dateTime;
 | ||
| 
 | ||
| 
 | ||
|         void SetDataType(OPCUATag tag, object value)
 | ||
|         {
 | ||
|             tag.IsDataTypeInit = true;
 | ||
|             var tp = value.GetType();
 | ||
|             if (tp == typeof(JArray))
 | ||
|             {
 | ||
|                 try
 | ||
|                 {
 | ||
|                     tp = ((JValue)((JArray)value).FirstOrDefault()).Value.GetType();
 | ||
|                     tag.ValueRank = ValueRanks.OneOrMoreDimensions;
 | ||
|                 }
 | ||
|                 catch
 | ||
|                 {
 | ||
|                 }
 | ||
|             }
 | ||
|             if (tp == typeof(JValue))
 | ||
|             {
 | ||
|                 tp = ((JValue)value).Value.GetType();
 | ||
|                 tag.ValueRank = ValueRanks.Scalar;
 | ||
|             }
 | ||
|             tag.DataType = DataNodeType(tp);
 | ||
| 
 | ||
|             tag.ClearChangeMasks(SystemContext, false);
 | ||
|         }
 | ||
| 
 | ||
|         void SetRank(OPCUATag tag, object value)
 | ||
|         {
 | ||
|             tag.IsDataTypeInit = true;
 | ||
|             var tp = value.GetType();
 | ||
|             if (tp == typeof(JArray))
 | ||
|             {
 | ||
|                 try
 | ||
|                 {
 | ||
|                     tp = ((JValue)((JArray)value).FirstOrDefault()).Value.GetType();
 | ||
|                     tag.ValueRank = ValueRanks.OneOrMoreDimensions;
 | ||
|                 }
 | ||
|                 catch
 | ||
|                 {
 | ||
|                 }
 | ||
|             }
 | ||
|             tag.ClearChangeMasks(SystemContext, false);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 创建文件夹
 | ||
|     /// </summary>
 | ||
|     private FolderState CreateFolder(NodeState parent, string name, string description)
 | ||
|     {
 | ||
|         FolderState folder = new(parent)
 | ||
|         {
 | ||
|             SymbolicName = name,
 | ||
|             ReferenceTypeId = ReferenceTypes.Organizes,
 | ||
|             TypeDefinitionId = ObjectTypeIds.FolderType,
 | ||
|             Description = description,
 | ||
|             NodeId = new NodeId(name, NamespaceIndex),
 | ||
|             BrowseName = new QualifiedName(name, NamespaceIndex),
 | ||
|             DisplayName = new LocalizedText(name),
 | ||
|             WriteMask = AttributeWriteMask.None,
 | ||
|             UserWriteMask = AttributeWriteMask.None,
 | ||
|             EventNotifier = EventNotifiers.None
 | ||
|         };
 | ||
| 
 | ||
|         parent?.AddChild(folder);
 | ||
| 
 | ||
|         return folder;
 | ||
|     }
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 创建一个值节点,类型需要在创建的时候指定
 | ||
|     /// </summary>
 | ||
|     private OPCUATag CreateVariable(NodeState parent, DeviceVariableRunTime variableRunTime)
 | ||
|     {
 | ||
|         OPCUATag variable = new(parent)
 | ||
|         {
 | ||
|             SymbolicName = variableRunTime.Name,
 | ||
|             ReferenceTypeId = ReferenceTypes.Organizes,
 | ||
|             TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
 | ||
|             NodeId = new NodeId(variableRunTime.Name, NamespaceIndex),
 | ||
|             Description = variableRunTime.Description,
 | ||
|             BrowseName = new QualifiedName(variableRunTime.Name, NamespaceIndex),
 | ||
|             DisplayName = new LocalizedText(variableRunTime.Name),
 | ||
|             WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description,
 | ||
|             UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description,
 | ||
|             ValueRank = ValueRanks.Scalar,
 | ||
|             Id = variableRunTime.Id,
 | ||
|             DataType = DataNodeType(variableRunTime)
 | ||
|         };
 | ||
|         var level = ProtectTypeTrans(variableRunTime);
 | ||
|         variable.AccessLevel = level;
 | ||
|         variable.UserAccessLevel = level;
 | ||
|         variable.Historizing = variableRunTime.HisEnable;
 | ||
|         variable.Value = Opc.Ua.TypeInfo.GetDefaultValue(variable.DataType, ValueRanks.Any, Server.TypeTree);
 | ||
|         var code = variableRunTime.IsOnline ? StatusCodes.Good : StatusCodes.Bad;
 | ||
|         variable.StatusCode = code;
 | ||
|         variable.Timestamp = variableRunTime.CollectTime;
 | ||
|         variable.OnWriteValue = OnWriteDataValue;
 | ||
|         parent?.AddChild(variable);
 | ||
|         NodeIdTags.AddOrUpdate(variable.NodeId, variable);
 | ||
|         return variable;
 | ||
|     }
 | ||
|     /// <summary>
 | ||
|     /// 网关转OPC数据类型
 | ||
|     /// </summary>
 | ||
|     /// <param name="variableRunTime"></param>
 | ||
|     /// <returns></returns>
 | ||
|     private NodeId DataNodeType(DeviceVariableRunTime variableRunTime)
 | ||
|     {
 | ||
|         var str = GetPropertyValue(variableRunTime, nameof(OPCUAServerVariableProperty.DataTypeEnum));
 | ||
|         Type tp;
 | ||
|         if (Enum.TryParse<DataTypeEnum>(str, out DataTypeEnum result))
 | ||
|         {
 | ||
|             tp = result.GetSystemType();
 | ||
|         }
 | ||
|         else
 | ||
|         {
 | ||
|             tp = variableRunTime.DataType;
 | ||
|         }
 | ||
| 
 | ||
|         return DataNodeType(tp);
 | ||
|     }
 | ||
|     private ServiceResult OnWriteDataValue(ISystemContext context, NodeState node, NumericRange indexRange, QualifiedName dataEncoding, ref object value, ref StatusCode statusCode, ref DateTime timestamp)
 | ||
|     {
 | ||
|         try
 | ||
|         {
 | ||
|             var context1 = context as ServerSystemContext;
 | ||
|             if (context1.UserIdentity.TokenType == UserTokenType.Anonymous)
 | ||
|             {
 | ||
|                 return StatusCodes.BadUserAccessDenied;
 | ||
|             }
 | ||
|             OPCUATag variable = node as OPCUATag;
 | ||
|             if (NodeIdTags.TryGetValue(variable.NodeId, out OPCUATag tag))
 | ||
|             {
 | ||
|                 if (StatusCode.IsGood(variable.StatusCode))
 | ||
|                 {
 | ||
|                     //仅当指定了值时才将值写入
 | ||
|                     if (variable.Value != null)
 | ||
|                     {
 | ||
|                         var nv = new KeyValuePair<string, string>(variable.SymbolicName, value?.ToString());
 | ||
| 
 | ||
|                         var result = _rpcCore.InvokeDeviceMethodAsync("OPCUASERVER-" + context1?.OperationContext?.Session?.Identity?.DisplayName, nv).ConfigureAwait(true).GetAwaiter().GetResult();
 | ||
|                         if (result.IsSuccess)
 | ||
|                         {
 | ||
|                             return StatusCodes.Good;
 | ||
|                         }
 | ||
|                         else
 | ||
|                         {
 | ||
|                             return new(StatusCodes.BadWaitingForResponse, result.Message);
 | ||
|                         }
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
|             }
 | ||
|             return StatusCodes.BadWaitingForResponse;
 | ||
|         }
 | ||
|         catch
 | ||
|         {
 | ||
|             return StatusCodes.BadTypeMismatch;
 | ||
|         }
 | ||
| 
 | ||
|     }
 | ||
| 
 | ||
|     private byte ProtectTypeTrans(DeviceVariableRunTime variableRunTime)
 | ||
|     {
 | ||
|         byte result = 0;
 | ||
|         result = variableRunTime.ProtectTypeEnum switch
 | ||
|         {
 | ||
|             ProtectTypeEnum.ReadOnly => (byte)(result | AccessLevels.CurrentRead),
 | ||
|             ProtectTypeEnum.ReadWrite => (byte)(result | AccessLevels.CurrentReadOrWrite),
 | ||
|             _ => (byte)(result | AccessLevels.CurrentRead),
 | ||
|         };
 | ||
|         if (variableRunTime.HisEnable)
 | ||
|         {
 | ||
|             result = (byte)(result | AccessLevels.HistoryRead);
 | ||
|         }
 | ||
|         return result;
 | ||
|     }
 | ||
| 
 | ||
| }
 | 
