目录
[1.导出的标签 识别标签名称](#1.导出的标签 识别标签名称)
1.导出的标签 识别标签名称
从CODESYS导出使用标签通讯的模块文档
大概是这样子的
XML
<?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.读写方法的封装
先定义一个读写通用的数据模型,方便数据交互
cs
/// <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和端口,才能进行连接
cs
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
cs
/// <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);
}
}
也可以一次性读取多个标签
cs
/// <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>);
}
}
写入数据到标签的方法实现:
cs
/// <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, // 目标节点ID
AttributeId = 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;
else
return new BaseResult(false, $"❌ 写入失败: {statusCode}");
}
else
{
return new BaseResult(false, $"节点 {nodeId} 不可写!");
}
}
else
{
return new BaseResult(false, $"节点 {nodeId} 不可写!");
}
}
else
{
return new BaseResult(false, "未建立连接");
}
}
4.完整的业务模块封装
最后,再整合一些自动重连,读写方法的封装等,一个方便调用的OPCUA通讯模块就封装好了,程序中使用就很便捷了
下面提供完整的封装代码:
cs
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;
//循环写入的睡眠时间 默认取数据间隔+100ms
private 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, // 目标节点ID
AttributeId = 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;
else
return 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; }
}
}