diff --git a/.gitignore b/.gitignore index dbbb3ef..82981d8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ #ignore builds Builds/ +.vscode + # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index b3a4f5c..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/Examples/bin/Debug/net5.0/Examples.dll", - "args": [], - "cwd": "${workspaceFolder}/Examples", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 2e99677..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/Examples/Examples.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/Examples/Examples.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "${workspaceFolder}/Examples/Examples.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2ebb538 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 WOLF Medizintechnik GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index df175e6..a6dc8c5 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,187 @@ # MewtocolNet -A Mewtocol protocol library to interface with Panasonic PLCs over TCP/Serial. +An easy to use Mewtocol protocol library to interface with Panasonic PLCs over TCP/Serial. + +## Disclaimer +This library is not an official panasonic product nor does panasonic provide financial support or limitations in any form. +This software was written by WOLF Medizintechnik GmbH (@WOmed/dev). + +# Features + +> Features that are not checked still need implementation + +- [x] Read out stats from your PLC +- [x] Read and write registers in real time +- [X] Dynamic register type casting from properties +- [ ] Change run / prog modes +- [ ] Write byte blocks in a whole chain +- [ ] Upload programs to the PLC +- [ ] Download programs from the PLC +- [ ] Reading / writing PLC system registers + +# Support + +## .NET Support + +This library was written in **netstandard2.0** and should by compatible with a lot of .NET environments. + +For a full list of supported .NET clrs see [this page](https://docs.microsoft.com/de-de/dotnet/standard/net-standard?tabs=net-standard-2-0#select-net-standard-version) + +## PLC Support + +> This library was only tested with a few PLCs, other types that support the Panasonic Mewtocol protocol might work. +> Use at your own risk, others might follow with community feedback + +|PLC Type|Supported|Tested| +|--------|---------|------| +FPX C14 |✔ |✔ | +FPX C30 |✔ |✔ | +FPX-H C14|✔ |✔ | +FPX-H C30|✔ |✔ | +FP Sigma |✔ |❌ | + +Where is the RS232/Serial support? + +> Support for the serial protocol will be added soon, feel free to contribute + +# Protocol description + +Panasonic has published a [protocol definition](https://mediap.industry.panasonic.eu/assets/custom-upload/Factory%20&%20Automation/PLC/Manuals/mn_all_plcs_mewtocol_user_pidsx_en.pdf) on their site. +Refer to this site if you want to see the general functionality or add / report missing features. + +> This library is at the time not feature complete, but all essential features are provided # Usage -1. Connecting to a PLC +See [More examples](/Examples) here + +## Connecting to a PLC + +Connecting to a PLC is as simple as ```C# -//Create a new interface class with your PLCs IP address and port -MewtocolInterface interf = new MewtocolInterface("127.0.0.1", 9094); + //attaching a logger +Logger.LogLevel = LogLevel.Verbose; +Logger.OnNewLogMessage((date, msg) => { + Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}"); +}); -//Setup the dataregisters of the PLC you want to read -interf.AddRegister("Test Integer",1204); -interf.AddRegister(1101, 4); +//setting up a new PLC interface +MewtocolInterface interf = new MewtocolInterface("10.237.191.3"); -//attaches an auto reader that polls the registers -interf.WithPoller(); +await interf.ConnectAsync(); +``` -//triggers when a dataregister changes its value -interf.RegisterChanged += (o) => { - Console.WriteLine($"DT{o.MemoryAdress} {(o.Name != null ? $"({o.Name}) " : "")}changed to {o.GetValueString()}"); -}; +You can also use the callbacks of the `ConnectAsync()` method to do something after the initial connection establishment. -//Connects to the PLC asynchronous and invokes connected or failed +```C# await interf.ConnectAsync( - (plcinf) => { - - Console.WriteLine("Connected to PLC:\n" + plcinf.ToString()); - - //read back a register value - var statusNum = (NRegister)interf.Registers[1204]; - Console.WriteLine($"Status num is: {statusNum.Value}"); - + //PLC connected + (plc) => { + if(plcinf.OperationMode.RunMode) + Console.WriteLine("PLC is in RUN"); }, + //Connection failed () => { - Console.WriteLine("Failed connection"); + Console.WriteLine("PLC failed to connect"); } ); ``` +## Reading data registers / contacts + +- Create a new class that inherits from `RegisterCollectionBase` + +```C# +public class TestRegisters : RegisterCollectionBase { + + //corresponds to a R100 boolean register in the PLC + [Register(100, RegisterType.R)] + public bool TestBool1 { get; private set; } + + //corresponds to a XD input of the PLC + [Register(RegisterType.X, SpecialAddress.D)] + public bool TestBoolInputXD { get; private set; } + + //corresponds to a DT7012 - DT7013 as a 32bit time value that gets parsed as a timespan (TIME) + //the smallest value to communicate to the PLC is 10ms + [Register(7012)] + public TimeSpan TestTime { get; private set; } + + //corresponds to a DT1101 - DT1104 string register in the PLC with (STRING[4]) + [Register(1101, 4)] + public string TestString1 { get; private set; } + +} +``` + +- Connect to the PLC and attach the register collection and logger +- attach an automatic poller by chaining `.WithPoller()` after the register attachment + +```C# +//attaching a logger +Logger.LogLevel = LogLevel.Verbose; +Logger.OnNewLogMessage((date, msg) => { + Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}"); +}); + +//setting up a new PLC interface and register collection +MewtocolInterface interf = new MewtocolInterface("192.168.115.3"); +TestRegisters registers = new TestRegisters(); + +//attaching the register collection and an automatic poller +interf.WithRegisterCollection(registers).WithPoller(); + +await interf.ConnectAsync( + (plcinf) => { + //reading a value from the register collection + Console.WriteLine($"Time Value is: {registers.TestTime}"); + } +); +``` +- Your properties are getting automatically updated after the initial connection + +> Note! this is not your only option to read registers, see here + +## Writing data registers / contacts + +⚠ **Never set a register by setting the property, always use one of the provided methods** + +### Synchronous + +Sets the register without feedback if it was set + +```C# +//inverts the boolean register +interf.SetRegister(nameof(registers.TestBool1), !registers.TestBool1); + +//set the current second to the PLCs TIME register +interf.SetRegister(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Second)); + + //writes 'Test' to the PLCs string register +interf.SetRegister(nameof(registers.TestString1), "Test"); +``` + +You can also set a register by calling its name directly (Must be either in an attached register collection or added to the list manually) + +Adding registers to a manual list +```C# +interf.AddRegister(105, _name: "ManualBoolRegister"); +``` + +Reading the value of the manually added register +```C# +//get the value as a string +string value = interf.GetRegister("ManualBoolRegister").GetValueString(); +//get the value by casting +bool value2 = interf.GetRegister("ManualBoolRegister").Value; +//for double casted ones like numbers +var value2 = interf.GetRegister>("NumberRegister").Value; +``` + +### Asynchronous + +Sets the register waiting for the PLC to confirm it was set + +```C# +//inverts the boolean register +interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1); +```