opcua认证测试1108 增加对三菱,西门子,modbus支持
一句话概括:在服务器中添加三个独立线程,对三菱,西门子,modbus的服务器进行读取地址位置,并将值转换,更新到服务器上的相应节点上。实现opcua对上述三种协议的支持。而上位机只需专注于对opcua服务器上数据获取即可,不用操心更多的plc问题。
1.线程:单独获取对应plc地址上数据
2.数据转换,并更新至对应节点
主要核心概括:
服务器启动时,读取三菱,西门子,modubus的一个地址位置,更新到节点上
https://www.cnblogs.com/dathlin/p/8976955.html C# OPC UA服务器 OPC UA网关 三菱 西门子 欧姆龙 Modbus转OPC UA 服务器 可配置的OPC UA服务器网关 HslSharp软件文档
https://blog.csdn.net/qq_38693757/article/details/121675847 c#读取ini
服务器增加三菱,西门子,modbus相关节点。增加对应节点读写能力。测试更新后的三协议通讯,测试ctt
主要实践内容:
C#读写三菱PLC和西门子PLC数据 使用TCP/IP 协议
C# 读写西门子PLC数据,包含S7协议和Fetch/Write协议,s7支持200smart,300PLC,1200PLC,1500PLC - dathlin - 博客园
C# ModBus Tcp读写数据 与服务器进行通讯 安卓modbus tcp
举例三菱:单独开线程,去读取对应地址位置。并更新数据
//线程启动的地方
//melsec thread
if (IniFunc.getString("IsUsePlc", "IsUsePlc1", null, filename)=="true")
{
Thread melsec_thread = new Thread(MelsecStatusThreadAsync);
melsec_thread.Start();
Utils.LogInfo(" melsec_thread.Start");
}
/// <summary>
/// Melsec Status thread, prints value of that address every 10 seconds.
/// </summary>
private void MelsecStatusThreadAsync()
{
string a = IniFunc.getString("Server", "TimeInterval", null, filename);
int timeinterval = Convert.ToInt32(a);
// 配置PLC连接参数
string ipAddress = IniFunc.getString("IPPORT1", "IP", null, filename); // PLC的IP地址
string port = IniFunc.getString("IPPORT1", "Port", null, filename); // PLC的IP地址; // PLC的端口号
_melsecMc_net = new MelsecMcNet();
_melsecMc_net.IpAddress = ipAddress;
_melsecMc_net.Port = int.Parse(port);
_melsecMc_net.ConnectTimeOut = 10000; // 网络连接的超时时间
try
{
OperateResult connect = _melsecMc_net.ConnectServer();
if (connect.IsSuccess)
{
Console.WriteLine("Roll PLC Melsec_MC Connect Success!!");
Utils.LogInfo("Roll PLC Melsec_MC Connect Success!");
}
else
{
Console.WriteLine("Roll PLC Melsec_MC Connect Fail!!");
Utils.LogInfo("Roll PLC Melsec_MC Connect Fail!!");
return;
}
// 开始定期读取使能地址
while (connect.IsSuccess)
{
ReadMelsecAddress();
// 等待xx秒
Thread.Sleep(timeinterval);
}
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
finally
{
// 断开与PLC的连接
//if (plc.IsConnected)
//{
// plc.Disconnect();
// Console.WriteLine("已断开与PLC的连接。");
//}
}
}
private void ReadMelsecAddress()
{
//melsec
try
{
// update value node
List<bool> boollist = new List<bool>();
//1
OperateResult<bool> result = new OperateResult<bool>();
string address = IniFunc.getString("ADDRESS1", "ConnectAdd", null, filename);
result = _melsecMc_net.ReadBool(address);
if (result.IsSuccess)
{
// 解析并输出读取到的值
var value = result.Content.ToString(); // 这里可能需要根据实际情况进行值类型转换
Console.WriteLine($"Melsec地址 {address} 的当前值: {value}");
boollist.Add(Convert.ToBoolean(value));
//m_server.CurrentInstance.NodeManager.NodeManagers.Last().ModifyBoolList(boollist);
}
else
{
Console.WriteLine($"Melsec读取地址 {address} 失败: {result.ErrorCode}");
}
//2 LoopEnadbleAdd
result = new OperateResult<bool>();
address = IniFunc.getString("ADDRESS1", "LoopEnadbleAdd", null, filename);
result = _melsecMc_net.ReadBool(address);
if (result.IsSuccess)
{
// 解析并输出读取到的值
var value = result.Content.ToString(); // 这里可能需要根据实际情况进行值类型转换
Console.WriteLine($"Melsec地址 {address} 的当前值: {value}");
// update value node
boollist.Add(Convert.ToBoolean(value));
}
else
{
Console.WriteLine($"Melsec读取地址 {address} 失败: {result.ErrorCode}");
}
//update
m_server.CurrentInstance.NodeManager.NodeManagers.Last().ModifyBoolListMelsec(boollist);
//3 Testint16
OperateResult<Int16> resultint16 = new OperateResult<Int16>();
address = IniFunc.getString("ADDRESS1", "Int16Test", null, filename);
resultint16 = _melsecMc_net.ReadInt16(address);
if (resultint16.IsSuccess)
{
// 解析并输出读取到的值
var value = resultint16.Content.ToString(); // 这里可能需要根据实际情况进行值类型转换
Console.WriteLine($"Melsec地址 {address} 的当前值: {value}");
// update value node
List<Int16> Int16list = new List<Int16>();
Int16list.Add(Convert.ToInt16(value));
m_server.CurrentInstance.NodeManager.NodeManagers.Last().ModifyInt16ListMelsec(Int16list);
}
else
{
Console.WriteLine($"Melsec读取地址 {address} 失败: {result.ErrorCode}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Melsec读取使能地址时发生错误: {ex.Message}");
}
}
服务器部分需要增加该节点的创建。增加对节点值的更新方法。
public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
{
///增加三菱,西门子,modbus等节点
}
举例三菱:在opcua中增加三菱节点
#region Create Melsec
//1
FolderState Melsec = CreateFolder(null, "Machines_Melsec");
Melsec.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, Melsec.NodeId));
Melsec.EventNotifier = EventNotifiers.SubscribeToEvents;
AddRootNotifier(Melsec);
//2
string[] machinesMelsec = new string[] { "Melsec" };
collectTimeListMelsec = new List<BaseDataVariableState<DateTime>>();
AMSDataListFloatMelsec = new List<BaseDataVariableState<float>>();
AMSPartitionDataListFloatMelsec = new List<BaseDataVariableState<float>>();
//增加备用节点Bool
AMSDataListBoolMelsec = new List<BaseDataVariableState<bool>>();
AMSDataListStringMelsec = new List<BaseDataVariableState<string>>();
AMSDataListInt16Melsec = new List<BaseDataVariableState<Int16>>();
AMSDataListInt32Melsec = new List<BaseDataVariableState<Int32>>();
AMSDataListInt64Melsec = new List<BaseDataVariableState<Int64>>();
AMSDataListUInt16Melsec = new List<BaseDataVariableState<UInt16>>();
AMSDataListUInt32Melsec = new List<BaseDataVariableState<UInt32>>();
AMSDataListUInt64Melsec = new List<BaseDataVariableState<UInt64>>();
AMSThicknessMeasDataListFloatMelsec = new List<BaseDataVariableState<float>>();
AMSDataListDoubleMelsec = new List<BaseDataVariableState<double>>();
for (int i = 0; i < machinesMelsec.Length; i++)
{
FolderState myFolder = CreateFolder(Melsec, machinesMelsec[i]);
//MachineParam.Closed loop enable ConnectAdd
BaseDataVariableState<bool> Closedloopenable = CreateVariable(myFolder, "MachineParam.Closed loop enable", DataTypeIds.Boolean, ValueRanks.Scalar, true);
Closedloopenable.Description = "Closed loop enable parameter";
AMSDataListBoolMelsec.Add(Closedloopenable);
//MachineParam.Closed loop ON/OFF
BaseDataVariableState<bool> ClosedLoopONOFF = CreateVariable(myFolder, "MachineParam.Closed loop ON/OFF", DataTypeIds.Boolean, ValueRanks.Scalar, true);
ClosedLoopONOFF.Description = "Closed loop ON/OFF parameter";
AMSDataListBoolMelsec.Add(ClosedLoopONOFF);
分区数
//BaseDataVariableState<float> partitionNumNode = CreateVariable(myFolder, "MachineParam.number.partitions", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//partitionNumNode.Description = "Number of partitions (active lanes)";
//AMSDataListFloatMelsec.Add(partitionNumNode);
ProcessVariable.IDProductConfiguration
//BaseDataVariableState<string> IDProductConfiguration = CreateVariable(myFolder, "ProcessVariable.IDProductConfiguration", DataTypeIds.String, ValueRanks.Scalar, "default");
//IDProductConfiguration.Description = "ID Production configuration";
//AMSDataListStringMelsec.Add(IDProductConfiguration);
MachineParam.dead_pixel_number_left_edge
//BaseDataVariableState<float> dead_pixel_number_left_edge = CreateVariable(myFolder, "MachineParam.dead_pixel_number_left_edge", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//dead_pixel_number_left_edge.Description = "Number of dead pixels on the left edge";
//AMSDataListFloatMelsec.Add(dead_pixel_number_left_edge);
MachineParam.dead_pixel_number_right_edge
//BaseDataVariableState<float> dead_pixel_number_right_edge = CreateVariable(myFolder, "MachineParam.dead_pixel_number_right_edge", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//dead_pixel_number_right_edge.Description = "Number of dead pixels on the right edge";
//AMSDataListFloatMelsec.Add(dead_pixel_number_right_edge);
MachineParam.dead_pixel_number_middle_edge
//BaseDataVariableState<float> dead_pixel_number_middle_edge = CreateVariable(myFolder, "MachineParam.dead_pixel_number_middle_edge", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//dead_pixel_number_middle_edge.Description = "Number of dead pixels on the middle edge";
//AMSDataListFloatMelsec.Add(dead_pixel_number_middle_edge);
MachineParam.Bvalue
//BaseDataVariableState<float> Bvalue = CreateVariable(myFolder, "MachineParam.Bvalue", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//Bvalue.Description = "Bvalue parameter";
//AMSDataListFloatMelsec.Add(Bvalue);
MachineParam.Kvalue
//BaseDataVariableState<float> Kvalue = CreateVariable(myFolder, "MachineParam.Kvalue", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//Kvalue.Description = "Kvalue parameter";
//AMSDataListFloatMelsec.Add(Kvalue);
MachineParam.ScanningSpeed
//BaseDataVariableState<float> ScanningSpeed = CreateVariable(myFolder, "MachineParam.ScanningSpeed", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//ScanningSpeed.Description = "Scanning Speed parameter";
//AMSDataListFloatMelsec.Add(ScanningSpeed);
MachineParam.SmoothingFactor
//BaseDataVariableState<float> SmoothingFactor = CreateVariable(myFolder, "MachineParam.SmoothingFactor", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//SmoothingFactor.Description = "Smoothing factor parameter";
//AMSDataListFloatMelsec.Add(SmoothingFactor);
MachineParam.CalibrationTime
//BaseDataVariableState<float> CalibrationTime = CreateVariable(myFolder, "MachineParam.CalibrationTime", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//CalibrationTime.Description = "Calibration time interval parameter";
//AMSDataListFloatMelsec.Add(CalibrationTime);
MachineParam.CloseLoopIntervalMeter
//BaseDataVariableState<float> CloseLoopIntervalMeter = CreateVariable(myFolder, "MachineParam.CloseLoopIntervalMeter", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//CloseLoopIntervalMeter.Description = "Close loop interval parameter";
//AMSDataListFloatMelsec.Add(CloseLoopIntervalMeter);
MachineParam.ActivationSpeed.Threshold
//BaseDataVariableState<float> ActivationSpeed = CreateVariable(myFolder, "MachineParam.ActivationSpeed.Threshold", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//ActivationSpeed.Description = "The lowest speed of walking";
//AMSDataListFloatMelsec.Add(ActivationSpeed);
ProcessVariable.length
//BaseDataVariableState<float> length = CreateVariable(myFolder, "ProcessVariable.length", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//length.Description = "Coil strip length";
//AMSDataListFloatMelsec.Add(length);
ProcessVariable.temperature
//BaseDataVariableState<float> temperature = CreateVariable(myFolder, "ProcessVariable.temperature", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//temperature.Description = "temperature";
//AMSDataListFloatMelsec.Add(temperature);
ProcessVariable.humidity
//BaseDataVariableState<float> humidity = CreateVariable(myFolder, "ProcessVariable.humidity", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//humidity.Description = "humidity";
//AMSDataListFloatMelsec.Add(humidity);
//ProcessVariable.scan times
BaseDataVariableState<Int16> scantimes = CreateVariable(myFolder, "ProcessVariable.scan times", DataTypeIds.Int16, ValueRanks.Scalar, Int16.MinValue);
scantimes.Description = "scan counter";
AMSDataListInt16Melsec.Add(scantimes);
//ProcessVariable.time
//BaseDataVariableState<string> time = CreateVariable(myFolder, "ProcessVariable.time", DataTypeIds.String, ValueRanks.Scalar, "2024/10/26");
//time.Description = "Scan timestamp";
//AMSDataListStringMelsec.Add(time);
ProcessVariable.firstStandValue
//BaseDataVariableState<float> firstStandValue = CreateVariable(myFolder, "ProcessVariable.firstStandValue", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//firstStandValue.Description = "firstStandValue";
//AMSDataListFloatMelsec.Add(firstStandValue);
ProcessVariable.firstMeansureValue
//BaseDataVariableState<float> firstMeansureValue = CreateVariable(myFolder, "ProcessVariable.firstMeansureValue", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//firstMeansureValue.Description = "firstMeansureValue";
//AMSDataListFloatMelsec.Add(firstMeansureValue);
ProcessVariable.Correction
//BaseDataVariableState<float> Correction = CreateVariable(myFolder, "ProcessVariable.Correction", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//Correction.Description = "Correction";
//AMSDataListFloatMelsec.Add(Correction);
ProcessVariable.secondStandValue
//BaseDataVariableState<float> secondStandValue = CreateVariable(myFolder, "ProcessVariable.secondStandValue", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//secondStandValue.Description = "secondStandValue";
//AMSDataListFloatMelsec.Add(secondStandValue);
ProcessVariable.secondMeansureValue
//BaseDataVariableState<float> secondMeansureValue = CreateVariable(myFolder, "ProcessVariable.secondMeansureValue", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//secondMeansureValue.Description = "secondMeansureValue";
//AMSDataListFloatMelsec.Add(secondMeansureValue);
ProcessVariable.threeStandValue
//BaseDataVariableState<float> threeStandValue = CreateVariable(myFolder, "ProcessVariable.threeStandValue", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//threeStandValue.Description = "threeStandValue";
//AMSDataListFloatMelsec.Add(threeStandValue);
ProcessVariable.threeMeansureValue
//BaseDataVariableState<float> threeMeansureValue = CreateVariable(myFolder, "ProcessVariable.threeMeansureValue", DataTypeIds.Float, ValueRanks.Scalar, 0f);
//threeMeansureValue.Description = "threeMeansureValue";
//AMSDataListFloatMelsec.Add(threeMeansureValue);
ProcessVariable.ThicknessMeas
最大支持1500,
//for (int iPartition = 1; iPartition <= 1500; iPartition++)
//{
// BaseDataVariableState<float> ThicknessMeasurement = CreateVariable(myFolder, "ProcessVariable.ThicknessMeas[" + iPartition + "]", DataTypeIds.Float, ValueRanks.Scalar, 0f);
// ThicknessMeasurement.Description = "ThicknessMeasurement " + iPartition;
// AMSThicknessMeasDataListFloat.Add(ThicknessMeasurement);
//}
最大支持100分区, 第一分区//ProcessVariable.lane
//for (int iPartition = 1; iPartition <= 100; iPartition++)
//{
// BaseDataVariableState<float> partitionEveryNode = CreateVariable(myFolder, "ProcessVariable.lane[" + iPartition + "]", DataTypeIds.Float, ValueRanks.Scalar, 0f);
// partitionEveryNode.Description = "lane" + iPartition;
// AMSPartitionDataListFloat.Add(partitionEveryNode);
//}
}
AddPredefinedNode(SystemContext, Melsec);
#endregion
举例opcua获取三菱数据后,更新至节点的实现:
#region Modify Melsec value
public override void ModifyInt16ListMelsec(List<Int16> Int16List)
{
if (AMSDataListInt16Melsec != null)
{
lock (Lock)
{
for (int i = 0; i < Int16List.Count; i++)
{
AMSDataListInt16Melsec[i].Value = Int16List[i];
}
// 下面这行代码非常的关键,涉及到更改之后会不会通知到客户端
for (int i = 0; i < Int16List.Count; i++)
{
AMSDataListInt16Melsec[i].ClearChangeMasks(SystemContext, false);
}
}
}
}
public override void ModifyBoolListMelsec(List<bool> BoolList)
{
if (AMSDataListBoolMelsec != null)
{
lock (Lock)
{
for (int i = 0; i < BoolList.Count; i++)
{
AMSDataListBoolMelsec[i].Value = BoolList[i];
}
// 下面这行代码非常的关键,涉及到更改之后会不会通知到客户端
for (int i = 0; i < BoolList.Count; i++)
{
AMSDataListBoolMelsec[i].ClearChangeMasks(SystemContext, false);
}
}
}
}
#endregion