Skip to content

Commit

Permalink
Merged in latest changes from master
Browse files Browse the repository at this point in the history
  • Loading branch information
timyhac committed Jul 13, 2024
2 parents 424fe50 + 343d1b0 commit dd99d65
Show file tree
Hide file tree
Showing 60 changed files with 839 additions and 252 deletions.
13 changes: 5 additions & 8 deletions .github/workflows/build-publish.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
name: Build and publish libplctag.NET nuget package

on: [push,workflow_dispatch]
on: [workflow_dispatch]

jobs:
build:

runs-on: windows-latest
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
lfs: true

- name: Publish libplctag on version change
run: .\build.cmd ReleaseLibplctag --nuget-api-key ${{secrets.NUGET_API_KEY}}

- name: Publish libplctag.NativeImport on version change
run: .\build.cmd ReleaseLibplctagNativeImport --nuget-api-key ${{secrets.NUGET_API_KEY}}
- name: Publish all packages on version change
run: ./build.sh ReleaseAll --nuget-api-key ${{secrets.NUGET_API_KEY}}
16 changes: 16 additions & 0 deletions .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: CI - Run all tests

on: [push]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
lfs: true

- name: Build all and run tests
run: ./build.sh Test
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup git user
run: |
Expand Down
2 changes: 2 additions & 0 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"Compile",
"PackLibplctag",
"PackLibplctagNativeImport",
"ReleaseAll",
"ReleaseLibplctag",
"ReleaseLibplctagNativeImport",
"Restore",
Expand All @@ -107,6 +108,7 @@
"Compile",
"PackLibplctag",
"PackLibplctagNativeImport",
"ReleaseAll",
"ReleaseLibplctag",
"ReleaseLibplctagNativeImport",
"Restore",
Expand Down
2 changes: 0 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

First off, welcome and thanks for taking the time to contribute!

libplctag.NET is a wrapper for libplctag, and as such is intended to expose libplctag functionality in a way that is convenient to use in .NET applications.

## How can I contribute?

### Reporting bugs
Expand Down
29 changes: 21 additions & 8 deletions dev/README.md → DEVELOPERS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Developer Documentation

This folder is for storing information regarding the development of libplctag.NET - the .NET wrapper for libplctag.
This folder is for storing information regarding the development of libplctag.NET.

## Project Goals

* Package the libplctag functionality in a way that is convenient to use in .NET applications.
* Be cross-platform: It should support any platform that libplctag can be built for, and supports .NET Standard 2.0


## Glossary

Expand All @@ -15,17 +21,24 @@ This folder is for storing information regarding the development of libplctag.NE



## Build Automation

GitHub Actions have been created to automate some common activities such as updating the core binaries and release nuget packages.

These workflows delegate the actual automation logic to the [`_build`](../build) [NUKE](https://nuke.build/) project.

### How to release Nuget packages

1. Update the version number in the csproj files of the libplctag or libplctag.NativeImport projects.
2. Trigger the "Build and publish libplctag.NET nuget package" workflow.
4. Create a GitHub "Release" with details of the new release.

## How to update libplctag.NativeImport
## How to update the core binaries libplctag.NativeImport

When a new version of libplctag core is released, the library binaries need to be updated in libplctag.NativeImport.
The build script can be used to copy these libraries in the project without error.

1. Run the script with the selected version:
`> .\build.cmd UpdateCoreBinaries --libplctag-core-version 2.6.0`
1. Trigger the "Update libplctag core" workflow.
2. Verify that the files have been correctly copied.
3. Make relevant modifications to libplctag.NativeImport such as modifying the method signatures (if required).
4. Increment version number of libplctag.NativeImport project.
5. Run the build script with `ReleaseLibplctagNativeImport` to upload to nuget.
`> .\build.cmd ReleaseLibplctagNatveImport`
Note there is a github action which automatically executes this.
4. Finally, release the updated project as a new Nuget packages.
76 changes: 11 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
<img src="https://raw.githubusercontent.com/libplctag/libplctag.NET/master/assets/libplctag-final.svg?sanitize=true" alt="libplctag" width="180"/>
<p>
<img src="assets/libplctag-logo.svg" alt="libplctag" width="180"/>

# libplctag.NET

[libplctag](https://github.com/libplctag/libplctag) is an open source C library for Linux, Windows and macOS using EtherNet/IP or Modbus TCP to read and write tags in PLCs.

libplctag.NET provides a collection of .NET wrapper packages for libplctag.
libplctag.NET provides .NET wrapper packages for libplctag, and publishes them to Nuget.org.

## Packages

This repository contains two .NET packages that are published to Nuget.org:

| Package | Downloads | Stable | Preview |
|-|-|-|-|
| [libplctag](https://www.nuget.org/packages/libplctag/) | ![Nuget](https://img.shields.io/nuget/dt/libplctag) | ![Nuget version](https://img.shields.io/nuget/v/libplctag) | ![Nuget version](https://img.shields.io/nuget/vpre/libplctag) |
| [libplctag.NativeImport](https://www.nuget.org/packages/libplctag.NativeImport/) | ![Nuget](https://img.shields.io/nuget/dt/libplctag.NativeImport) | ![Nuget version](https://img.shields.io/nuget/v/libplctag.NativeImport) | ![Nuget version](https://img.shields.io/nuget/vpre/libplctag.NativeImport) |


## libplctag
## [libplctag](https://www.nuget.org/packages/libplctag/) ![Nuget](https://img.shields.io/nuget/dt/libplctag) ![Nuget version](https://img.shields.io/nuget/v/libplctag)

This is the package intended for use in .NET applications.
It provides an API for libplctag that should feel natural to .NET developers by supporting the following features:
Expand All @@ -26,65 +15,22 @@ It provides an API for libplctag that should feel natural to .NET developers by
* Async/Await
* Native resource cleanup

This package depends on libplctag.NativeImport to gain access to the underlying libplctag native library.

## libplctag.NativeImport

See [here](docs/libplctag.NativeImport.md) for information on this package.

## Getting Started

Add the libplctag package in the Visual Studio Nuget Package Manager UI or via the commandline:

`> dotnet add package libplctag`

### Simple Example Code for an Allen-Bradley CompactLogix/ControlLogix PLC

```csharp
// Instantiate the tag with the appropriate mapper and datatype
var myTag = new TagDint()
{
//Name is the full path to tag.
Name = "PROGRAM:SomeProgram.SomeDINT",
//Gateway is the IP Address of the PLC or communication module.
Gateway = "10.10.10.10",
//Path is the location in the control plane of the CPU. Almost always "1,0".
Path = "1,0",
PlcType = PlcType.ControlLogix,
Protocol = Protocol.ab_eip,
Timeout = TimeSpan.FromSeconds(5)
};

// Read the value from the PLC
int output = myTag.Read();

// Output to Console
Console.WriteLine($"Original value: SomeProgram.SomeDINT = {output}");

// Write a new value to the PLC then read it back
myTag.Write(37);
output = myTag.Read();

// Output to Console
Console.WriteLine($"Updated value: SomeProgram.SomeDINT = {output}");
```

In advanced scenarios, tags can be instantiated using generics (ex. `Tag<DintPlcMapper, int>`, `Tag<BoolPlcMapper, bool>`) and can be referenced via an `ITag` interface.
See [examples](examples/) and [the docs](docs/libplctag.md#getting-started) for more information.

For more detail and further usage, see the examples in the example projects:
## [libplctag.NativeImport](https://www.nuget.org/packages/libplctag.NativeImport/) ![Nuget](https://img.shields.io/nuget/dt/libplctag.NativeImport) ![Nuget version](https://img.shields.io/nuget/v/libplctag.NativeImport)

* [C#](src/Examples/CSharp%20DotNetCore)
* [VB.NET](src/Examples/VB.NET%20DotNetCore/Program.vb)
This package provides low-level (raw) access to the libplctag core library which is written in C.
The purpose of this package is to expose the API for this library to .NET applications, and handle platform and configuration issues.

## Project Goals
Application developers typically won't need to reference this package directly; it is primarily for use in other wrapper libraries.

* Package the libplctag functionality in a way that is convenient to use in .NET applications.
* Be cross-platform: It should support any platform that libplctag can be built for, and supports .NET Standard 2.0
See [here](docs/libplctag.NativeImport.md) for more information.

## Getting Help

* [libplctag Wiki](https://github.com/libplctag/libplctag/wiki)
* [libplctag.NET docs](docs)
* [Contributions guidance](CONTRIBUTING.md).
* [Contributing](CONTRIBUTING.md)
* [Reporting an issue](CONTRIBUTING.md#reporting-bugs)

libplctag.NET is part of the libplctag organization, so the [same policies apply](https://github.com/libplctag/libplctag#contact-and-support).
File renamed without changes
15 changes: 15 additions & 0 deletions build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,21 @@ class Build : NukeBuild
PushAndTag($"libplctag.NativeImport.{version}.nupkg", $"libplctag.NativeImport-v{version}");
});

Target ReleaseAll => _ => _
.DependsOn(PackLibplctag)
.DependsOn(PackLibplctagNativeImport)
.Requires(() => NugetApiUrl)
.Requires(() => NugetApiKey)
.Requires(() => Configuration.Equals(Configuration.Release))
.Executes(() =>
{
var libplctagNativeImportVersion = libplctag_NativeImport.GetProperty("Version");
PushAndTag($"libplctag.NativeImport.{libplctagNativeImportVersion}.nupkg", $"libplctag.NativeImport-v{libplctagNativeImportVersion}");

var libplctagVersion = libplctag.GetProperty("Version");
PushAndTag($"libplctag.{libplctagVersion}.nupkg", $"libplctag-v{libplctagVersion}");
});

Target UpdateCoreBinaries => _ => _
.Requires(() => LibplctagCoreVersion)
.Executes(() =>
Expand Down
2 changes: 1 addition & 1 deletion docs/libplctag.NativeImport.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Console.WriteLine(theValue);
plctag.plc_tag_destroy(tagHandle);
```

Further examples of its usage can be found [here](src/Examples/CSharp%20DotNetCore/NativeImportExample.cs).
Further examples can be found [here](../examples/CSharp%20NativeImport/).

## How it works

Expand Down
143 changes: 143 additions & 0 deletions docs/libplctag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# libplctag.NET

[libplctag](https://github.com/libplctag/libplctag) is a C library for Linux, Android, Windows and macOS that uses EtherNet/IP or Modbus TCP to read and write tags in PLCs.

[libplctag.NET](https://www.nuget.org/packages/libplctag/) provides wrapper packages for libplctag, with an API naturalised to .NET by adding the following features:

* Values are strongly-typed (both Atomic types and User-Defined Types).
* Errors are thrown as Exceptions
* Async/Await
* Native resource cleanup

## How to use

```csharp
// Example tag configuration for a global DINT tag in an Allen-Bradley CompactLogix/ControlLogix PLC
var myTag = new TagDint()
{
Name = "SomeDINT",
Gateway = "10.10.10.10",
Path = "1,0",
PlcType = PlcType.ControlLogix,
Protocol = Protocol.ab_eip
};

// Read the value from the PLC and output to console
int output = myTag.Read();
Console.WriteLine($"Original value: {output}");

// Write a new value to the PLC, then read it back, and output to console
myTag.Write(37);
output = myTag.Read();
Console.WriteLine($"Updated value: {output}");
```

See the examples projects for further detail and usage:

* [C# (.NET)](../examples/CSharp%20DotNetCore/)
* [C# (.NET Framework)](../examples/CSharp%20DotNetFramework/)
* [VB.NET](../examples/VB.NET%20DotNetCore/Program.vb)

## Introduction

A tag is a local reference to a region of PLC memory.
Depending on the PLC type and protocol the region may be named.
For some protocols, the region is simply a type and register number (e.g. Modbus).
For other protocols, it is a name, possible array element, field names etc. (e.g. a CIP-based PLC).

Your program directly controls the resources associated with the connection to the PLC.
It initializes these resources through the `Initialize()` function and frees the resources used with the `Dispose()` function.
The lowest level of access to a tag is via the `Read()` and `Write()` operations.
In most cases you must explicitly call these functions to write to the PLC or read from the PLC.
There are also attributes that can be passed when creating a Tag to make it either automatically write to the PLC when the local copy of the tag is updated or read from the PLC periodically, or both.
Once initialized, the tag memory is exposed as a byte array, and can be manipulated using various [data accessors](https://github.com/libplctag/libplctag/wiki/API#tag-data-accessors).

Libplctag does not expose the concept of a PLC.
Just tags.

Read more on the [libplctag wiki](https://github.com/libplctag/libplctag/wiki/API).

## `Tag`

libplctag.NET provides a wrapper for the C API naturalised for .NET.
The `Tag` class is intended to be functionally equivalent to the C API.

For example:

* `Read(..)`[`plc_tag_read(..)`](https://github.com/libplctag/libplctag/wiki/API#reading-a-tag)
* `Write(..)`[`plc_tag_write(..)`](https://github.com/libplctag/libplctag/wiki/API#writing-a-tag)
* `GetInt32(..)`[`plc_tag_get_int32(..)`](https://github.com/libplctag/libplctag/wiki/API#tag-data-accessors)

Some methods are presented slightly differently due to the differences in languages and language idioms.
For example, the counterpart to `Initialize(..)` is [`plc_tag_create(..)`](https://github.com/libplctag/libplctag/wiki/API#creating-a-tag-handle) and the tag attributes are specified as properties (e.g. `Tag.Path`).


## `Tag<M,T>` and Mappers

In your .NET application, you will usually need to convert the raw bytes into a .NET type.
It is possible to use `GetInt32()` and `SetInt32()` (and [others](https://github.com/libplctag/libplctag/wiki/API#tag-data-accessors)) provided by the `Tag` class to perform this conversion, and most of the time, there will only be one sensible way to interpret these bytes for a given tag.

For example, a `DINT` tag defined in a PLC is a 32bit signed integer, and this would be exposed as a little-endian encoded 4-byte array.
The natural choice for a C# type would be `int` - it would be rare to want to work with this data as a `float` or a 4-byte ASCII string for example.

To this end, libplctag.NET offers a typed tag class `Tag<M,T>` that exposes the tag value as a C# type instead of the complete set of Getter/Setter functions.
This class pairs with an [`IPlcMapper`](src/libplctag/DataTypes/IPlcMapper.cs), which encapsulates the mapping between a .NET type (e.g. `int`, `float`) and the PLC type (e.g. `DINT`, `REAL`) by calling the appropriate functions on a Tag (as well as providing other information that libplctag needs for this mapping).

```csharp
class DintPlcMapper : IPlcMapper<int>
{
public PlcType PlcType { get; set; }
public int? ElementSize => 4;
public int[] ArrayDimensions { get; set; }
public int? GetElementCount() => 1;
public int Decode(Tag tag, int offset) => tag.GetInt32(offset);
public void Encode(Tag tag, int offset, int value) => tag.SetInt32(offset, value);
}

var myTag = new Tag<DintPlcMapper, int>(){...configuration...};
myTag.Initialize();
myTag.Value = 1234;
myTag.Write();
```

In general, you will need prior knowedge of the structure of the tag data, and you may need to reverse-engineer it.
An example for reverse engineering a UDT can be found [here](../examples/CSharp%20DotNetCore/SequencePlcMapper.cs).

Because the structure of the data depends on many factors (PLC Make/model, Protocol, and even the tag Name), libplctag.NET does not provide built-in Mappers for all types.
The manuals provided by your device manufacturer are the best source of information on these details.

## Types

### `libplctag` namepsace
* `Tag` - A wrapper around the core libplctag library tag with an interface naturalised to .NET.
* `Tag<M,T>` - A wrapper that exposes a .NET type (generic parameter `T`) instead of Data Accessors. The data access logic is delegated to an `IPlcMapper` (generic parameter `M`).
* `ITag` - an interface that is implemented by `Tag<M,T>`.
* `Libplctag` - A static class used to access some additional features of the libplctag base library such as global debug levels and logging.
* Enum types such as `DebugLevel`.
* Supporting types such as `TagEventArgs`.

All types are shipped with XML documentation, so the full API is discoverable in your IDE.

### `libplctag.DataTypes` namespace

* [`IPlcMapper`](src/libplctag/DataTypes/IPlcMapper.cs)
* [`DintPlcMapper`](src/libplctag/DataTypes/DintPlcMapper.cs)
* [`LrealPlcMapper`](src/libplctag/DataTypes/LrealPlcMapper.cs)
* ... and so on

Of note are [TagInfoPlcMapper](src/libplctag/DataTypes/TagInfoPlcMapper.cs) and [UdtInfoMapper](src/libplctag/DataTypes/UdtInfoPlcMapper.cs), which can be used to [list the tags in a ControlLogix PLC](../examples/CSharp%20DotNetCore/ListUdtDefinitions.cs).

### `libplctag.DataTypes.Simple` namespace

In simple cases such as atomic tags (e.g.`DINT`) or arrays of atomic tags (e.g. `LREAL[x,y]`), we provide classes that pair a built-in `IPlcMapper` with the natural .NET type:

* [`TagDint`](src/libplctag/DataTypes/Simple/Definitions.cs#L21)
* [`TagLreal2D`](src/libplctag/DataTypes/Simple/Definitions.cs#L41)
* ... and so on

## libplctag.NativeImport

The libplctag package depends on the core libplctag libraries which are written in C and are released as native binaries.
The delivery of these files, and the interop to the .NET environment is provided by the [libplctag.NativeImport](https://www.nuget.org/packages/libplctag.NativeImport/) package.

Information on this package can be found [here](libplctag.NativeImport.md).
Loading

0 comments on commit dd99d65

Please sign in to comment.