本项目隶属于 HslCommunication 项目的SDK套件,如果不清楚HslCommunication组件的话,可以先了解那个项目,源代码地址:
本项目源代码地址:https://github.com/dathlin/SharpNodeSettings
本项目的主要实现的功能主要有2个:
- 实现单个设备信息的可配置,可存储,采用一个相对标准的Xml存储机制实现,适用的场景是:如果你有20个西门子PLC(种类需要一致),但是PLC的ip地址不一致,或是具体的型号不一致,需要进行可视化的存储
- 实现一个数据网关中心,内置了一个自身协议的网络,当然您也可以实现其他的,比如示例项目里的Redis数据网关,OPC UA数据网关。
本项目的所有的核心构建,都是围绕一定格式的Xml文件展开的,以 NodeClass 作为节点的基类,赋予每个节点 Name 值,Description 值,节点下可以跟随子节点,或是跟随设备节点,设备下可以跟随请求节点,多说无益,直接上代码
以上就是一个示例的XML文件,手动创建这样的一个数据表将会是难以想象的,所以本组件提供了可视化的数据创建中心,
Form nodeSettings = new SharpNodeSettings.View.FormNodeSetting( "settings.xml" )nodeSettings.ShowDialog();
这样就可以显示一个窗体,显示节点配置信息了。
不仅可以配置左侧的节点,设备信息,还支持配置解析规则和可视化的显示,辅助你找到正确的字节索引。点击保存,即可生成上述示例的一个xml配置表。
我们有了这个配置文件后,如何才能解析出来,并且生成相应的设备呢?
我们可以调用 SharpNodeServer 来创建服务器应用,可以生成相应的节点信息,并且根据配置信息来请求设备,更新对应的数据。创建服务器的代码如下:
SharpNodeServer sharpNodeServer = new SharpNodeServer( );sharpNodeServer.LoadByXmlFile( "settings.xml" );sharpNodeServer.ServerStart( 12345 );
这样就启动了一个最简单的服务器,主要包含实例化,加载配置,启动服务器,注意:加载配置必须放置到服务器启动之前。
怎样查看服务器的数据呢?内置了一个默认的 SimplifyNet 服务器,想要知道更多的这个服务器的内容,可以参照下面的博客:
基于 NetSimplifyClient 实现了一个通用的数据节点查看器,需要指定服务器的Ip地址和端口号:
SharpNodeSettings.View.FormNodeView form = new SharpNodeSettings.View.FormNodeView( "127.0.0.1",12345 );form.ShowDialog();
如果你想实现访问单个的数据,可以使用 NetSimplifyClient 创建的Demo来访问,需要注意的是,此处请求的数据都是序列化的JSON字符串。
在实际开发中,可能你不需要上述的配置功能,你就想实现某个PLC的设备信息是可配置的,那么也可以通过本组件实现:
SharpNodeSettings.View.FormSelectDevice selectDevice = new View.FormSelectDevice( );if (selectDevice.ShowDialog( ) == DialogResult.OK){ XElement xmlDevice = selectDevice.DeviceXml; // 设备的配置对象可用于存储,网络传输等等操作 // 如果想要通过xml信息创建设备 SharpNodeSettings.Device.DeviceCore deviceCore = SharpNodeSettings.Util.CreateFromXElement( xmlDevice ); // 演示读取数据,此处有个问题在于如果是相同种类的PLC,应用还是很方便的,如果是不同种类的,地址模型就比较麻烦。 HslCommunication.OperateResultread = deviceCore.ReadWriteDevice.ReadInt16( "D100" );}
Quick Start
按照如下的步骤走,就可以急速体验本项目所传达的核心功能价值,就可以明白本项目是否符合您的需求。启动测试之前,你需要准备个真实的设备:
- 西门子PLC
- 三菱PLC
- 欧姆龙PLC
- ModbusTcp设备
如果您没有真实的设备,也可以从网上下载个Modbus服务器软件,这里也提供一个下载地址:
下载完成后启动服务器即可。
配置Xml信息
去本项目的目录下配置设备的信息: \SharpNodeSettings\XmlFile 运行 SharpNodeSettings.Tools.exe 进行配置,已经配置了一部分,如果想要快速开始,忽略本步骤也可以。
SampleServer
本示例直接重新生成 SampleServer 项目,启动程序即可。如果想要看实际的数据信息,启动 SharpNodeSettings.NodeView项目查看
RedisServer
本示例是在 SampleServer 的基础上添加了Redis服务器,所以需要先安装好Redis服务器,windows版本下载地址:
当然,最好再下载安装一个redis服务器的可视化工具,此处推荐 RedisDesktopManager
然后基于本项目,重新生成 SharpNodeSettings.RedisServer 项目,启动服务器
上述的 SharpNodeSettings.NodeView 项目依然可以查看,然后下图演示Redis
OpcUaServer
本示例是演示从PLC采集数据并且写入到OPC UA服务器中的示例,重新生成 SharpNodeSettings.OpcUaServer 项目,启动它,如果显示是否增加信任证书时,选择是即可。
首先创建OPC UA服务器项目的时候,需要根据xml文件创建对应的OPC UA节点,这部分还是比较麻烦的
#region INodeManager Members ////// Does any initialization required before the address space can be used. /// ////// The externalReferences is an out parameter that allows the node manager to link to nodes /// in other node managers. For example, the 'Objects' node is managed by the CoreNodeManager and /// should have a reference to the root folder node(s) exposed by this node manager. /// public override void CreateAddressSpace( IDictionary> externalReferences ) { lock (Lock) { LoadPredefinedNodes( SystemContext, externalReferences ); IList references = null; if (!externalReferences.TryGetValue( ObjectIds.ObjectsFolder, out references )) { externalReferences[ObjectIds.ObjectsFolder] = references = new List ( ); } dict_BaseDataVariableState = new Dictionary ( ); try { // ========================================================================================= // // 此处需要加载本地文件,并且创建对应的节点信息, // // ========================================================================================= sharpNodeServer = new SharpNodeServer( ); sharpNodeServer.WriteCustomerData = ( Device.DeviceCore deviceCore, string name ) => { string opcNode = "ns=2;s=" + string.Join( "/", deviceCore.DeviceNodes ) + "/" + name; lock (Lock) { if (dict_BaseDataVariableState.ContainsKey( opcNode )) { dict_BaseDataVariableState[opcNode].Value = deviceCore.GetDynamicValueByName( name ); dict_BaseDataVariableState[opcNode].ClearChangeMasks( SystemContext, false ); } } }; XElement element = XElement.Load( "settings.xml" ); dicRegularItemNode = SharpNodeSettings.Util.ParesRegular( element ); AddNodeClass( null, element, references ); // 加载配置文件之前设置写入方法 sharpNodeServer.LoadByXmlFile( "settings.xml" ); // 最后再启动服务器信息 sharpNodeServer.ServerStart( 12345 ); } catch (Exception e) { Utils.Trace( e, "Error creating the address space." ); } } } private void AddNodeClass( NodeState parent, XElement nodeClass, IList references ) { foreach (var xmlNode in nodeClass.Elements( )) { if (xmlNode.Name == "NodeClass") { SharpNodeSettings.Node.NodeBase.NodeClass nClass = new SharpNodeSettings.Node.NodeBase.NodeClass( ); nClass.LoadByXmlElement( xmlNode ); FolderState son; if (parent == null) { son = CreateFolder( null, nClass.Name ); son.Description = nClass.Description; son.AddReference( ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder ); references.Add( new NodeStateReference( ReferenceTypes.Organizes, false, son.NodeId ) ); son.EventNotifier = EventNotifiers.SubscribeToEvents; AddRootNotifier( son ); AddNodeClass( son, xmlNode, references ); AddPredefinedNode( SystemContext, son ); } else { son = CreateFolder( parent, nClass.Name, nClass.Description ); AddNodeClass( son, xmlNode, references ); } } else if (xmlNode.Name == "DeviceNode") { AddDeviceCore( parent, xmlNode ); } else if (xmlNode.Name == "Server") { AddServer( parent, xmlNode, references ); } } } private void AddDeviceCore( NodeState parent, XElement device ) { if (device.Name == "DeviceNode") { // 提取名称和描述信息 string name = device.Attribute( "Name" ).Value; string description = device.Attribute( "Description" ).Value; // 创建OPC节点 FolderState deviceFolder = CreateFolder( parent, device.Attribute( "Name" ).Value, device.Attribute( "Description" ).Value ); // 添加Request foreach (var requestXml in device.Elements( "DeviceRequest" )) { DeviceRequest deviceRequest = new DeviceRequest( ); deviceRequest.LoadByXmlElement( requestXml ); AddDeviceRequest( deviceFolder, deviceRequest ); } } } private void AddServer( NodeState parent, XElement xmlNode, IList references ) { int serverType = int.Parse( xmlNode.Attribute( "ServerType" ).Value ); if (serverType == ServerNode.ModbusServer) { NodeModbusServer serverNode = new NodeModbusServer( ); serverNode.LoadByXmlElement( xmlNode ); FolderState son = CreateFolder( parent, serverNode.Name, serverNode.Description ); AddNodeClass( son, xmlNode, references ); } else if (serverType == ServerNode.AlienServer) { AlienServerNode alienNode = new AlienServerNode( ); alienNode.LoadByXmlElement( xmlNode ); FolderState son = CreateFolder( parent, alienNode.Name, alienNode.Description ); AddNodeClass( son, xmlNode, references ); } } private void AddDeviceRequest( NodeState parent, DeviceRequest deviceRequest ) { // 提炼真正的数据节点 if (!dicRegularItemNode.ContainsKey( deviceRequest.PraseRegularCode )) return; List regularNodes = dicRegularItemNode[deviceRequest.PraseRegularCode]; foreach (var regularNode in regularNodes) { if (regularNode.RegularCode == RegularNodeTypeItem.Bool.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Boolean, ValueRanks.Scalar, default( bool ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Boolean, ValueRanks.OneDimension, new bool[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.Byte.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Byte, ValueRanks.Scalar, default( byte ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Byte, ValueRanks.OneDimension, new byte[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.Int16.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Int16, ValueRanks.Scalar, default( short ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Int16, ValueRanks.OneDimension, new short[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.UInt16.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.UInt16, ValueRanks.Scalar, default( ushort ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.UInt16, ValueRanks.OneDimension, new ushort[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.Int32.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Int32, ValueRanks.Scalar, default( int ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Int32, ValueRanks.OneDimension, new int[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.UInt32.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.UInt32, ValueRanks.Scalar, default( uint ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.UInt32, ValueRanks.OneDimension, new uint[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.Float.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Float, ValueRanks.Scalar, default( float ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Float, ValueRanks.OneDimension, new float[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.Int64.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Int64, ValueRanks.Scalar, default( long ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Int64, ValueRanks.OneDimension, new long[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.UInt64.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.UInt64, ValueRanks.Scalar, default( ulong ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.UInt64, ValueRanks.OneDimension, new ulong[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.Double.Code) { if (regularNode.TypeLength == 1) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Double, ValueRanks.Scalar, default( double ) ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } else { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.Double, ValueRanks.OneDimension, new double[regularNode.TypeLength] ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } else if (regularNode.RegularCode == RegularNodeTypeItem.StringAscii.Code || regularNode.RegularCode == RegularNodeTypeItem.StringUnicode.Code || regularNode.RegularCode == RegularNodeTypeItem.StringUtf8.Code) { var dataVariableState = CreateBaseVariable( parent, regularNode.Name, regularNode.Description, DataTypeIds.String, ValueRanks.OneDimension, string.Empty ); dict_BaseDataVariableState.Add( dataVariableState.NodeId.ToString( ), dataVariableState ); } } } /// /// 创建一个新的节点,节点名称为字符串 /// protected FolderState CreateFolder( NodeState parent, string name ) { return CreateFolder( parent, name, string.Empty ); } ////// 创建一个新的节点,节点名称为字符串 /// protected FolderState CreateFolder( NodeState parent, string name, string description ) { FolderState folder = new FolderState( parent ); folder.SymbolicName = name; folder.ReferenceTypeId = ReferenceTypes.Organizes; folder.TypeDefinitionId = ObjectTypeIds.FolderType; folder.Description = description; if (parent == null) { folder.NodeId = new NodeId( name, NamespaceIndex ); } else { folder.NodeId = new NodeId( parent.NodeId.ToString( ) + "/" + name ); } folder.BrowseName = new QualifiedName( name, NamespaceIndex ); folder.DisplayName = new LocalizedText( name ); folder.WriteMask = AttributeWriteMask.None; folder.UserWriteMask = AttributeWriteMask.None; folder.EventNotifier = EventNotifiers.None; if (parent != null) { parent.AddChild( folder ); } return folder; } ////// 创建一个值节点,类型需要在创建的时候指定 /// protected BaseDataVariableState CreateBaseVariable( NodeState parent, string name, string description, NodeId dataType, int valueRank, object defaultValue ) { BaseDataVariableState variable = new BaseDataVariableState( parent ); variable.SymbolicName = name; variable.ReferenceTypeId = ReferenceTypes.Organizes; variable.TypeDefinitionId = VariableTypeIds.BaseDataVariableType; if (parent == null) { variable.NodeId = new NodeId( name, NamespaceIndex ); } else { variable.NodeId = new NodeId( parent.NodeId.ToString( ) + "/" + name ); } variable.Description = description; variable.BrowseName = new QualifiedName( name, NamespaceIndex ); variable.DisplayName = new LocalizedText( name ); variable.WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description; variable.UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description; variable.DataType = dataType; variable.ValueRank = valueRank; variable.AccessLevel = AccessLevels.CurrentReadOrWrite; variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite; variable.Historizing = false; variable.Value = defaultValue; variable.StatusCode = StatusCodes.Good; variable.Timestamp = DateTime.Now; if (parent != null) { parent.AddChild( variable ); } return variable; }
然后再启动一个 OPC UA Client的示例项目