C# 使用OPCUA 与CODESYS进行标签通讯

目录

1.导出的标签 识别标签名称

2.引用OPCUA的包

3.读写方法的封装

4.完整的业务模块封装


1.导出的标签 识别标签名称

从CODESYS导出使用标签通讯的模块文档

大概是这样子的

<?xml version="1.0" encoding="utf-8"?>
<Symbolconfiguration xmlns="http://www.3s-software.com/schemas/Symbolconfiguration.xsd"><Header><Version>3.5.14.0</Version><SymbolConfigObject version="4.5.1.0" runtimeid="3.5.18.40" libversion="4.5.0.0" compiler="3.5.20.0" lmm="3.5.20.0" profile="CODESYS V3.5 SP20+" settings="SupportOPCUA, XmlIncludeComments, LayoutCalculator=OptimizedClientSideLayoutCalculator" /><ProjectInfo name="CheckWeek1" devicename="Device" appname="Application" /></Header><TypeList><TypeSimple name="T_BOOL" size="1" swapsize="0" typeclass="Bool" iecname="BOOL" /><TypeSimple name="T_REAL" size="4" swapsize="4" typeclass="Real" iecname="REAL" /><TypeSimple name="T_STRING" size="81" swapsize="0" typeclass="String" iecname="STRING" /></TypeList><NodeList><Node name="Application"><Node name="GVL"><Comment>{attribute 'qualified_only'}</Comment><Node name="Button01" type="T_BOOL" access="ReadWrite" /><Node name="Button02" type="T_BOOL" access="ReadWrite" /><Node name="now_wei" type="T_REAL" access="ReadWrite"><Comment>当前重量值</Comment></Node><Node name="overweight" type="T_BOOL" access="ReadWrite"><Comment>超重</Comment></Node><Node name="pass" type="T_BOOL" access="ReadWrite"><Comment>合格</Comment></Node><Node name="print_wei" type="T_REAL" access="ReadWrite"><Comment>显示重量</Comment></Node><Node name="Reset_ON" type="T_BOOL" access="ReadWrite" /><Node name="underweight" type="T_BOOL" access="ReadWrite"><Comment>欠重</Comment></Node><Node name="检重结果" type="T_STRING" access="ReadWrite" /></Node><Node name="PersistentVars"><Comment>{attribute 'qualified_only'}</Comment><Node name="over_wei" type="T_REAL" access="ReadWrite"><Comment>上限</Comment></Node><Node name="pass_wei" type="T_REAL" access="ReadWrite" /><Node name="under_wei" type="T_REAL" access="ReadWrite"><Comment>下限</Comment></Node><Node name="合格数" type="T_REAL" access="ReadWrite" /><Node name="总数" type="T_REAL" access="ReadWrite" /><Node name="欠重数" type="T_REAL" access="ReadWrite" /><Node name="超重数" type="T_REAL" access="ReadWrite" /></Node></Node></NodeList>
</Symbolconfiguration>

进行通讯的标签 包含在NodeList 节点下的Node标签

标签有层级关系,下面的当前重量值,标签索引就是  GVL.now_wei

<Node name="GVL">
        <Node name="now_wei" type="T_REAL" access="ReadWrite">
          <Comment>当前重量值</Comment>
        </Node>

</Node>

但实际还有前缀和sn,前缀跟设备型号有关,每台设备都可能不一样,最后完整的标签信息是:

ns=4;s=|var|Sinsegye x86_64-Linux-SM-CNC.Application.GVL.now_wei
 

还有要注意类型:

type="T_BOOL"  对应的就是Bool类型

type="T_REAL"  对应的就是float类型

类型不一致,会导致读写出现异常

2.引用OPCUA的包

Nuget 搜索Opc.Ua.Client  找到: OPCFoundation.NetStandard.Opc.Ua.Client

然后安装合适自己程序的版本

我的程序目标框架是.NET Framework 4.6.1 ; OPCUA安装的版本是1.5.376.235

3.读写方法的封装

先定义一个读写通用的数据模型,方便数据交互

  /// <summary>/// 节点数据模型/// 读写共用/// </summary>public class NodeModel{public NodeModel() { }public NodeModel(string name, ushort index) { Name = name; Index = index; }public NodeModel(string name, ushort index, object value) { Name = name; Index = index; Value = value; }/// <summary>/// 标签名/// </summary>public string Name { get; set; }/// <summary>/// NS 索引值/// </summary>public ushort Index { get; set; }/// <summary>/// 写入的值/// </summary>public object Value { get; set; }}

先建立连接-OPCUA

Application 的名字自行定义,需要提供OPC的IP和端口,才能进行连接

        private ISession _session;public ISessionFactory SessionFactory { get; set; } = DefaultSessionFactory.Instance;/// <summary>/// 建立连接/// </summary>/// <returns></returns>public async Task<BaseResult> Connect(){try{if (_session != null && _session.Connected){return BaseResult.Successed;}var endpointUrl = $"opc.tcp://{_ip}:{_port}";//var endpointUrl = "opc.tcp://DESKTOP-DGM4E64:53530/OPCUA/SimulationServer";// 创建客户端应用程序实例var application = new ApplicationInstance{ApplicationName = "LSOpcUaClient",ApplicationType = ApplicationType.Client};// 动态创建配置var config = new ApplicationConfiguration{ApplicationName = "LSOpcUaClient",ApplicationType = ApplicationType.Client,ApplicationUri = "urn:localhost:LSOpcUaClient",ProductUri = "urn:example.com:LSOpcUaClient",SecurityConfiguration = new SecurityConfiguration{AutoAcceptUntrustedCertificates = true,ApplicationCertificate = new CertificateIdentifier{StoreType = "Directory",StorePath = "./PKI/own",SubjectName = "CN=LSOpcUaClient, O=Example, C=US",},TrustedIssuerCertificates = new CertificateTrustList{StoreType = "Directory",StorePath = "./PKI/issuers"},TrustedPeerCertificates = new CertificateTrustList{StoreType = "Directory",StorePath = "./PKI/trusted"},},ClientConfiguration = new ClientConfiguration{DefaultSessionTimeout = 60000,MinSubscriptionLifetime = 60000,}};// 验证配置并应用await config.Validate(ApplicationType.Client);application.ApplicationConfiguration = config;// 检查并创建客户端证书bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, 0);if (!haveAppCertificate){return new BaseResult(false, "无法创建客户端证书");}// 创建并连接会话var endpointDescription = CoreClientUtils.SelectEndpoint(endpointUrl, useSecurity: false);var endpointConfiguration = EndpointConfiguration.Create(config);var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);endpoint.Configuration.OperationTimeout = 2000;_session = await SessionFactory.CreateAsync(config, endpoint, false, false,"urn:DESKTOP-DGM4E64:UnifiedAutomation:UaExpert", 60000, new UserIdentity(), null).ConfigureAwait(false);_session.KeepAliveInterval = 30000; // 30秒心跳_session.KeepAlive += (sender, e) =>{if (e.Status != null && StatusCode.IsBad(e.Status.Code))Connect(); // 触发重连逻辑};_session.SessionClosing += (s, e) =>{};//Console.WriteLine("成功连接到服务器");if (_session.Connected){isConnect = true;return BaseResult.Successed;}else{isConnect = false;return new BaseResult(false, "连接失败");}}catch (Exception ex){isConnect = false;return new BaseResult(false, "连接失败");}}

建立连接后,使用连接对象 _session进行读写的操作。

读取单个数据的方法,使用泛型来决定读取数据的类型,读取T_BOOL就传入bool,读取T_REAL就传入float

        /// <summary>/// 读取单个数据/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name"></param>/// <param name="index"></param>/// <returns></returns>public async Task<T> Read<T>(NodeModel data){if (isConnect){var nodeId = NodeId.Parse($"ns={data.Index};s={data.Name}");var node = await _session.ReadValueAsync(nodeId);return (T)node.Value;}else{return default(T);}}

也可以一次性读取多个标签

       /// <summary>/// 读取多个数据/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name"></param>/// <param name="index"></param>/// <returns></returns>public async ValueTask<List<T>> ReadMultiple<T>(List<NodeModel> datas){if (isConnect){List<NodeId> ids = new List<NodeId>();foreach (var data in datas){var nodeId = NodeId.Parse($"ns={data.Index};s={data.Name}");ids.Add(nodeId);}var nodes = await _session.ReadValuesAsync(ids);List<T> res = new List<T>();foreach (var node in nodes.Item1){res.Add((T)node.Value);}return res;}else{return default(List<T>);}}

写入数据到标签的方法实现:

/// <summary>
/// 通用的写入方法
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<BaseResult> Write(NodeModel data)
{if (isConnect){var requestHeader = new RequestHeader(){};var nodeId = NodeId.Parse($"ns={data.Index};s={data.Name}");var writeValue = new WriteValue{NodeId = nodeId, // 目标节点IDAttributeId = 13,Value = new DataValue() { Value = data.Value } // 写入值(自动类型转换)};var writeRequest = new WriteValueCollection();writeRequest.Add(writeValue);var node = _session.ReadNode(nodeId);if (node is VariableNode variableNode){if ((variableNode.UserAccessLevel & AccessLevels.CurrentWrite) != 0){// 5. 执行写入                var writeResponse = await _session.WriteAsync(requestHeader, writeRequest, CancellationToken.None);var statusCode = writeResponse.Results[0]; // 获取首个节点的写入状态                if (StatusCode.IsGood(statusCode))return BaseResult.Successed;elsereturn new BaseResult(false, $"❌ 写入失败: {statusCode}");}else{return new BaseResult(false, $"节点 {nodeId} 不可写!");}}else{return new BaseResult(false, $"节点 {nodeId} 不可写!");}}else{return new BaseResult(false, "未建立连接");}
}

4.完整的业务模块封装

最后,再整合一些自动重连,读写方法的封装等,一个方便调用的OPCUA通讯模块就封装好了,程序中使用就很便捷了

下面提供完整的封装代码:

using Google.Protobuf.WellKnownTypes;
using LS.Standard.Data;
using LS.WPF.MVVM;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Shapes;namespace WPFClient.Controls
{public class OPCUAControl{/// <summary>/// OPCUA 通讯/// </summary>/// <param name="ip">PLC ip</param>/// <param name="port">PLC 端口</param>/// <param name="name">名称标识</param>public OPCUAControl(string ip, int port, string name = "OPCUA"){_ip = ip;_port = port;_name = name;}#region 属性//重连的时间间隔private int _reconnectTime = 1000;//获取数据的间隔private int _runTime = 100;//避免数据快速丢失,每次发指令循环写入次数private int RecycleWriteTime = 1;//循环写入的睡眠时间 默认取数据间隔+100msprivate int RecycleTime { get => 20; }private bool isConnect = false;private bool _isRunning = false;private string _ip;private int _port;private string _name;private string _prefix = "|var|Sinsegye x86_64-Linux-SM-CNC.Application.";//标签前缀  private ushort _index = 4;private bool isInit = false;private ISession _session;public ISessionFactory SessionFactory { get; set; } = DefaultSessionFactory.Instance;private WeighModel WeighData { get; set; } = new WeighModel();/// <summary>/// 运行后执行一次的方法/// 运行完返回True  返回False会再次执行/// </summary>public event DelegateRunStart OnRunStart;/// <summary>/// 委托  启动时运行一次/// </summary>public delegate bool DelegateRunStart();#endregion/// <summary>/// 建立连接/// </summary>/// <returns></returns>public async Task<BaseResult> Connect(){try{if (_session != null && _session.Connected){return BaseResult.Successed;}var endpointUrl = $"opc.tcp://{_ip}:{_port}";//var endpointUrl = "opc.tcp://DESKTOP-DGM4E64:53530/OPCUA/SimulationServer";// 创建客户端应用程序实例var application = new ApplicationInstance{ApplicationName = "LSOpcUaClient",ApplicationType = ApplicationType.Client};// 动态创建配置var config = new ApplicationConfiguration{ApplicationName = "LSOpcUaClient",ApplicationType = ApplicationType.Client,ApplicationUri = "urn:localhost:LSOpcUaClient",ProductUri = "urn:example.com:LSOpcUaClient",SecurityConfiguration = new SecurityConfiguration{AutoAcceptUntrustedCertificates = true,ApplicationCertificate = new CertificateIdentifier{StoreType = "Directory",StorePath = "./PKI/own",SubjectName = "CN=LSOpcUaClient, O=Example, C=US",},TrustedIssuerCertificates = new CertificateTrustList{StoreType = "Directory",StorePath = "./PKI/issuers"},TrustedPeerCertificates = new CertificateTrustList{StoreType = "Directory",StorePath = "./PKI/trusted"},},ClientConfiguration = new ClientConfiguration{DefaultSessionTimeout = 60000,MinSubscriptionLifetime = 60000,}};// 验证配置并应用await config.Validate(ApplicationType.Client);application.ApplicationConfiguration = config;// 检查并创建客户端证书bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, 0);if (!haveAppCertificate){return new BaseResult(false, "无法创建客户端证书");}// 创建并连接会话var endpointDescription = CoreClientUtils.SelectEndpoint(endpointUrl, useSecurity: false);var endpointConfiguration = EndpointConfiguration.Create(config);var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);endpoint.Configuration.OperationTimeout = 2000;_session = await SessionFactory.CreateAsync(config, endpoint, false, false,"urn:DESKTOP-DGM4E64:UnifiedAutomation:UaExpert", 60000, new UserIdentity(), null).ConfigureAwait(false);_session.KeepAliveInterval = 30000; // 30秒心跳_session.KeepAlive += (sender, e) =>{if (e.Status != null && StatusCode.IsBad(e.Status.Code))Connect(); // 触发重连逻辑};_session.SessionClosing += (s, e) =>{};//Console.WriteLine("成功连接到服务器");if (_session.Connected){isConnect = true;return BaseResult.Successed;}else{isConnect = false;return new BaseResult(false, "连接失败");}}catch (Exception ex){isConnect = false;return new BaseResult(false, "连接失败");}}/// <summary>/// 获取称重数据/// </summary>/// <returns></returns>public WeighModel GetWeighData(){return WeighData;}/*/// <summary>/// 连接到OPC UA服务器/// </summary>/// <param name="serverUrl">服务器地址 (e.g. opc.tcp://localhost:4840)</param>/// <param name="useAuth">是否使用用户名/密码认证</param>/// <param name="username">用户名(可选)</param>/// <param name="password">密码(可选)</param>public async Task ConnectAsync(string serverUrl, bool useAuth = false, string username = "", string password = ""){try{// 选择安全策略 (Basic256Sha256 或 None)var endpointDescription = CoreClientUtils.SelectEndpoint(serverUrl, useSecurity: false);endpointDescription.SecurityPolicyUri = SecurityPolicies.Basic256Sha256;// 配置端点var endpointConfiguration = EndpointConfiguration.Create(_config);var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);// 设置用户身份IUserIdentity identity = useAuth ?new UserIdentity(username, password) :new UserIdentity(new AnonymousIdentityToken());// 创建会话_session = await Session.Create(configuration: _config,endpoint: endpoint,updateBeforeConnect: true,checkDomain: false,sessionName: "OPCUA_Client_Session",sessionTimeout: SessionTimeout,identity: identity,preferredLocales: null);Console.WriteLine($"✅ 已连接到服务器: {serverUrl}");Console.WriteLine($"🔒 安全策略: {_session.Endpoint.SecurityPolicyUri}");// 设置保活回调_session.KeepAliveInterval = 5000;_session.KeepAlive += (session, e) =>{if (e.Status != null && ServiceResult.IsNotGood(e.Status))Console.WriteLine($"⚠️ 连接状态: {e.Status}");};}catch (Exception ex){Console.WriteLine($"❌ 连接失败: {ex.Message}");throw;}}*//// <summary>/// 设置一些参数/// 在Start之前调用才有效/// </summary>/// <param name="reconnectTime">重连的时间间隔</param>/// <param name="runTime">获取数据的间隔</param>/// <param name="recycleWriteTime">避免数据快速丢失,每次发指令循环写入次数</param>public void SetProperty(int reconnectTime, int runTime, int recycleWriteTime){//重连的时间间隔_reconnectTime = reconnectTime;//获取数据的间隔_runTime = runTime;//避免数据快速丢失,每次发指令循环写入次数RecycleWriteTime = recycleWriteTime;}/// <summary>/// 判断是否连接上PLC/// </summary>/// <returns></returns>public bool IsConnect(){return isConnect;}/// <summary>/// 自动运行/// </summary>private void AutoRun(){if (isInit){return;}_isRunning = true;new Thread(() =>{while (true){if (!_isRunning)break;//重连RunConnect();Thread.Sleep(_reconnectTime);}}){ IsBackground = true }.Start();new Thread(() =>{while (true){if (!_isRunning)break;UpdateData();Thread.Sleep(_runTime);}}){ IsBackground = true }.Start();new Thread(() =>{while (true){if (!_isRunning)break;GetStart();Thread.Sleep(_runTime);}}){ IsBackground = true }.Start();RunStatr();isInit = true;}/// <summary>/// 断线重连/// </summary>private void RunConnect(){try{if (!isConnect){if (_session != null){_session.CloseAsync();_session = null;}if (string.IsNullOrEmpty(this._ip) || _port <= 0){Thread.Sleep(1000);return;}Start().Wait();if (isConnect){_reconnectTime = 1000;}}}catch (Exception ex){if (isConnect){LogOperate.Error($"[{_name}] 断线重连", ex);}else{_reconnectTime = 10 * 1000;}isConnect = false;}}/// <summary>/// 更新一次数据/// </summary>private async Task UpdateData(){try{if (_session != null && _session.Connected && isConnect){try{NodeModel node = new NodeModel();node.Name = _prefix + "GVL.now_wei";node.Index = _index;var value =await Read<float>(node);WeighData.Weigh = Math.Round(value,3);}catch (Exception ex){LogOperate.Error("GetWeigh", ex);}try{NodeModel node = new NodeModel();node.Name = _prefix + "GVL.print_wei";node.Index = _index;var value =await Read<float>(node);WeighData.BoxWeigh= Math.Round(value, 3);}catch (Exception ex){LogOperate.Error("GetBoxWeigh", ex);}try{NodeModel node = new NodeModel();node.Name = _prefix + "GVL.检重结果";node.Index = _index;var value =await Read<string>(node);WeighData.WeighResult= value;}catch (Exception ex){LogOperate.Error("GetWeighResult", ex);}try{NodeModel node = new NodeModel();node.Name = _prefix + "GVL.overweight";node.Index = _index;var value =await Read<bool>(node);WeighData.IsOverWeight = value;}catch (Exception ex){LogOperate.Error("IsOverWeight", ex);}try{NodeModel node = new NodeModel();node.Name = _prefix + "GVL.pass";node.Index = _index;var value =await Read<bool>(node);WeighData.IsPass= value;}catch (Exception ex){LogOperate.Error("IsPass", ex);}try{NodeModel node = new NodeModel();node.Name = _prefix + "GVL.underweight";node.Index = _index;var value =await Read<bool>(node);WeighData.IsUnderWeight= value;}catch (Exception ex){LogOperate.Error("IsUnderWeight", ex);}try{NodeModel node = new NodeModel();node.Name = _prefix + "PersistentVars.pass_wei";node.Index = _index;var value =await Read<float>(node);WeighData.StandardWeight= Math.Round(value, 2);}catch (Exception ex){LogOperate.Error("GetStandardWeight", ex);}try{NodeModel node = new NodeModel();node.Name = _prefix + "PersistentVars.over_wei";node.Index = _index;var value = await Read<float>(node);WeighData.UpperLimit= Math.Round(value, 2);}catch (Exception ex){LogOperate.Error("GetUpperLimit", ex);}try{NodeModel node = new NodeModel();node.Name = _prefix + "PersistentVars.under_wei";node.Index = _index;var value = await Read<float>(node);WeighData.LowerLimit= Math.Round(value, 2);}catch (Exception ex){LogOperate.Error("GetLowerLimit", ex);}}else{if (isConnect){LogOperate.Error($"[{_name}] -- 更新数据时通讯 异常");isConnect = false;}Thread.Sleep(1000);}}catch (Exception ex){if (isConnect){LogOperate.Error($"[{_name}] -- UpdateData 异常", ex);}isConnect = false;Thread.Sleep(1000);}}/// <summary>/// 运行在刚启动的时候/// </summary>private void RunStatr(){if (!isInit){while (true){try{if (OnRunStart == null){isInit = true;}else{if (OnRunStart.Invoke()){isInit = true;}}}catch (Exception ex){LogOperate.Error("RunStatr", ex);}if (isInit){return;}Thread.Sleep(1000);}}}public delegate void DelegateOnTrigger();public event DelegateOnTrigger OnTrigger;#region 方法/// <summary>/// 打开通讯,并开始轮询数据/// </summary>/// <returns></returns>public async Task<BaseResult> Start(){try{LogOperate.General(_name, "开始建立通讯");BaseResult res = Stop();if (res){//赋值后IP依然为空,则不再进行连接if (string.IsNullOrEmpty(_ip) && _port <= 0){LogOperate.General(_name, "IP 未配置,不再进行连接");return new BaseResult(false, "IP 未配置,不再进行连接");}res = await Connect();}if (res){isConnect = true;LogOperate.General(_name, "建立通讯成功");Thread.Sleep(500);}return res;}catch (Exception ex){LogOperate.Error($"[{_name}]-Start 异常", ex);return new BaseResult(false, ex.Message);}finally{AutoRun();}}/// <summary>/// 停止通讯/// </summary>/// <returns></returns>public BaseResult Stop(){isConnect = false;_isRunning = false;Thread.Sleep(20);_session?.CloseAsync();return BaseResult.Successed;}#region 业务方法private bool isStart = false;/// <summary>/// 获取两个按钮是否都按下/// </summary>/// <returns></returns>public async Task<bool> GetStart(){try{NodeModel node1 = new NodeModel();node1.Name = _prefix + "GVL.Button01";node1.Index = _index;NodeModel node2 = new NodeModel();node2.Name = _prefix + "GVL.Button02";node2.Index = _index;List<NodeModel> nodes = new List<NodeModel>();nodes.Add(node1);nodes.Add(node2);var value = await ReadMultiple<bool>(nodes);if (value != null){WeighData.IsStart = !value.Contains(false);if (WeighData.IsStart && !isStart){isStart = true;OnTrigger?.Invoke();}if (!WeighData.IsStart && isStart){isStart = false;}}return WeighData.IsStart;}catch (Exception ex){LogOperate.Error("GetStart", ex);return false;}}/// <summary>/// 读取当前实时重量/// </summary>/// <returns></returns>public double GetWeigh(){return WeighData.Weigh;}/// <summary>/// 读取显示重量/// 称重重量/// </summary>/// <returns></returns>public double GetBoxWeigh(){return WeighData.BoxWeigh;}/// <summary>/// 获取减重结果/// </summary>/// <returns></returns>public string GetWeighResult(){return WeighData.WeighResult;            }/// <summary>/// 获取是否超重/// </summary>/// <returns></returns>public bool IsOverWeight(){return WeighData.IsOverWeight;}/// <summary>/// 读取是否Pass/// </summary>/// <returns></returns>public bool IsPass(){return WeighData.IsPass;}/// <summary>/// 读取是否欠重/// </summary>/// <returns></returns>public bool IsUnderWeight(){return WeighData.IsUnderWeight;}/// <summary>/// 获取标准重量/// </summary>/// <returns></returns>public double GetStandardWeight(){return WeighData.StandardWeight;}/// <summary>/// 获取上限 公差/// </summary>/// <returns></returns>public double GetUpperLimit(){return WeighData.UpperLimit;}/// <summary> /// 获取下限 公差/// </summary>/// <returns></returns>public double GetLowerLimit(){return WeighData.LowerLimit;}/// <summary>/// 获取标准重量/// </summary>/// <returns></returns>public BaseResult SetStandardWeight(float num){try{NodeModel node = new NodeModel();node.Name = _prefix + "PersistentVars.pass_wei";node.Index = _index;node.Value = num;var value = Write(node);value.Wait();return value.Result;}catch (Exception ex){LogOperate.Error("SetStandardWeight", ex);return new BaseResult(false, ex.Message);}}/// <summary>/// 设置上限 公差/// </summary>/// <returns></returns>public BaseResult SetUpperLimit(float num){try{NodeModel node = new NodeModel();node.Name = _prefix + "PersistentVars.over_wei";node.Index = _index;node.Value = num;var value = Write(node);value.Wait();return value.Result;}catch (Exception ex){LogOperate.Error("SetUpperLimit", ex);return new BaseResult(false,ex.Message);}}/// <summary>/// 设置下限 公差/// </summary>/// <returns></returns>public BaseResult SetLowerLimit(float num){try{NodeModel node = new NodeModel();node.Name = _prefix + "PersistentVars.under_wei";node.Index = _index;node.Value = num;var value = Write(node);value.Wait();return value.Result;}catch (Exception ex){LogOperate.Error("SetLowerLimit", ex);return new BaseResult(false, ex.Message);}}#endregion/// <summary>/// 通用的写入方法/// </summary>/// <param name="model"></param>/// <returns></returns>public async Task<BaseResult> Write(NodeModel data){if (isConnect){var requestHeader = new RequestHeader(){};var nodeId = NodeId.Parse($"ns={data.Index};s={data.Name}");var writeValue = new WriteValue{NodeId = nodeId, // 目标节点IDAttributeId = 13,Value = new DataValue() { Value = data.Value } // 写入值(自动类型转换)};var writeRequest = new WriteValueCollection();writeRequest.Add(writeValue);var node = _session.ReadNode(nodeId);if (node is VariableNode variableNode){if ((variableNode.UserAccessLevel & AccessLevels.CurrentWrite) != 0){// 5. 执行写入                var writeResponse = await _session.WriteAsync(requestHeader, writeRequest, CancellationToken.None);var statusCode = writeResponse.Results[0]; // 获取首个节点的写入状态                if (StatusCode.IsGood(statusCode))return BaseResult.Successed;elsereturn new BaseResult(false, $"❌ 写入失败: {statusCode}");}else{return new BaseResult(false, $"节点 {nodeId} 不可写!");}}else{return new BaseResult(false, $"节点 {nodeId} 不可写!");}}else{return new BaseResult(false, "未建立连接");}}/// <summary>/// 读取单个数据/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name"></param>/// <param name="index"></param>/// <returns></returns>public async Task<T> Read<T>(NodeModel data){if (isConnect){var nodeId = NodeId.Parse($"ns={data.Index};s={data.Name}");var node = await _session.ReadValueAsync(nodeId);return (T)node.Value;}else{return default(T);}}/// <summary>/// 读取多个数据/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name"></param>/// <param name="index"></param>/// <returns></returns>public async ValueTask<List<T>> ReadMultiple<T>(List<NodeModel> datas){if (isConnect){List<NodeId> ids = new List<NodeId>();foreach (var data in datas){var nodeId = NodeId.Parse($"ns={data.Index};s={data.Name}");ids.Add(nodeId);}var nodes = await _session.ReadValuesAsync(ids);List<T> res = new List<T>();foreach (var node in nodes.Item1){res.Add((T)node.Value);}return res;}else{return default(List<T>);}}#endregion}/// <summary>/// 节点数据模型/// 读写共用/// </summary>public class NodeModel{public NodeModel() { }public NodeModel(string name, ushort index) { Name = name; Index = index; }public NodeModel(string name, ushort index, object value) { Name = name; Index = index; Value = value; }/// <summary>/// 标签名/// </summary>public string Name { get; set; }/// <summary>/// NS 索引值/// </summary>public ushort Index { get; set; }/// <summary>/// 写入的值/// </summary>public object Value { get; set; }}/// <summary>/// 称重平台数据模型/// </summary>public class WeighModel{/// <summary>/// 是否启动/// </summary>public bool IsStart { get; set; }/// <summary>/// 实时重量/// </summary>public double Weigh { get; set; }/// <summary>/// 称重重量/// </summary>public double BoxWeigh { get; set; }/// <summary>/// 称重结果/// </summary>public string WeighResult { get; set; }/// <summary>/// 是否超重/// </summary>public bool IsOverWeight { get; set; }/// <summary>/// 是否合格/// </summary>public bool IsPass { get; set; }/// <summary>/// 是否欠重/// </summary>public bool IsUnderWeight { get; set; }/// <summary>/// 标准重量/// </summary>public double StandardWeight { get; set; }/// <summary>/// 上限公差/// </summary>public double UpperLimit { get; set; }/// <summary>/// 下限公差/// </summary>public double LowerLimit { get; set; }}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96771.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96771.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ 中 `std::map` 的 `insert` 函数

1. 函数的概念与用途 std::map::insert 是 C 标准模板库&#xff08;STL&#xff09;中 map 容器的一个核心成员函数。它的核心任务很明确&#xff1a;向 map 中插入一个新的键值对&#xff08;key-value pair&#xff09;。 核心用途&#xff1a; 数据构建&#xff1a;初始化一…

【机器学习学习笔记】机器学习引言

前言本文章是拨珠自己的学习笔记&#xff0c;自用为主&#xff0c;学习请移步专门教程&#xff0c;若有错误请大佬轻喷&#xff0c;也欢迎同好交流学习。本文将阐述三个问题。什么是机器学习&#xff1f;监督学习、无监督学习到底在干什么&#xff1f;分类、回归、聚类又是怎么…

程序设计---状态机

在软件工程、嵌入式开发、自动化控制等领域&#xff0c;状态机&#xff08;State Machine&#xff09;是一种描述系统行为的强大工具。它通过抽象“状态”“事件”“转换”和“动作”四大核心要素&#xff0c;将复杂的逻辑流程转化为可视化、可验证的状态流转规则&#xff0c;广…

GaussDB 数据库架构师修炼(十八) SQL引擎-分布式计划

1 分布式架构GaussDB基于MPP &#xff08;Massively Parallel Processing&#xff09; 并行架构Streaming流式计算框架2 分布式计划CN轻量化&#xff08;light proxy&#xff09; FQS&#xff08; fast query shipping &#xff09; STREAM计划 XC计划计划类型场景原理CN…

微前端架构核心要点对比

1. 样式隔离 常见的隔离方式有以下几种,还是根据自身业务来确定: 1.1. shadowDOM 目前相对来说使用最多的样式隔离机制。 但shadowDOM并不是银弹,由于子应用的样式作用域仅在 shadow 元素下,那么一旦子应用中出现运行时“翻墙”跑到外面构建 DOM 的场景,必定会导致构建…

VMware 17.6安装包下载与保姆级图文安装教程!

软件下载 [软件名称]&#xff1a;VMware 17.6 [软件大小]&#xff1a;226.66MB [系统环境]&#xff1a;win 7/8/10/11或更高&#xff0c;64位操作系统 VMware合集&#xff0c;软件下载&#xff08;夸克网盘需手机打开&#xff09;&#xff1a;&#xff1a;VMware合集丨夸克网…

关于微服务下的不同服务之间配置不能通用的问题

问题引入现有两个服务&#xff0c;一个是 A 服务&#xff0c;一个是 B 服务&#xff0c;并且这两个服务都需要使用 mysql。现 B 服务中引入了 A 服务的依赖&#xff0c;在 A 服务中添加了 mysql 的相关配置&#xff0c;那么这时就有一个问题&#xff1a;既然 B 已经引入了 A 的…

【机器学习项目 心脏病预测】

文章目录心脏病预测导入数据集数据集介绍理解数据数据处理机器学习K近邻分类器逻辑回归支持向量分类器&#xff08;SVC&#xff09;决策树分类器随机森林分类器结论心脏病预测 在这个机器学习项目中&#xff0c;我们使用UCI心脏病数据集 UCI &#xff0c;并将使用机器学习方法…

【论文阅读 | arXiv 2025 | WaveMamba:面向RGB-红外目标检测的小波驱动Mamba融合方法】

论文阅读 | arXiv 2025 | WaveMamba&#xff1a;面向RGB-红外目标检测的小波驱动Mamba融合方法​​1&&2. 摘要&&引言3. 方法3.1. 预备知识3.2. WaveMamba3.3. WaveMamba融合块&#xff08;WMFB&#xff09;3.3.1. 低频Mamba融合块&#xff08;LMFB&#xff09;…

DevExpress发布PowerPoint Presentation API库,支持跨平台与 PDF 导出

DevExpress专注于为 .NET、JavaScript、VCL 等多种平台提供高性能 UI 控件、报表工具、数据可视化组件及开发框架&#xff0c;产品覆盖桌面、Web、移动及跨平台应用开发领域。凭借稳定的性能、丰富的功能与优质的技术支持&#xff0c;DevExpress 的解决方案已广泛应用于金融、制…

Vue3使用 DAG 图(AntV X6)

参考文档 AntV X6 文档 可自定义设置以下属性 容器宽度&#xff08;width&#xff09;&#xff0c;类型&#xff1a;number | string&#xff0c;默认 ‘100%’容器高度&#xff08;height&#xff09;&#xff0c;类型&#xff1a;number | string&#xff0c;默认 ‘100%’…

【数据结构】跳表的概率模型详解与其 C 代码实现

文章目录介绍关键组成部分读者可以比对这张图片去理解跳表 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c5704b6276a14c3f9facdc3e55015bcc.jpeg#pic_center) 核心操作原理算法的概率模型跳表的 C代码实现初始化跳跃表的节点、跳跃表本身跳表插入节点查找元素更新…

Verilog实现除法器

文章目录基本原理确定除数最高位移位相减基本原理 若想得到yx\frac{y}{x}xy​的商和余数&#xff0c;一种最直观的想法就是不断地用yyy减掉xxx&#xff0c;直到y<xy< xy<x为止&#xff0c;写成伪代码如下 z 0 while y<x:y - xz 1这种算实在是太低效了&#xff…

EasyLive的一些疑问

目录 一、pinia是什么 二、html的代码片段又失效&#xff1f; 三、Request.js 四 、状态管理库 五、main.js:19 Uncaught SyntaxError: The requested module /src/utils/Api.js?t1745328489985 does not provide an export named default (at main.js:19:8)​编辑 六、…

C++(String):

目录 string与C中字符串的区别&#xff1a; C字符串&#xff1a; string字符串&#xff1a; string的定义和初始化&#xff1a; 输入字符串&#xff1a; 方式1&#xff1a; 方式2&#xff1a; 字符串的拼接的操作&#xff1a; 方式1&#xff1a;使用“” 方式2&#…

【Linux】Java线上问题,一分钟日志定位

【Linux】Java线上问题&#xff0c;一分钟日志定位1. 查看异常堆栈2. 实时叮新日志3. 翻历史/压缩日志4. 统计异常数量5. 多种异常一起查6. 反向过滤7. 同时满足多个关键字查询8. 定位最近一次异常9. 异常排行榜1. 查看异常堆栈 # 在 a.log 文件中查找包含 NullPointerExcepti…

智慧农业温室大棚远程监控物联网系统解决方案

一、方案背景与目标随着现代农业向智能化、精准化转型&#xff0c;传统温室大棚管理面临效率低、响应慢、成本高等痛点。本方案通过部署御控农业物联网系统&#xff0c;实现温室环境参数实时监测、设备远程控制、数据智能分析及预警决策&#xff0c;助力农户降低人工成本&#…

【剖析高并发秒杀】从流量削峰到数据一致性的架构演进与实践

一、 挑战&#xff1a;三高背景下的数据库瓶颈秒杀场景的核心挑战可以归结为“三高”&#xff1a;高并发、高性能、高可用。而系统中最脆弱的一环&#xff0c;往往是我们的关系型数据库&#xff08;如MySQL&#xff09;。它承载着最终的数据落地&#xff0c;其连接数、IOPS和CP…

Redisson最新版本(3.50.0左右)启动时提示Netty的某些类找不到

文章目录一、写在前面二、解决方案1、解决方案2、一劳永逸3、确定redisson依赖netty的版本一、写在前面 Redisson最新版本&#xff0c;大概3.47.0&#xff0c;在JDK8环境下&#xff08;实测JDK17也一样&#xff09;会提示Netty的某些类找不到&#xff1a; Exception in threa…

MTK Linux DRM分析(八)- KMS drm_crtc.c

一、简介 Linux DRM(Direct Rendering Manager)子系统是内核中管理图形硬件的核心组件,而 CRTC(CRT Controller)又是其中的关键之一。它起源于过去控制阴极射线管(CRT)显示器的控制器概念,如今在现代图形显示中依旧扮演着至关重要的角色。 可以把 CRTC 想象成图形显示…