L5Sharp
A library for intuitively interacting with Rockwell's L5X import/export files.
Purpose
The purpose of this library is to offer a reusable, strongly typed, intuitive API over Rockwell's L5X schema, allowing developers to quickly modify or generate L5X content. In doing so, this library can aid in creation of tools or scripts that automate the PLC development, maintenance, or testing processes for automation engineers.
Goals
The following are some high level goals this project aimed to accomplish.
- Provide a simple and intuitive API, making the learning curve as low as possible.
- Ensure performance as much as possible without sacrificing simplicity.
- Make it easy and seamless to extend the API to support custom queries or functions.
- Support strongly typed mutable tag data, so we can reference complex structures statically at compile time.
Packages
You can consume the library via NuGet.
Install-Package L5Sharp
Previously I had two separate libraries but have since consolidated to a single package to avoid confusion and since I think most people would want all functionality anyway.
L5Sharp.Extensions
is no longer maintained.
Quick Start
Install package from Nuget.
Install-Package L5Sharp
Load an L5X file using the
L5X
class static factory methods.var content = L5X.Load("C:\PathToMyFile\FileName.L5X");
Get started by querying elements across the L5X using the
Query()
methods and LINQ extensions. The following query gets all tags and their nested tag members of type TIMER and returns the TagName, Description, and Preset value in a flat list ordered by TagName.var results = content.Query<Tag>() .SelectMany(t => t.Members()) .Where(t => t.DataType == "TIMER") .Select(t => new {t.TagName, t.Description, Preset = t["PRE"].Value}) .OrderBy(v => v.TagName) .ToList();
Query<T>()
returns anIEnumerable<T>
, allowing for complex queries using LINQ and the strongly typed objects in the library. SinceQuery<T>()
queries the entire L5X for the specified type, the above query will return all Tag components found, including controller and program tags. The above query gets all tags and their nested tag members of type TIMER and returns the TagName, Comment, and Preset value in a flat list.
Usage
The LogixContent
class contains LogixContainer
collections for all L5X components,
such as Tag, DataType,
Moulde, and more.
These classes expose methods for querying and modifying the collections and components within the collections.
The following set of examples demonstrate some of these features using the Tags
container.
Querying
Component containers implement IEnumerable<T>
, and hence support LINQ extensions such as ToList()
,
Where()
, and much more.
- Get a list of all components in a container like so:
var tags = content.Tags.ToList();
Note
Tags
on the root LogixContent
class represent controller tags only.
To get program specific tags, access the Tags
container of a
specific Program
component.
- Perform complex filtering using LINQ expressions:
var tags = content.Tags.Where(t => t.DataType == "TIMER" && t["PRE"].Value >= 5000);
Aside from LINQ, the following are some built in ways to get or find components.
- Get a component at a specific index using the indexer property:
var tag = content.Tags[4];
- Get a component by name using
Get()
and specifying the component name:
var tag = content.Tags.Get("MyTag");
Warning
Get()
will throw an exception if the component name was not found or more than one component with
the specified name exists in the container. This is synonymous with Single()
from LINQ.
- Find a component by name using
Find()
and specifying the component name:
var tag = content.Tags.Find("SomeTag");
Note
Using Find
will not throw an exception if the specified component is not found in
the L5X. Rather, it will simply return null
. Find()
is synonymous with FirstOfDefault()
from LINQ.
Modifying
LogixContainer
implement methods for mutating the collections as well. Create new component objects in memory,
configure their properties, and add them to the container.
- Adds a new component to the container.
var tag = new Tag { Name = "MyTag", Value = 100 };
content.Tags.Add(tag);
Warning
Components are not validated as they are created or added to a L5X container. Therefore, adding duplicate component names or components with invalid property values may results in import failures.
- Add a new list of components to the container.
content.Tags.AddRange(new List<Tag>
{
new() { Name = "tag01", Value = 100 },
new() { Name = "tag02", Value = new TIMER { PRE = 2000 } },
new() { Name = "tag03", Value = "This is a string tag value" }
});
- Updating properties of a component will directly update the underlying L5X content.
var tag = content.Tags.Get("MyTag");
tag.Value = 1234;
tag.Description = "This is a tag's description";
- You may also want to apply and update to all components in a collection.
content.Tags.Update(t => t.Comment = string.Empty);
- Or better yet, update only components that satisfy a condition.
content.Tags.Update(t => t.DataType == "MyType", t => t.Comment = "This is an instance ot MyType");
- Remove a component by name.
content.Tags.Remove("MyTag");
- Remove all components that satisfy a condition.
content.Tags.Remove(t => t.DataType == "TypeName");
- Saving will write the updated L5X content to the specified file.
content.Save("C:\PathToMyOutputFile\FileName.L5X");
Components
The following is a list of some of the Logix components this library supports.
- DataType
- AddOnInstruction
- Module
- Tag
- Program
- Routine
- Task
- Trend
- WatchList
- ParameterConnection
For more information for each component, you can read the article [Components] or review the [Api] documentation.
Tag Data
This library supports static access and in memory creation of complex tag data structures.
The following example illustrates how this is done by creating a tag initialized with a TIMER
structure,
and accessing it's PRE
member statically.
//Create a TIMER tag.
var tag = new Tag { Name = "Test", Value = new TIMER { PRE = 5000 } };
//Get PRE value statically
var pre = tag.Value.As<TIMER>().PRE;
//Assert that the value is correct.
pre.Shoud().Be(5000);
This library also comes built in with all atomic logix types (BOOL, DINT, REAL, etc.) and some predefined logix types (TIMER, COUNTER, ALARM_ANALOG, PID, etc). You may also create your own user defined types to perform the same operations as shown above.
For more information on tag data and these complex logix type objects, see the article [Tag Data].
Extensions
Extending this library and it's components or objects is fairly straight forward. We simply make use
of C# extension methods. Since most objects implement ILogixSerializable
, you will have access to the underlying XML,
This can be used as a means to write custom LINQ to XML queries or functions.
The following is an example of using an extension method and LINQ to XML to add a query that gets all DataType
components that are dependent on a specific data type name. In other words, data types that have members of the
specified data type name.
public static IEnumerable<DataType> DependentsOf(this LogixContainer<DataType> dataTypes, string name)
{
return dataTypes.Serialize().Descendants(L5XName.DataType)
.Where(e => e.Descendants(L5XName.Member).Any(m => m.Attribute(L5XName.DataType)?.Value == name))
.Select(e => new DataType(e));
}
Here you see we first call Serialize()
to get the attached XElement
object. We then perform a LINQ to XML query
and finally return a materialized list of DataType
component objects.
For more information on extending the library, see the article [Extensions].
References
For more information regarding the structure and content of Rockwell's L5X file, see [Logix 5000 Controllers Import Export](../refs/Logix 5000 Controllers Import Export.pdf)