54 Commits

Author SHA1 Message Date
Felix Weiß
7bd07bb520 Fixed env.VERSION for sed step 2023-06-16 12:10:57 +02:00
Felix Weiß
f99af9511b Fix wrong ver 2023-06-16 12:03:44 +02:00
Felix Weiß
b13b2d0199 Fixed sed escape 2023-06-16 11:49:17 +02:00
Felix Weiß
364e0637bb fixed versioning auto release 2023-06-16 11:42:04 +02:00
Felix Weiß
bba90106cb Made test-pipeline.yml callable 2023-06-16 11:22:48 +02:00
Felix Weiß
3dea18d285 Speperated test and publish pipeline 2023-06-16 11:18:08 +02:00
Sandoun
ad61361008 Use setup dotnet v2 2023-06-16 10:39:41 +02:00
Sandoun
0e0659000a Fixed ci no write permission 2023-06-16 10:34:57 +02:00
Sandoun
8cc47b496a Fixed new publish pipeline 2023-06-16 10:30:14 +02:00
Sandoun
9eb09fc7ec Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-06-16 10:24:25 +02:00
Sandoun
9b8fca6561 Changed package publisher to gh 2023-06-16 10:24:19 +02:00
Felix Weiß
6205f81931 Update README.md 2023-06-16 10:05:30 +02:00
Sandoun
a8960f12c8 Changed to opencover test format 2023-06-16 10:00:15 +02:00
Felix Weiß
0b670b1a27 Update build-pipeline.yml 2023-06-16 09:48:15 +02:00
Felix Weiß
63a1348704 Update README.md 2023-06-15 21:03:31 +02:00
Felix Weiß
61fe2aff65 Update build-pipeline.yml 2023-06-15 21:02:55 +02:00
Felix Weiß
314ce9c053 Merge pull request #5 from WOmed/PropAutoSetters
Prop auto setters
2023-06-15 20:51:34 +02:00
Sandoun
7864915967 Added auto prop sending
- updated readme
2023-06-15 20:49:14 +02:00
Sandoun
62f4a48bf9 Housekeeping 2023-06-15 20:23:48 +02:00
Sandoun
a78a405c25 update readme 2023-06-15 20:08:20 +02:00
Felix Weiß
09f4da54a9 Made registers use the IRegister interface
- cleanup and refactoring
- fully implemented auto prop register generator unit tests #4
- added plc test program c30 fpx-h
- fixed bitarray setback
- cleaned up examples and added new ones with addition of attributes for later additions
2023-06-15 20:04:38 +02:00
Felix Weiss
6ca8e9de96 Added mewtocol logger to unit test output 2023-06-13 23:11:56 +02:00
Felix Weiss
53a0856634 Change test result step name 2023-06-13 22:58:50 +02:00
Felix Weiß
2b173aeb95 Update build-pipeline.yml 2023-06-13 22:44:49 +02:00
Felix Weiß
2680bbb07b Update build-pipeline.yml 2023-06-13 22:41:46 +02:00
Felix Weiß
9fd178424f Update build-pipeline.yml 2023-06-13 22:35:38 +02:00
Felix Weiß
e96496cff8 Update README.md 2023-06-13 13:52:40 +02:00
Felix Weiß
39ff1a5c5b Made code ql only callable 2023-06-13 13:02:21 +02:00
Felix Weiß
6c50324696 Code ql test 2023-06-13 12:58:37 +02:00
Felix Weiß
affa2ea83f Merged CI into one workflow file 2023-06-13 12:49:33 +02:00
Felix Weiß
af101812a8 Reordered CI 2023-06-13 12:35:34 +02:00
Felix Weiß
35f2786129 Added test CI on push 2023-06-13 12:30:08 +02:00
Felix Weiß
062eb75876 CI use only net6 2023-06-13 12:09:29 +02:00
Felix Weiß
c771e747eb CI only .net 5 and 6 2023-06-13 12:04:30 +02:00
Felix Weiß
dc24b33297 CI fix 2023-06-13 12:01:28 +02:00
Felix Weiß
68aa2ad12a Unit test CI 2023-06-13 11:56:09 +02:00
Felix Weiß
bb1cc8c011 Merge pull request #3 from WOmed/dev
Added new unit tests
2023-06-13 11:45:38 +02:00
Felix Weiß
24525521af Added new unit tests 2023-06-13 11:42:49 +02:00
Felix Weiß
435624f8a2 Update test-on-hardware.yml 2023-06-13 10:41:11 +02:00
Felix Weiß
7c3f279576 Create test-on-hardware.yml 2023-06-13 10:36:33 +02:00
Felix Weiß
6001402991 Merge branch 'master' of https://github.com/WOmed/MewtocolNet 2023-02-27 16:01:54 +01:00
Felix Weiß
fb2bd8d56d Added test structure 2023-02-27 16:01:47 +01:00
Felix Weiß
a71d545bf3 Create codeql.yml 2023-02-24 10:11:51 +01:00
Felix Weiß
43c7f72ac4 Added auto register reset to default typed value on disconnect 2022-10-21 11:56:58 +02:00
Felix Weiß
2e35ed87af Updated hyperlinks in readme 2022-10-20 10:16:11 +02:00
Felix Weiß
a7f97a72ea Package id change in readme 2022-10-20 10:11:13 +02:00
Felix Weiß
51870166e4 Update publish.yml 2022-10-20 10:04:49 +02:00
Felix Weiß
b43e9bd201 Package id 2022-10-20 10:02:59 +02:00
Felix Weiß
e313dbc3ec Changed package ID 2022-10-20 10:01:53 +02:00
Felix Weiß
bdf9f93f97 Update MewtocolNet.csproj 2022-10-20 09:50:58 +02:00
Felix Weiß
fe816ab78e Update publish.yml 2022-10-20 09:46:08 +02:00
Felix Weiß
48a5977185 Create publish.yml 2022-10-20 09:36:35 +02:00
Felix Weiß
c69f63c191 Update README.md 2022-09-27 10:21:37 +02:00
Felix Weiß
6a2f278dd1 Fixed negative queued msges
- version count up
2022-09-27 10:18:01 +02:00
30 changed files with 1945 additions and 642 deletions

70
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
workflow_call:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@@ -1,23 +0,0 @@
name: .NET Windows
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
dotnet-version: [ '3.0', '3.1.x', '5.0.x' ]
steps:
- uses: actions/checkout@v3
- name: Setup dotnet ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
# You can test your matrix by printing the current dotnet version
- name: Display dotnet version
run: dotnet --version

63
.github/workflows/publish-pipeline.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Publish pipeline
on:
workflow_dispatch:
release:
types: [published]
permissions: write-all
jobs:
test-pipeline:
name: 'Invoke the test pipeline'
uses: ./.github/workflows/test-pipeline.yml
secrets: inherit
#Deploy package
publish-package:
name: 'Build and publish package'
needs: test-pipeline
runs-on: [self-hosted, linux, x64, womed-local-linux]
steps:
- uses: actions/checkout@v3
- name: Parse version tag
run: |
VERSION=${{ github.ref_name }}
echo "VERSION=${VERSION:1}" >> $GITHUB_ENV
- name: Set .csproj version to ${{ env.VERSION }}
run: |
sed -i 's/<Version>[0-9].[0-9].[0-9]<\/Version>/<Version>${{ env.VERSION }}<\/Version>/g' MewtocolNet/MewtocolNet.csproj
less MewtocolNet/MewtocolNet.csproj
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build as ${{ env.VERSION }}
run: dotnet build "MewtocolNet" --no-incremental
- name: Pack as ${{ env.VERSION }}
run: dotnet pack "MewtocolNet"
- name: Publish as ${{ env.VERSION }}
run: |
cd '${{ github.workspace }}/Builds'
ls -l
dotnet nuget push "*.nupkg" --skip-duplicate --api-key ${{ secrets.GITHUB_TOKEN }} --source "https://nuget.pkg.github.com/WOmed/index.json"
- name: 'Upload artifacts to latest release'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: "${{ github.event.release.upload_url }}"
asset_path: ${{ github.workspace }}/Builds/Mewtocol.NET.${{ env.VERSION }}.nupkg
asset_name: Mewtocol.NET.${{ env.VERSION }}.nupkg
asset_content_type: application/zip

50
.github/workflows/test-pipeline.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Test pipeline
on:
workflow_dispatch:
workflow_call:
push:
branches:
- main
- master
paths-ignore:
- '**.md'
permissions: write-all
jobs:
#Check the online status of the test PLCs first
check-plcs-online:
name: 'Test PLC online status'
runs-on: [self-hosted, linux, x64, womed-local-linux]
steps:
- name: 'Ping FPX-H-C30T'
run: ping 192.168.115.210 -w 5
- name: 'Ping FPX-H-C14R'
run: ping 192.168.115.212 -w 5
- name: 'Ping FPX-C30T'
run: ping 192.168.115.213 -w 5
#Run unit tests on the test PLCs
run-unit-tests:
name: 'Run unit tests'
needs: check-plcs-online
runs-on: [self-hosted, linux, x64, womed-local-linux]
steps:
- uses: actions/checkout@v3
- name: 'Setup dotnet'
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'
- name: 'Run tests'
run: |
cd '${{ github.workspace }}/MewtocolTests'
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
#Upload to codecov
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -0,0 +1,183 @@
using MewtocolNet.Logging;
using MewtocolNet;
using System;
using System.Reflection;
using System.Threading.Tasks;
using System.Collections;
namespace Examples;
public class ExampleScenarios {
public static bool MewtocolLoggerEnabled = false;
public void SetupLogger () {
//attaching the logger
Logger.LogLevel = LogLevel.Verbose;
Logger.OnNewLogMessage((date, msg) => {
if(MewtocolLoggerEnabled)
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}");
});
}
[Scenario("Permament connection with poller")]
public async Task RunCyclicPollerAsync () {
Console.WriteLine("Starting poller scenario");
int runTime = 10000;
int remainingTime = runTime;
//setting up a new PLC interface and register collection
MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
TestRegisters registers = new TestRegisters();
//attaching the register collection and an automatic poller
interf.WithRegisterCollection(registers).WithPoller();
await interf.ConnectAsync();
_ = Task.Factory.StartNew(async () => {
while (interf.IsConnected) {
//flip the bool register each tick and wait for it to be registered
await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1);
Console.Title = $"Polling Paused: {interf.PollingPaused}, " +
$"Poller active: {interf.PollerActive}, " +
$"Speed UP: {interf.BytesPerSecondUpstream} B/s, " +
$"Speed DOWN: {interf.BytesPerSecondDownstream} B/s, " +
$"Poll delay: {interf.PollerDelayMs} ms, " +
$"Queued MSGs: {interf.QueuedMessages}";
Console.Clear();
Console.WriteLine("Underlying registers on tick: \n");
foreach (var register in interf.Registers) {
Console.WriteLine($"{register.GetCombinedName()} / {register.GetRegisterPLCName()} - Value: {register.GetValueString()}");
}
Console.WriteLine($"{registers.TestBool1}");
Console.WriteLine($"{registers.TestDuplicate}");
remainingTime -= 1000;
Console.WriteLine($"\nStopping in: {remainingTime}ms");
await Task.Delay(1000);
}
});
await Task.Delay(runTime);
interf.Disconnect();
}
[Scenario("Dispose and disconnect connection")]
public async Task RunDisposalAndDisconnectAsync () {
//automatic disposal
using (var interf = new MewtocolInterface("192.168.115.210")) {
await interf.ConnectAsync();
if (interf.IsConnected) {
Console.WriteLine("Opened connection");
await Task.Delay(5000);
}
}
Console.WriteLine("Disposed, closed connection");
//manual close
var interf2 = new MewtocolInterface("192.168.115.210");
await interf2.ConnectAsync();
if (interf2.IsConnected) {
Console.WriteLine("Opened connection");
await Task.Delay(5000);
}
interf2.Disconnect();
Console.WriteLine("Disconnected, closed connection");
}
[Scenario("Test auto enums and bitwise, needs the example program from MewtocolNet/PLC_Test")]
public async Task RunEnumsBitwiseAsync () {
Console.WriteLine("Starting auto enums and bitwise");
//setting up a new PLC interface and register collection
MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
TestRegistersEnumBitwise registers = new TestRegistersEnumBitwise();
//attaching the register collection and an automatic poller
interf.WithRegisterCollection(registers).WithPoller();
registers.PropertyChanged += (s, e) => {
Console.Clear();
var props = registers.GetType().GetProperties();
foreach (var prop in props) {
var val = prop.GetValue(registers);
string printVal = val?.ToString() ?? "null";
if (val is BitArray bitarr) {
printVal = bitarr.ToBitString();
}
Console.Write($"{prop.Name} - ");
if(printVal == "True") {
Console.ForegroundColor = ConsoleColor.Green;
}
Console.Write($"{printVal}");
Console.ResetColor();
Console.WriteLine();
}
};
await interf.ConnectAsync();
//use the async method to make sure the cycling is stopped
await interf.SetRegisterAsync(nameof(registers.StartCyclePLC), false);
await Task.Delay(5000);
//set the register without waiting for it async
registers.StartCyclePLC = true;
await Task.Delay(5000);
registers.StartCyclePLC = false;
await Task.Delay(2000);
}
}

View File

@@ -1,206 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using MewtocolNet;
using MewtocolNet.Logging;
using MewtocolNet.Registers;
namespace Examples;
class Program {
static ExampleScenarios ExampleSzenarios = new ExampleScenarios();
static void Main(string[] args) {
Console.WriteLine("Enter your scenario number:\n" +
"1 = Permanent connection\n" +
"2 = Dispose connection");
AppDomain.CurrentDomain.UnhandledException += (s,e) => {
Console.WriteLine(e.ExceptionObject.ToString());
};
TaskScheduler.UnobservedTaskException += (s,e) => {
Console.WriteLine(e.Exception.ToString());
};
ExampleSzenarios.SetupLogger();
LoopInput();
}
private static void LoopInput () {
Console.WriteLine("All available scenarios\n");
var methods = ExampleSzenarios.GetType().GetMethods();
var invokeableMethods = new List<MethodInfo>();
for (int i = 0, j = 0; i < methods.Length; i++) {
MethodInfo method = methods[i];
var foundAtt = method.GetCustomAttribute(typeof(ScenarioAttribute));
if(foundAtt != null && foundAtt is ScenarioAttribute att) {
Console.WriteLine($"[{j + 1}] {method.Name}() - {att.Description}");
invokeableMethods.Add(method);
j++;
}
}
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("\nEnter a number to excecute a example");
Console.ResetColor();
Console.WriteLine("\nOther possible commands: \n\n" +
"'toggle logger' - toggle the built in mewtocol logger on/off\n" +
"'exit' - to close this program \n" +
"'clear' - to clear the output \n");
Console.Write("> ");
var line = Console.ReadLine();
if(line == "1") {
Scenario1();
}
if (line == "toggle logger") {
if (line == "2") {
Scenario2();
}
ExampleScenarios.MewtocolLoggerEnabled = !ExampleScenarios.MewtocolLoggerEnabled;
Console.ReadLine();
}
Console.WriteLine(ExampleScenarios.MewtocolLoggerEnabled ? "Logger enabled" : "Logger disabled");
private static bool isProgressReadout = false;
} else if (line == "exit") {
static void Scenario1 () {
Environment.Exit(0);
Task.Factory.StartNew(async () => {
} else if (line == "clear") {
//attaching the logger
Logger.LogLevel = LogLevel.Critical;
Logger.OnNewLogMessage((date, msg) => {
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}");
});
Console.Clear();
//setting up a new PLC interface and register collection
MewtocolInterface interf = new MewtocolInterface("10.237.191.3");
TestRegisters registers = new TestRegisters();
} else if (int.TryParse(line, out var lineNum)) {
//attaching the register collection and an automatic poller
interf.WithRegisterCollection(registers).WithPoller();
var index = Math.Clamp(lineNum - 1, 0, invokeableMethods.Count - 1);
_ = Task.Factory.StartNew(async () => {
while (true) {
if (isProgressReadout) continue;
Console.Title = $"Polling Paused: {interf.PollingPaused}, " +
$"Speed UP: {interf.BytesPerSecondUpstream} B/s, " +
$"Speed DOWN: {interf.BytesPerSecondDownstream} B/s, " +
$"Poll delay: {interf.PollerDelayMs} ms, " +
$"Queued MSGs: {interf.QueuedMessages}";
await Task.Delay(1000);
}
});
var task = (Task)invokeableMethods.ElementAt(index).Invoke(ExampleSzenarios, null);
await interf.ConnectAsync((plcinf) => AfterConnect(interf, registers));
task.Wait();
});
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("The program ran to completition");
Console.ResetColor();
}
static void AfterConnect (MewtocolInterface interf, TestRegisters registers) {
//reading a value from the register collection
Console.WriteLine($"BitValue is: {registers.BitValue}");
Console.WriteLine($"TestEnum is: {registers.TestEnum}");
_ = Task.Factory.StartNew(async () => {
while(true) {
isProgressReadout = true;
await interf.ReadByteRange(1000, 2000, (p) => {
var totSteps = 10;
var cSteps = totSteps * p;
string progBar = "";
for (int i = 0; i < totSteps; i++) {
if(i < (int)cSteps) {
progBar += "⬛";
} else {
progBar += "⬜";
}
Console.WriteLine("Wrong input");
}
Console.Title = $"Prog read range: {(p * 100).ToString("N1")}% {progBar} Queued MSGs: {interf.QueuedMessages}";
});
isProgressReadout = false;
await Task.Delay(3000);
}
});
//writing a value to the registers
_ = Task.Factory.StartNew(async () => {
//set plc to run mode if not already
await interf.SetOperationMode(OPMode.Run);
int startAdress = 10000;
int entryByteSize = 20 * 20;
var bytes = await interf.ReadByteRange(startAdress, entryByteSize);
Console.WriteLine($"Bytes: {string.Join('-', bytes)}");
await Task.Delay(2000);
await interf.SetRegisterAsync(nameof(registers.TestInt32), 100);
//adds 10 each time the plc connects to the PLCs INT regíster
interf.SetRegister(nameof(registers.TestInt16), (short)(registers.TestInt16 + 10));
//adds 1 each time the plc connects to the PLCs DINT regíster
interf.SetRegister(nameof(registers.TestInt32), (registers.TestInt32 + 1));
//adds 11.11 each time the plc connects to the PLCs REAL regíster
interf.SetRegister(nameof(registers.TestFloat32), (float)(registers.TestFloat32 + 11.11));
//writes 'Hello' to the PLCs string register
interf.SetRegister(nameof(registers.TestString2), "Hello");
//set the current second to the PLCs TIME register
interf.SetRegister(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Second));
//test pausing poller
bool pollerPaused = false;
while (true) {
await Task.Delay(5000);
pollerPaused = !pollerPaused;
if (pollerPaused) {
Console.WriteLine("Pausing poller");
await interf.PausePollingAsync();
//interf.PollerDelayMs += 10;
Console.WriteLine("Paused poller");
} else {
interf.ResumePolling();
Console.WriteLine("Resumed poller");
}
}
});
}
static void Scenario2 () {
Logger.LogLevel = LogLevel.Critical;
Logger.OnNewLogMessage((date, msg) => {
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}");
});
Task.Factory.StartNew(async () => {
//automatic endpoint
using (var interf = new MewtocolInterface("10.237.191.3")) {
await interf.ConnectAsync();
if (interf.IsConnected) {
await Task.Delay(5000);
}
interf.Disconnect();
}
//manual endpoint
using (var interf = new MewtocolInterface("10.237.191.3")) {
interf.HostEndpoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("10.237.191.77"), 0);
await interf.ConnectAsync();
if(interf.IsConnected) {
await Task.Delay(5000);
}
interf.Disconnect();
}
});
LoopInput();
}

View File

@@ -0,0 +1,15 @@
using System;
namespace Examples;
public class ScenarioAttribute : Attribute {
public string Description { get; private set; }
public ScenarioAttribute(string description) {
Description = description;
}
}

View File

@@ -10,8 +10,13 @@ namespace Examples {
[Register(1000, RegisterType.R)]
public bool TestBool1 { get; private set; }
private int testDuplicate;
[Register(1000)]
public int TestDuplicate { get; private set; }
public int TestDuplicate {
get => testDuplicate;
set => AutoSetter(value, ref testDuplicate);
}
//corresponds to a XD input of the PLC
[Register(RegisterType.X, SpecialAddress.D)]

View File

@@ -0,0 +1,106 @@
using MewtocolNet;
using MewtocolNet.RegisterAttributes;
using System;
using System.Collections;
namespace Examples {
public class TestRegistersEnumBitwise : RegisterCollectionBase {
private bool startCyclePLC;
[Register(50, RegisterType.R)]
public bool StartCyclePLC {
get => startCyclePLC;
set => AutoSetter(value, ref startCyclePLC);
}
//the enum you want to read out
public enum CurrentState {
Undefined = 0,
State1 = 1,
State2 = 2,
//If you leave an enum empty it still works
//State3 = 3,
State4 = 4,
State5 = 5,
State6 = 6,
State7 = 7,
State8 = 8,
State9 = 9,
State10 = 10,
State11 = 11,
State12 = 12,
State13 = 13,
State14 = 14,
State15 = 15,
}
//automatically convert the short (PLC int) register to an enum
[Register(500)]
public CurrentState TestEnum16 { get; private set; }
//also works for 32bit registers
[Register(501, BitCount.B32)]
public CurrentState TestEnum32 { get; private set; }
//get the whole bit array from DT503
[Register(503)]
public BitArray TestBitRegister16 { get; private set; }
//you can also extract single bits from DT503
[Register(503, 0, BitCount.B16)]
public bool BitValue0 { get; private set; }
[Register(503, 1, BitCount.B16)]
public bool BitValue1 { get; private set; }
[Register(503, 2, BitCount.B16)]
public bool BitValue2 { get; private set; }
[Register(503, 3, BitCount.B16)]
public bool BitValue3 { get; private set; }
[Register(503, 4, BitCount.B16)]
public bool BitValue4 { get; private set; }
[Register(503, 5, BitCount.B16)]
public bool BitValue5 { get; private set; }
[Register(503, 6, BitCount.B16)]
public bool BitValue6 { get; private set; }
[Register(503, 7, BitCount.B16)]
public bool BitValue7 { get; private set; }
[Register(503, 8, BitCount.B16)]
public bool BitValue8 { get; private set; }
[Register(503, 9, BitCount.B16)]
public bool BitValue9 { get; private set; }
[Register(503, 10, BitCount.B16)]
public bool BitValue10 { get; private set; }
[Register(503, 11, BitCount.B16)]
public bool BitValue11 { get; private set; }
[Register(503, 12, BitCount.B16)]
public bool BitValue12 { get; private set; }
[Register(503, 13, BitCount.B16)]
public bool BitValue13 { get; private set; }
[Register(503, 14, BitCount.B16)]
public bool BitValue14 { get; private set; }
[Register(503, 15, BitCount.B16)]
public bool BitValue15 { get; private set; }
}
}

View File

@@ -1,11 +1,13 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
# Visual Studio Version 17
VisualStudioVersion = 17.5.33103.201
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MewtocolNet", "MewtocolNet\MewtocolNet.csproj", "{8B7863E7-5E82-4990-9138-2C0C24629982}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewtocolNet", "MewtocolNet\MewtocolNet.csproj", "{8B7863E7-5E82-4990-9138-2C0C24629982}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples", "Examples\Examples.csproj", "{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples", "Examples\Examples.csproj", "{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MewtocolTests", "MewtocolTests\MewtocolTests.csproj", "{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -16,9 +18,6 @@ Global
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -44,5 +43,20 @@ Global
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x64.Build.0 = Release|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x86.ActiveCfg = Release|Any CPU
{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x86.Build.0 = Release|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|x64.ActiveCfg = Debug|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|x64.Build.0 = Debug|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|x86.ActiveCfg = Debug|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Debug|x86.Build.0 = Debug|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|Any CPU.Build.0 = Release|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|x64.ActiveCfg = Release|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|x64.Build.0 = Release|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|x86.ActiveCfg = Release|Any CPU
{C1BF3AB0-CDFE-4070-A759-C3B25A20ABE1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MewtocolNet.Logging;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
namespace MewtocolNet {
@@ -19,11 +21,17 @@ namespace MewtocolNet {
/// </summary>
public bool PollingPaused => pollerIsPaused;
/// <summary>
/// True if the poller is actvice (can be paused)
/// </summary>
public bool PollerActive => !pollerTaskStopped;
internal event Action PolledCycle;
internal volatile bool pollerTaskRunning;
internal volatile bool pollerTaskStopped;
internal volatile bool pollerIsPaused;
internal volatile bool pollerFirstCycle = false;
internal bool usePoller = false;
@@ -37,6 +45,8 @@ namespace MewtocolNet {
pollerTaskRunning = false;
pollerTaskStopped = true;
ClearRegisterVals();
}
/// <summary>
@@ -80,6 +90,8 @@ namespace MewtocolNet {
if (pollerTaskRunning)
return;
pollerFirstCycle = true;
Task.Factory.StartNew(async () => {
Logger.Log("Poller is attaching", LogLevel.Info, this);
@@ -167,6 +179,7 @@ namespace MewtocolNet {
}
iteration++;
pollerFirstCycle = false;
await Task.Delay(pollerDelayMs);
@@ -182,6 +195,12 @@ namespace MewtocolNet {
}
internal void PropertyRegisterWasSet (string propName, object value) {
SetRegister(propName, value);
}
#endregion
#region Register Adding
@@ -203,7 +222,7 @@ namespace MewtocolNet {
/// <param name="_name">A naming definition for QOL, doesn't effect PLC and is optional</param>
public void AddRegister (int _address, RegisterType _type, string _name = null) {
Register toAdd = null;
IRegister toAdd = null;
//as number registers
if (_type == RegisterType.DT_short) {
@@ -232,30 +251,29 @@ namespace MewtocolNet {
internal void AddRegister (Type _colType, int _address, RegisterType _type, string _name = null) {
Register toAdd = null;
IRegister toAdd = null;
//as number registers
if (_type == RegisterType.DT_short) {
toAdd = new NRegister<short>(_address, _name);
toAdd = new NRegister<short>(_address, _name).WithCollectionType(_colType);
}
if (_type == RegisterType.DT_ushort) {
toAdd = new NRegister<ushort>(_address, _name);
toAdd = new NRegister<ushort>(_address, _name).WithCollectionType(_colType);
}
if (_type == RegisterType.DDT_int) {
toAdd = new NRegister<int>(_address, _name);
toAdd = new NRegister<int>(_address, _name).WithCollectionType(_colType);
}
if (_type == RegisterType.DDT_uint) {
toAdd = new NRegister<uint>(_address, _name);
toAdd = new NRegister<uint>(_address, _name).WithCollectionType(_colType);
}
if (_type == RegisterType.DDT_float) {
toAdd = new NRegister<float>(_address, _name);
toAdd = new NRegister<float>(_address, _name).WithCollectionType(_colType);
}
if (toAdd == null) {
toAdd = new BRegister(_address, _type, _name);
toAdd = new BRegister(_address, _type, _name).WithCollectionType(_colType);
}
toAdd.collectionType = _colType;
Registers.Add(toAdd);
}
@@ -317,7 +335,7 @@ namespace MewtocolNet {
throw new NotSupportedException($"_lenght parameter only allowed for register of type string");
}
Register toAdd;
IRegister toAdd;
if (regType == typeof(short)) {
toAdd = new NRegister<short>(_address, _name);
@@ -348,7 +366,8 @@ namespace MewtocolNet {
}
internal void AddRegister<T> (Type _colType, int _address, int _length = 1, string _name = null, bool _isBitwise = false, Type _enumType = null) {
//Internal register adding for auto register collection building
internal void AddRegister<T> (Type _colType, int _address, PropertyInfo boundProp, int _length = 1, bool _isBitwise = false, Type _enumType = null) {
Type regType = typeof(T);
@@ -356,44 +375,50 @@ namespace MewtocolNet {
throw new NotSupportedException($"_lenght parameter only allowed for register of type string");
}
if (Registers.Any(x => x.MemoryAdress == _address) && _isBitwise) {
if (Registers.Any(x => x.MemoryAddress == _address) && _isBitwise) {
return;
}
Register reg = null;
IRegister reg = null;
string propName = boundProp.Name;
//rename the property name to prevent duplicate names in case of a bitwise prop
if(_isBitwise && regType == typeof(short))
propName = $"Auto_Bitwise_DT{_address}";
if (_isBitwise && regType == typeof(int))
propName = $"Auto_Bitwise_DDT{_address}";
if (regType == typeof(short)) {
reg = new NRegister<short>(_address, _name, _isBitwise);
reg = new NRegister<short>(_address, propName, _isBitwise, _enumType).WithCollectionType(_colType);
} else if (regType == typeof(ushort)) {
reg = new NRegister<ushort>(_address, _name);
reg = new NRegister<ushort>(_address, propName).WithCollectionType(_colType);
} else if (regType == typeof(int)) {
reg = new NRegister<int>(_address, _name, _isBitwise, _enumType);
reg = new NRegister<int>(_address, propName, _isBitwise, _enumType).WithCollectionType(_colType);
} else if (regType == typeof(uint)) {
reg = new NRegister<uint>(_address, _name);
reg = new NRegister<uint>(_address, propName).WithCollectionType(_colType);
} else if (regType == typeof(float)) {
reg = new NRegister<float>(_address, _name);
reg = new NRegister<float>(_address, propName).WithCollectionType(_colType);
} else if (regType == typeof(string)) {
reg = new SRegister(_address, _length, _name);
reg = new SRegister(_address, _length, propName).WithCollectionType(_colType);
} else if (regType == typeof(TimeSpan)) {
reg = new NRegister<TimeSpan>(_address, _name);
reg = new NRegister<TimeSpan>(_address, propName).WithCollectionType(_colType);
} else if (regType == typeof(bool)) {
reg = new BRegister(_address, RegisterType.R, _name);
reg = new BRegister(_address, RegisterType.R, propName).WithCollectionType(_colType);
}
if (reg == null) {
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
$"Allowed are: short, ushort, int, uint, float and string");
} else {
}
if (Registers.Any(x => x.GetRegisterPLCName() == reg.GetRegisterPLCName()) && !_isBitwise) {
throw new NotSupportedException($"Cannot add a register multiple times, " +
$"make sure that all register attributes or AddRegister assignments have different adresses.");
}
reg.collectionType = _colType;
Registers.Add(reg);
}
}
@@ -405,7 +430,7 @@ namespace MewtocolNet {
/// Gets a register that was added by its name
/// </summary>
/// <returns></returns>
public Register GetRegister (string name) {
public IRegister GetRegister (string name) {
return Registers.FirstOrDefault(x => x.Name == name);
@@ -416,13 +441,18 @@ namespace MewtocolNet {
/// </summary>
/// <typeparam name="T">The type of register</typeparam>
/// <returns>A casted register or the <code>default</code> value</returns>
public T GetRegister<T> (string name) where T : Register {
public T GetRegister<T> (string name) where T : IRegister {
try {
var reg = Registers.FirstOrDefault(x => x.Name == name);
return reg as T;
return (T)reg;
} catch (InvalidCastException) {
return default(T);
}
}
#endregion
@@ -432,7 +462,7 @@ namespace MewtocolNet {
/// <summary>
/// Gets a list of all added registers
/// </summary>
public List<Register> GetAllRegisters () {
public List<IRegister> GetAllRegisters () {
return Registers;
@@ -442,7 +472,7 @@ namespace MewtocolNet {
#region Event Invoking
internal void InvokeRegisterChanged (Register reg) {
internal void InvokeRegisterChanged (IRegister reg) {
RegisterChanged?.Invoke(reg);

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet {
/// <summary>
/// An interface for all register types
/// </summary>
public interface IRegister {
/// <summary>
/// Gets called whenever the value was changed
/// </summary>
event Action<object> ValueChanged;
/// <summary>
/// The name of the register
/// </summary>
string Name { get; }
/// <summary>
/// The current value of the register
/// </summary>
object Value { get; }
/// <summary>
/// The plc memory address of the register
/// </summary>
int MemoryAddress { get; }
/// <summary>
/// Indicates if the register is processed bitwise
/// </summary>
/// <returns>True if bitwise</returns>
bool IsUsedBitwise();
/// <summary>
/// Generates a string describing the starting memory address of the register
/// </summary>
string GetStartingMemoryArea();
/// <summary>
/// Gets the current value formatted as a readable string
/// </summary>
string GetValueString();
/// <summary>
/// Builds the identifier for the mewtocol query string
/// </summary>
/// <returns></returns>
string BuildMewtocolQuery();
/// <summary>
/// Builds a register string that prepends the memory address fe. DDT or DT, X, Y etc
/// </summary>
string GetRegisterString();
/// <summary>
/// Builds a combined name for the attached property to uniquely identify the property register binding
/// </summary>
/// <returns></returns>
string GetCombinedName();
/// <summary>
/// Gets the name of the class that contains the attached property
/// </summary>
/// <returns></returns>
string GetContainerName();
/// <summary>
/// Builds a register name after the PLC convention <br/>
/// Example <code>DDT100, XA, X6, Y1, DT3300</code>
/// </summary>
string GetRegisterPLCName();
/// <summary>
/// Clears the current value of the register and resets it to default
/// </summary>
void ClearValue();
/// <summary>
/// Triggers a notifychanged update event
/// </summary>
void TriggerNotifyChange();
/// <summary>
/// Gets the type of the class collection its attached property is in or null
/// </summary>
/// <returns>The class name or null if manually added</returns>
Type GetCollectionType();
/// <summary>
/// Builds a readable string with all important register informations
/// </summary>
string ToString();
}
}

View File

@@ -197,6 +197,39 @@ namespace MewtocolNet {
}
internal static bool IsDoubleNumericRegisterType (this Type type) {
//Type[] singles = new Type[] {
// typeof(short),
// typeof(ushort),
//};
Type[] doubles = new Type[] {
typeof(int),
typeof(uint),
typeof(float),
typeof(TimeSpan),
};
return doubles.Contains(type);
}
internal static bool IsNumericSupportedType (this Type type) {
Type[] supported = new Type[] {
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(float),
typeof(TimeSpan),
};
return supported.Contains(type);
}
}
}

View File

@@ -15,8 +15,11 @@ using System.ComponentModel;
using System.Net;
using System.Threading;
using MewtocolNet.Queue;
using System.Reflection;
using System.Timers;
namespace MewtocolNet {
namespace MewtocolNet
{
/// <summary>
/// The PLC com interface class
@@ -36,7 +39,7 @@ namespace MewtocolNet {
/// <summary>
/// Gets triggered when a registered data register changes its value
/// </summary>
public event Action<Register> RegisterChanged;
public event Action<IRegister> RegisterChanged;
/// <summary>
/// Gets triggered when a property of the interface changes
@@ -114,7 +117,7 @@ namespace MewtocolNet {
/// <summary>
/// The registered data registers of the PLC
/// </summary>
public List<Register> Registers { get; set; } = new List<Register>();
public List<IRegister> Registers { get; set; } = new List<IRegister>();
private string ip;
private int port;
@@ -209,7 +212,7 @@ namespace MewtocolNet {
RegisterChanged += (o) => {
string address = $"{o.GetRegisterString()}{o.MemoryAdress}".PadRight(5, (char)32);
string address = $"{o.GetRegisterString()}{o.GetStartingMemoryArea()}".PadRight(5, (char)32);
Logger.Log($"{address} " +
$"{(o.Name != null ? $"({o.Name}) " : "")}" +
@@ -395,6 +398,17 @@ namespace MewtocolNet {
}
private void ClearRegisterVals () {
for (int i = 0; i < Registers.Count; i++) {
var reg = Registers[i];
reg.ClearValue();
}
}
#endregion
#region Register Collection
@@ -431,40 +445,46 @@ namespace MewtocolNet {
}
if (prop.PropertyType == typeof(short)) {
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, _name: propName);
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(ushort)) {
AddRegister<ushort>(collection.GetType(), cAttribute.MemoryArea, _name: propName);
AddRegister<ushort>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(int)) {
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, _name: propName);
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(uint)) {
AddRegister<uint>(collection.GetType(), cAttribute.MemoryArea, _name: propName);
AddRegister<uint>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(float)) {
AddRegister<float>(collection.GetType(), cAttribute.MemoryArea, _name: propName);
AddRegister<float>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(string)) {
AddRegister<string>(collection.GetType(), cAttribute.MemoryArea, cAttribute.StringLength, _name: propName);
AddRegister<string>(collection.GetType(), cAttribute.MemoryArea, prop, cAttribute.StringLength);
}
if (prop.PropertyType.IsEnum) {
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, _name: propName, _enumType: prop.PropertyType);
if (cAttribute.BitCount == BitCount.B16) {
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, prop, _enumType: prop.PropertyType);
} else {
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, prop, _enumType: prop.PropertyType);
}
}
//read number as bit array
if (prop.PropertyType == typeof(BitArray)) {
if (cAttribute.BitCount == BitCount.B16) {
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, _name: propName, _isBitwise: true);
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, prop, _isBitwise: true);
} else {
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, _name: propName, _isBitwise: true);
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, prop, _isBitwise: true);
}
}
@@ -475,15 +495,15 @@ namespace MewtocolNet {
//var bitwiseCount = Registers.Count(x => x.Value.isUsedBitwise);
if (cAttribute.BitCount == BitCount.B16) {
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, _name: $"Auto_Bitwise_DT{cAttribute.MemoryArea}", _isBitwise: true);
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, prop, _isBitwise: true);
} else {
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, _name: $"Auto_Bitwise_DDT{cAttribute.MemoryArea}", _isBitwise: true);
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, prop, _isBitwise: true);
}
}
if (prop.PropertyType == typeof(TimeSpan)) {
AddRegister<TimeSpan>(collection.GetType(), cAttribute.MemoryArea, _name: propName);
AddRegister<TimeSpan>(collection.GetType(), cAttribute.MemoryArea, prop);
}
}
@@ -494,43 +514,51 @@ namespace MewtocolNet {
RegisterChanged += (reg) => {
//if the register is also used bitwise assign the boolean bit value to the according prop
if(reg.isUsedBitwise) {
//register is used bitwise
if(reg.IsUsedBitwise()) {
for (int i = 0; i < props.Length; i++) {
var prop = props[i];
var bitWiseFound = prop.GetCustomAttributes(true)
.FirstOrDefault(y => y.GetType() == typeof(RegisterAttribute) && ((RegisterAttribute)y).MemoryArea == reg.MemoryAdress);
.FirstOrDefault(y => y.GetType() == typeof(RegisterAttribute) && ((RegisterAttribute)y).MemoryArea == reg.MemoryAddress);
if(bitWiseFound != null) {
if(bitWiseFound != null && reg is NRegister<short> reg16) {
var casted = (RegisterAttribute)bitWiseFound;
var bitIndex = casted.AssignedBitIndex;
var bytes = BitConverter.GetBytes(reg16.Value);
BitArray bitAr = new BitArray(bytes);
BitArray bitAr = null;
if (bitIndex < bitAr.Length && bitIndex >= 0) {
prop.SetValue(collection, bitAr[bitIndex]);
collection.TriggerPropertyChanged(prop.Name);
if (reg is NRegister<short> reg16) {
var bytes = BitConverter.GetBytes((short)reg16.Value);
bitAr = new BitArray(bytes);
} else if(reg is NRegister<int> reg32) {
var bytes = BitConverter.GetBytes((int)reg32.Value);
bitAr = new BitArray(bytes);
}
} else if (bitWiseFound != null && reg is NRegister<int> reg32) {
var casted = (RegisterAttribute)bitWiseFound;
var bitIndex = casted.AssignedBitIndex;
if (bitAr != null && bitIndex < bitAr.Length && bitIndex >= 0) {
var bytes = BitConverter.GetBytes(reg32.Value);
BitArray bitAr = new BitArray(bytes);
//set the specific bit index if needed
prop.SetValue(collection, bitAr[bitIndex]);
collection.TriggerPropertyChanged(prop.Name);
} else if (bitAr != null) {
//set the specific bit array if needed
prop.SetValue(collection, bitAr);
collection.TriggerPropertyChanged(prop.Name);
}
}
}
}
//updating normal properties
var foundToUpdate = props.FirstOrDefault(x => x.Name == reg.Name);
if (foundToUpdate != null) {
@@ -546,67 +574,25 @@ namespace MewtocolNet {
//check if bit parse mode
if (registerAttr.AssignedBitIndex == -1) {
//setting back booleans
if (foundToUpdate.PropertyType == typeof(bool)) {
foundToUpdate.SetValue(collection, ((BRegister)reg).Value);
}
HashSet<Type> NumericTypes = new HashSet<Type> {
typeof(bool),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(float),
typeof(TimeSpan),
typeof(string)
};
//setting back numbers
var regValue = ((IRegister)reg).Value;
if (foundToUpdate.PropertyType == typeof(short)) {
foundToUpdate.SetValue(collection, ((NRegister<short>)reg).Value);
}
if (foundToUpdate.PropertyType == typeof(ushort)) {
foundToUpdate.SetValue(collection, ((NRegister<ushort>)reg).Value);
}
if (foundToUpdate.PropertyType == typeof(int)) {
foundToUpdate.SetValue(collection, ((NRegister<int>)reg).Value);
}
if (foundToUpdate.PropertyType == typeof(uint)) {
foundToUpdate.SetValue(collection, ((NRegister<uint>)reg).Value);
}
if (foundToUpdate.PropertyType == typeof(float)) {
foundToUpdate.SetValue(collection, ((NRegister<float>)reg).Value);
if (NumericTypes.Any(x => foundToUpdate.PropertyType == x)) {
foundToUpdate.SetValue(collection, regValue);
}
if (foundToUpdate.PropertyType.IsEnum) {
foundToUpdate.SetValue(collection, ((NRegister<int>)reg).Value);
}
if (foundToUpdate.PropertyType == typeof(TimeSpan)) {
foundToUpdate.SetValue(collection, ((NRegister<TimeSpan>)reg).Value);
}
//setting back strings
if (foundToUpdate.PropertyType == typeof(string)) {
foundToUpdate.SetValue(collection, ((SRegister)reg).Value);
}
}
if (foundToUpdate.PropertyType == typeof(BitArray)) {
//setting back bit registers
if (reg is NRegister<short> shortReg) {
var bytes = BitConverter.GetBytes(shortReg.Value);
BitArray bitAr = new BitArray(bytes);
foundToUpdate.SetValue(collection, bitAr);
}
if (reg is NRegister<int> intReg) {
var bytes = BitConverter.GetBytes(intReg.Value);
BitArray bitAr = new BitArray(bytes);
foundToUpdate.SetValue(collection, bitAr);
foundToUpdate.SetValue(collection, regValue);
}
}
@@ -733,7 +719,10 @@ namespace MewtocolNet {
try {
queuedMessages++;
var response = await queue.Enqueue(() => SendSingleBlock(_msg));
if (queuedMessages > 0)
queuedMessages--;
if (response == null) {

View File

@@ -166,7 +166,7 @@ namespace MewtocolNet {
/// <param name="_toRead">The register to read</param>
public async Task<BRegisterResult> ReadBoolRegister (BRegister _toRead) {
string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolIdent()}";
string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring);
if(!result.Success) {
@@ -198,7 +198,7 @@ namespace MewtocolNet {
/// <returns>The success state of the write operation</returns>
public async Task<bool> WriteBoolRegister (BRegister _toWrite, bool value) {
string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolIdent()}{(value ? "1" : "0")}";
string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(value ? "1" : "0")}";
var result = await SendCommandAsync(requeststring);
@@ -220,7 +220,7 @@ namespace MewtocolNet {
Type numType = typeof(T);
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolIdent()}";
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring);
var failedResult = new NRegisterResult<T> {
@@ -335,7 +335,7 @@ namespace MewtocolNet {
toWriteVal = null;
}
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolIdent()}{toWriteVal.ToHexString()}";
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{toWriteVal.ToHexString()}";
var result = await SendCommandAsync(requeststring);
@@ -362,7 +362,7 @@ namespace MewtocolNet {
/// <returns></returns>
public async Task<SRegisterResult> ReadStringRegister (SRegister _toRead, int _stationNumber = 1) {
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolIdent()}";
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring);
if (result.Success)
_toRead.SetValueFromPLC(result.Response.ParseDTString());

View File

@@ -8,7 +8,8 @@ using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.RegisterAttributes {
namespace MewtocolNet.RegisterAttributes
{
/// <summary>
/// A register collection base with full auto read and notification support built in
@@ -34,6 +35,24 @@ namespace MewtocolNet.RegisterAttributes {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Use this on the setter method of a property to enable automatic property register writing
/// </summary>
public void AutoSetter<T> (object value, ref T privateField, [CallerMemberName] string propName = null) {
PLCInterface.PropertyRegisterWasSet(propName, value);
if(value is IRegister reg) {
privateField = (T)reg.Value;
return;
}
privateField = (T)value;
}
/// <summary>
/// Gets called when the register collection base was linked to its parent mewtocol interface
/// </summary>

View File

@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Text;
using MewtocolNet;
@@ -7,17 +8,47 @@ namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a boolean
/// </summary>
public class BRegister : Register {
public class BRegister : IRegister, INotifyPropertyChanged {
/// <summary>
/// Gets called whenever the value was changed
/// </summary>
public event Action<object> ValueChanged;
/// <summary>
/// Triggers when a property on the register changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
internal RegisterType RegType { get; private set; }
internal SpecialAddress SpecialAddress { get; private set; }
internal bool LastValue;
internal Type collectionType;
/// <summary>
/// The type of collection the register is in or null of added manually
/// </summary>
public Type CollectionType => collectionType;
internal bool lastValue;
/// <summary>
/// The value of the register
/// </summary>
public bool Value => LastValue;
public object Value => lastValue;
internal string name;
/// <summary>
/// The register name or null of not defined
/// </summary>
public string Name => name;
internal int memoryAdress;
/// <summary>
/// The registers memory adress if not a special register
/// </summary>
public int MemoryAddress => memoryAdress;
/// <summary>
/// Defines a register containing a number
@@ -27,7 +58,11 @@ namespace MewtocolNet.Registers {
/// <param name="_name">Name of the register</param>
public BRegister (int _address, RegisterType _type = RegisterType.R, string _name = null) {
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
if (_address > 99999) throw new NotSupportedException("Memory addresses cant be greater than 99999");
if (_type != RegisterType.X && _type != RegisterType.Y && _type != RegisterType.R)
throw new NotSupportedException("The register type cant be numeric, use X, Y or R");
memoryAdress = _address;
name = _name;
@@ -44,7 +79,10 @@ namespace MewtocolNet.Registers {
public BRegister (SpecialAddress _address, RegisterType _type = RegisterType.R, string _name = null) {
if (_address == SpecialAddress.None)
throw new NotSupportedException("Special adress cant be none");
throw new NotSupportedException("Special address cant be none");
if (_type != RegisterType.X && _type != RegisterType.Y && _type != RegisterType.R)
throw new NotSupportedException("The register type cant be numeric, use X, Y or R");
SpecialAddress = _address;
name = _name;
@@ -53,15 +91,22 @@ namespace MewtocolNet.Registers {
}
internal BRegister WithCollectionType(Type colType) {
collectionType = colType;
return this;
}
/// <summary>
/// Builds the register area name
/// </summary>
public override string BuildMewtocolIdent () {
public string BuildMewtocolQuery () {
//build area code from register type
StringBuilder asciistring = new StringBuilder(RegType.ToString());
if(SpecialAddress == SpecialAddress.None) {
asciistring.Append(MemoryAdress.ToString().PadLeft(4, '0'));
asciistring.Append(MemoryAddress.ToString().PadLeft(4, '0'));
} else {
asciistring.Append(SpecialAddress.ToString().PadLeft(4, '0'));
}
@@ -71,11 +116,52 @@ namespace MewtocolNet.Registers {
}
internal void SetValueFromPLC (bool val) {
LastValue = val;
lastValue = val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
}
public string GetStartingMemoryArea() {
if (SpecialAddress != SpecialAddress.None)
return SpecialAddress.ToString();
return MemoryAddress.ToString();
}
public bool IsUsedBitwise() => false;
public Type GetCollectionType() => CollectionType;
public string GetValueString() => Value.ToString();
public void ClearValue() => SetValueFromPLC(false);
public string GetRegisterString() => RegType.ToString();
public string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
public string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
public string GetRegisterPLCName() {
if (SpecialAddress != SpecialAddress.None) {
return $"{GetRegisterString()}{SpecialAddress}";
}
return $"{GetRegisterString()}{MemoryAddress}";
}
internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed);
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
}
}

View File

@@ -1,18 +1,63 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a number
/// </summary>
/// <typeparam name="T">The type of the numeric value</typeparam>
public class NRegister<T> : Register {
public class NRegister<T> : IRegister {
internal T LastValue;
/// <summary>
/// Gets called whenever the value was changed
/// </summary>
public event Action<object> ValueChanged;
/// <summary>
/// Triggers when a property on the register changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
internal Type collectionType;
/// <summary>
/// The type of collection the register is in or null of added manually
/// </summary>
public Type CollectionType => collectionType;
internal T lastValue;
/// <summary>
/// The value of the register
/// </summary>
public T Value => LastValue;
public object Value => lastValue;
internal string name;
/// <summary>
/// The register name or null of not defined
/// </summary>
public string Name => name;
internal int memoryAdress;
/// <summary>
/// The registers memory adress if not a special register
/// </summary>
public int MemoryAddress => memoryAdress;
internal int memoryLength;
/// <summary>
/// The rgisters memory length
/// </summary>
public int MemoryLength => memoryLength;
internal bool isUsedBitwise { get; set; }
internal Type enumType { get; set; }
/// <summary>
/// Defines a register containing a number
@@ -23,6 +68,7 @@ namespace MewtocolNet.Registers {
if (_adress > 99999)
throw new NotSupportedException("Memory adresses cant be greater than 99999");
memoryAdress = _adress;
name = _name;
Type numType = typeof(T);
@@ -71,15 +117,159 @@ namespace MewtocolNet.Registers {
}
internal NRegister<T> WithCollectionType (Type colType) {
collectionType = colType;
return this;
}
internal void SetValueFromPLC (object val) {
LastValue = (T)val;
lastValue = (T)val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
}
public string GetStartingMemoryArea () => this.MemoryAddress.ToString();
public Type GetCollectionType() => CollectionType;
public bool IsUsedBitwise() => isUsedBitwise;
public string GetValueString() {
//is number or bitwise
if(enumType == null) {
return $"{Value}{(isUsedBitwise ? $" [{GetBitwise().ToBitString()}]" : "")}";
}
//is enum
var dict = new Dictionary<int, string>();
foreach (var name in Enum.GetNames(enumType)) {
int enumKey = (int)Enum.Parse(enumType, name);
if (!dict.ContainsKey(enumKey)) {
dict.Add(enumKey, name);
}
}
if (enumType != null && Value is short shortVal) {
if (dict.ContainsKey(shortVal)) {
return $"{Value} ({dict[shortVal]})";
} else {
return $"{Value} (Missing Enum)";
}
}
if (enumType != null && Value is int intVal) {
if (dict.ContainsKey(intVal)) {
return $"{Value} ({dict[intVal]})";
} else {
return $"{Value} (Missing Enum)";
}
}
return Value.ToString();
}
/// <summary>
/// Gets the register bitwise if its a 16 or 32 bit int
/// </summary>
/// <returns>A bitarray</returns>
public BitArray GetBitwise() {
if (this is NRegister<short> shortReg) {
var bytes = BitConverter.GetBytes((short)Value);
BitArray bitAr = new BitArray(bytes);
return bitAr;
}
if (this is NRegister<int> intReg) {
var bytes = BitConverter.GetBytes((int)Value);
BitArray bitAr = new BitArray(bytes);
return bitAr;
}
return null;
}
public string BuildMewtocolQuery() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + MemoryLength).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
public string GetRegisterString() {
if(Value is short) {
return "DT";
}
if (Value is ushort) {
return "DT";
}
if (Value is int) {
return "DDT";
}
if (Value is uint) {
return "DDT";
}
if (Value is float) {
return "DDT";
}
if (Value is TimeSpan) {
return "DDT";
}
throw new NotSupportedException("Numeric type is not supported");
}
public string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
public string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
public string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}";
public void ClearValue() => SetValueFromPLC(default(T));
internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed);
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
}
}

View File

@@ -21,12 +21,14 @@ namespace MewtocolNet.Registers {
/// Trys to get the value of there is one
/// </summary>
public bool TryGetValue (out T value) {
if(Result.Success) {
value = Register.Value;
value = (T)Register.Value;
return true;
}
value = default(T);
return false;
}
}

View File

@@ -1,249 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// A class describing a register
/// </summary>
public abstract class Register : INotifyPropertyChanged {
/// <summary>
/// Gets called whenever the value was changed
/// </summary>
public event Action<object> ValueChanged;
/// <summary>
/// Triggers when a property on the register changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
internal Type collectionType;
/// <summary>
/// The type of collection the register is in or null of added manually
/// </summary>
public Type CollectionType => collectionType;
internal string name;
/// <summary>
/// The register name or null of not defined
/// </summary>
public string Name => name;
internal int memoryAdress;
/// <summary>
/// The registers memory adress if not a special register
/// </summary>
public int MemoryAdress => memoryAdress;
internal int memoryLength;
/// <summary>
/// The rgisters memory length
/// </summary>
public int MemoryLength => memoryLength;
/// <summary>
/// The value of the register auto converted to a string
/// </summary>
public string StringValue => GetValueString();
/// <summary>
/// The name the register would have in the PLC
/// </summary>
public string RegisterPLCName => GetRegisterPLCName();
/// <summary>
/// The combined name with the holding register class type infront
/// </summary>
public string CombinedName => GetCombinedName();
/// <summary>
/// The name of the class that contains this register or empty if it was added manually
/// </summary>
public string ContainerName => GetContainerName();
internal bool isUsedBitwise { get; set; }
internal Type enumType { get; set; }
internal Register () {
ValueChanged += (obj) => {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StringValue)));
};
}
/// <summary>
/// Builds the register area name
/// </summary>
public virtual string BuildMewtocolIdent() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
internal void TriggerChangedEvnt(object changed) {
ValueChanged?.Invoke(changed);
}
internal void TriggerNotifyChange () {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
}
/// <summary>
/// Gets the starting memory are either numeric or A,B,C,D etc for special areas like inputs
/// </summary>
/// <returns></returns>
public string GetStartingMemoryArea () {
if (this is BRegister bReg && bReg.SpecialAddress != SpecialAddress.None) {
return bReg.SpecialAddress.ToString();
}
return this.MemoryAdress.ToString();
}
/// <summary>
/// Gets the current value in the adress as a string
/// </summary>
/// <returns></returns>
public string GetValueString () {
if (enumType != null && this is NRegister<int> intEnumReg) {
var dict = new Dictionary<int, string>();
foreach (var name in Enum.GetNames(enumType)) {
int enumKey = (int)Enum.Parse(enumType, name);
if(!dict.ContainsKey(enumKey)) {
dict.Add(enumKey, name);
}
}
if(dict.ContainsKey(intEnumReg.Value)) {
return $"{intEnumReg.Value} ({dict[intEnumReg.Value]})";
} else {
return $"{intEnumReg.Value} (Missing Enum)";
}
}
if (this is NRegister<short> shortReg) {
return $"{shortReg.Value}{(isUsedBitwise ? $" [{shortReg.GetBitwise().ToBitString()}]" : "")}";
}
if (this is NRegister<ushort> ushortReg) {
return ushortReg.Value.ToString();
}
if (this is NRegister<int> intReg) {
return $"{intReg.Value}{(isUsedBitwise ? $" [{intReg.GetBitwise().ToBitString()}]" : "")}";
}
if (this is NRegister<uint> uintReg) {
return uintReg.Value.ToString();
}
if (this is NRegister<float> floatReg) {
return floatReg.Value.ToString();
}
if (this is NRegister<TimeSpan> tsReg) {
return tsReg.Value.ToString();
}
if (this is BRegister boolReg) {
return boolReg.Value.ToString();
}
if (this is SRegister stringReg) {
return stringReg.Value ?? "";
}
return "Type of the register is not supported.";
}
/// <summary>
/// Gets the register bitwise if its a 16 or 32 bit int
/// </summary>
/// <returns>A bitarray</returns>
public BitArray GetBitwise () {
if (this is NRegister<short> shortReg) {
var bytes = BitConverter.GetBytes(shortReg.Value);
BitArray bitAr = new BitArray(bytes);
return bitAr;
}
if (this is NRegister<int> intReg) {
var bytes = BitConverter.GetBytes(intReg.Value);
BitArray bitAr = new BitArray(bytes);
return bitAr;
}
return null;
}
/// <summary>
/// Gets the register dataarea string DT for 16bit and DDT for 32 bit types
/// </summary>
public string GetRegisterString () {
if (this is NRegister<short> shortReg) {
return "DT";
}
if (this is NRegister<ushort> ushortReg) {
return "DT";
}
if (this is NRegister<int> intReg) {
return "DDT";
}
if (this is NRegister<uint> uintReg) {
return "DDT";
}
if (this is NRegister<float> floatReg) {
return "DDT";
}
if (this is NRegister<TimeSpan> tsReg) {
return "DDT";
}
if (this is BRegister boolReg) {
return boolReg.RegType.ToString();
}
if (this is SRegister stringReg) {
return "DT";
}
return "Type of the register is not supported.";
}
internal string GetCombinedName () {
return $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
}
internal string GetContainerName () {
return $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
}
internal string GetRegisterPLCName () {
if (this is BRegister bReg && bReg.SpecialAddress != SpecialAddress.None) {
return $"{GetRegisterString()}{bReg.SpecialAddress}";
}
return $"{GetRegisterString()}{MemoryAdress}";
}
}
}

View File

@@ -1,18 +1,54 @@
using System;
using System.ComponentModel;
using System.Text;
namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a string
/// </summary>
public class SRegister : Register {
private string lastVal = "";
public class SRegister : IRegister {
/// <summary>
/// The current value of the register
/// Gets called whenever the value was changed
/// </summary>
public string Value => lastVal;
public event Action<object> ValueChanged;
/// <summary>
/// Triggers when a property on the register changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
internal Type collectionType;
/// <summary>
/// The type of collection the register is in or null of added manually
/// </summary>
public Type CollectionType => collectionType;
internal string lastValue;
/// <summary>
/// The value of the register
/// </summary>
public object Value => lastValue;
internal string name;
/// <summary>
/// The register name or null of not defined
/// </summary>
public string Name => name;
internal int memoryAdress;
/// <summary>
/// The registers memory adress if not a special register
/// </summary>
public int MemoryAddress => memoryAdress;
internal int memoryLength;
/// <summary>
/// The registers memory length
/// </summary>
public int MemoryLength => memoryLength;
internal short ReservedSize { get; set; }
@@ -35,15 +71,22 @@ namespace MewtocolNet.Registers {
memoryLength = (int)Math.Round(wordsize + 1);
}
internal SRegister WithCollectionType(Type colType) {
collectionType = colType;
return this;
}
/// <summary>
/// Builds the register identifier for the mewotocol protocol
/// </summary>
public override string BuildMewtocolIdent() {
public string BuildMewtocolQuery() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0'));
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + MemoryLength).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
@@ -55,18 +98,45 @@ namespace MewtocolNet.Registers {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAdress + overwriteWordLength - 1).ToString().PadLeft(5, '0'));
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + overwriteWordLength - 1).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
public Type GetCollectionType() => CollectionType;
public bool IsUsedBitwise() => false;
internal void SetValueFromPLC (string val) {
lastVal = val;
lastValue = val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
}
public string GetStartingMemoryArea() => this.MemoryAddress.ToString();
public string GetValueString() => Value?.ToString() ?? "";
public string GetRegisterString() => "DT";
public string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
public string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
public string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}";
public void ClearValue() => SetValueFromPLC(null);
internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed);
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
public override string ToString () => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
}
}

View File

@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>MewtocolNet</PackageId>
<Version>0.5.8</Version>
<PackageId>Mewtocol.NET</PackageId>
<Version>0.7.0</Version>
<Authors>Felix Weiss</Authors>
<Company>Womed</Company>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>A Mewtocol protocol library to interface with Panasonic PLCs over TCP/Serial.</Description>
<Copyright>Copyright (c) 2022 WOLF Medizintechnik GmbH</Copyright>
<Copyright>Copyright (c) 2022 - 2023 WOLF Medizintechnik GmbH</Copyright>
<PackageProjectUrl>https://github.com/WOmed/MewtocolNet</PackageProjectUrl>
<RepositoryUrl>https://github.com/WOmed/MewtocolNet</RepositoryUrl>
<PackageTags>plc;panasonic;mewtocol;automation;</PackageTags>
@@ -18,4 +18,9 @@
<DocumentationFile>..\Builds\MewtocolNet.xml</DocumentationFile>
<OutputPath>..\Builds</OutputPath>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>MewtocolTests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,306 @@
using Xunit;
using MewtocolNet;
using MewtocolNet.Registers;
using System.Diagnostics;
using Xunit.Abstractions;
using System.Collections;
using MewtocolNet.RegisterAttributes;
using Microsoft.Win32;
namespace MewtocolTests {
public class AutomatedPropertyRegisters {
private readonly ITestOutputHelper output;
public AutomatedPropertyRegisters(ITestOutputHelper output) {
this.output = output;
}
public class TestRegisterCollection : RegisterCollectionBase {
//corresponds to a R100 boolean register in the PLC
[Register(1000, 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 DT1101 - DT1104 string register in the PLC with (STRING[4])
//[Register(1101, 4)]
//public string TestString1 { get; private set; }
//corresponds to a DT7000 16 bit int register in the PLC
[Register(899)]
public short TestInt16 { get; private set; }
[Register(342)]
public ushort TestUInt16 { get; private set; }
//corresponds to a DTD7001 - DTD7002 32 bit int register in the PLC
[Register(7001)]
public int TestInt32 { get; private set; }
[Register(765)]
public uint TestUInt32 { get; private set; }
//corresponds to a DTD7001 - DTD7002 32 bit float register in the PLC (REAL)
[Register(7003)]
public float TestFloat32 { get; private set; }
//corresponds to a DT7005 - DT7009 string register in the PLC with (STRING[5])
[Register(7005, 5)]
public string TestString2 { get; private set; }
//corresponds to a DT7010 as a 16bit word/int and parses the word as single bits
[Register(7010)]
public BitArray TestBitRegister { get; private set; }
[Register(8010, BitCount.B32)]
public BitArray TestBitRegister32 { get; private set; }
//corresponds to a DT1204 as a 16bit word/int takes the bit at index 9 and writes it back as a boolean
[Register(1204, 9, BitCount.B16)]
public bool BitValue { get; private set; }
[Register(1204, 5, BitCount.B16)]
public bool FillTest { 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; }
public enum CurrentState {
Undefined = 0,
State1 = 1,
State2 = 2,
//State3 = 3,
State4 = 4,
State5 = 5,
StateBetween = 100,
State6 = 6,
State7 = 7,
}
[Register(50)]
public CurrentState TestEnum16 { get; private set; }
[Register(51, BitCount.B32)]
public CurrentState TestEnum32 { get; private set; }
}
private void TestBasicGeneration(IRegister reg, string propName, object expectValue, int expectAddr, string expectPlcName) {
Assert.NotNull(reg);
Assert.Equal(propName, reg.Name);
Assert.Equal(expectValue, reg.Value);
Assert.Equal(expectAddr, reg.MemoryAddress);
Assert.Equal(expectPlcName, reg.GetRegisterPLCName());
output.WriteLine(reg.ToString());
}
//actual tests
[Fact(DisplayName = "Boolean R generation")]
public void BooleanGen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestBool1), false, 1000, "R1000");
}
[Fact(DisplayName = "Boolean input XD generation")]
public void BooleanInputGen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBoolInputXD));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestBoolInputXD), false, 0, "XD");
}
[Fact(DisplayName = "Int16 generation")]
public void Int16Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt16));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestInt16), (short)0, 899, "DT899");
}
[Fact(DisplayName = "UInt16 generation")]
public void UInt16Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt16));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestUInt16), (ushort)0, 342, "DT342");
}
[Fact(DisplayName = "Int32 generation")]
public void Int32Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt32));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestInt32), (int)0, 7001, "DDT7001");
}
[Fact(DisplayName = "UInt32 generation")]
public void UInt32Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt32));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestUInt32), (uint)0, 765, "DDT765");
}
[Fact(DisplayName = "Float32 generation")]
public void Float32Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestFloat32));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestFloat32), (float)0, 7003, "DDT7003");
}
[Fact(DisplayName = "String generation")]
public void StringGen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestString2));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestString2), null!, 7005, "DT7005");
Assert.Equal(5, ((SRegister)register).ReservedSize);
Assert.Equal(4, ((SRegister)register).MemoryLength);
}
[Fact(DisplayName = "BitArray 16bit generation")]
public void BitArray16Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister($"Auto_Bitwise_DT7010");
//test generic properties
TestBasicGeneration(register, "Auto_Bitwise_DT7010", (short)0, 7010, "DT7010");
Assert.True(((NRegister<short>)register).isUsedBitwise);
Assert.Equal(0, ((NRegister<short>)register).MemoryLength);
}
[Fact(DisplayName = "BitArray 32bit generation")]
public void BitArray32Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister($"Auto_Bitwise_DDT8010");
//test generic properties
TestBasicGeneration(register, "Auto_Bitwise_DDT8010", (int)0, 8010, "DDT8010");
Assert.True(((NRegister<int>)register).isUsedBitwise);
Assert.Equal(1, ((NRegister<int>)register).MemoryLength);
}
[Fact(DisplayName = "BitArray single bool generation")]
public void BitArraySingleBool16Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister($"Auto_Bitwise_DT1204");
//test generic properties
TestBasicGeneration(register, "Auto_Bitwise_DT1204", (short)0, 1204, "DT1204");
Assert.True(((NRegister<short>)register).isUsedBitwise);
}
[Fact(DisplayName = "TimeSpan generation")]
public void TimespanGen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestTime), TimeSpan.Zero, 7012, "DDT7012");
}
[Fact(DisplayName = "Enum16 generation")]
public void Enum16Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestEnum16));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestEnum16), (short)TestRegisterCollection.CurrentState.Undefined, 50, "DT50");
}
[Fact(DisplayName = "Enum32 generation")]
public void Enum32Gen () {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestEnum32));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestEnum32), (int)TestRegisterCollection.CurrentState.Undefined, 51, "DDT51");
}
}
}

View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,114 @@
using MewtocolNet;
using MewtocolNet.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
namespace MewtocolTests {
public class TestLivePLC {
private readonly ITestOutputHelper output;
private List<ExpectedTestData> testData = new() {
new ExpectedTestData {
PLCName = "FPX-H C30T",
PLCIP = "192.168.115.210",
PLCPort = 9094,
Type = CpuType.FP_Sigma_X_H_30K_60K_120K,
ProgCapacity = 32,
},
new ExpectedTestData {
PLCName = "FPX-H C14R",
PLCIP = "192.168.115.212",
PLCPort = 9094,
Type = CpuType.FP_Sigma_X_H_30K_60K_120K,
ProgCapacity = 16,
},
};
public TestLivePLC (ITestOutputHelper output) {
this.output = output;
Logger.LogLevel = LogLevel.Verbose;
Logger.OnNewLogMessage((d,m) => {
output.WriteLine($"Mewtocol Logger: {d} {m}");
});
}
[Fact(DisplayName = "Connection cycle client to PLC")]
public async void TestClientConnection () {
foreach (var plc in testData) {
output.WriteLine($"Testing: {plc.PLCName}");
var cycleClient = new MewtocolInterface(plc.PLCIP, plc.PLCPort);
await cycleClient.ConnectAsync();
Assert.True(cycleClient.IsConnected);
cycleClient.Disconnect();
Assert.False(cycleClient.IsConnected);
}
}
[Fact(DisplayName = "Reading basic information from PLC")]
public async void TestClientReadPLCStatus () {
foreach (var plc in testData) {
output.WriteLine($"Testing: {plc.PLCName}\n");
var client = new MewtocolInterface(plc.PLCIP, plc.PLCPort);
await client.ConnectAsync();
output.WriteLine($"{client.PlcInfo}\n");
Assert.True(client.IsConnected);
Assert.Equal(client.PlcInfo.CpuInformation.Cputype, plc.Type);
Assert.Equal(client.PlcInfo.CpuInformation.ProgramCapacity, plc.ProgCapacity);
client.Disconnect();
}
}
}
public class ExpectedTestData {
public string PLCName { get; set; }
public string PLCIP { get; set; }
public int PLCPort { get; set; }
public CpuType Type { get; set; }
public int ProgCapacity { get; set; }
}
}

View File

@@ -0,0 +1,164 @@
using Xunit;
using MewtocolNet;
using MewtocolNet.Registers;
using Xunit.Abstractions;
namespace MewtocolTests {
public class TestRegisterInterface {
private readonly ITestOutputHelper output;
public TestRegisterInterface (ITestOutputHelper output) {
this.output = output;
}
[Fact(DisplayName = "Numeric mewtocol query building")]
public void NumericRegisterMewtocolIdentifiers () {
List<IRegister> registers = new List<IRegister> {
new NRegister<short>(50, _name: null),
new NRegister<ushort>(50, _name: null),
new NRegister<int>(50, _name : null),
new NRegister<uint>(50, _name : null),
new NRegister<float>(50, _name : null),
new NRegister<TimeSpan>(50, _name : null),
};
List<string> expcectedIdents = new List<string> {
"D0005000050", //single word register
"D0005000050", //single word register
"D0005000051", //double word register
"D0005000051", //double word register
"D0005000051", //double word register
"D0005000051", //double word register
};
//test mewtocol idents
for (int i = 0; i < registers.Count; i++) {
IRegister? reg = registers[i];
string expect = expcectedIdents[i];
Assert.Equal(expect, reg.BuildMewtocolQuery());
}
}
[Fact(DisplayName = "PLC register naming convention test")]
public void PLCRegisterIdentifiers () {
List<IRegister> registers = new List<IRegister> {
//numeric ones
new NRegister<short>(50, _name: null),
new NRegister<ushort>(60, _name : null),
new NRegister<int>(70, _name : null),
new NRegister<uint>(80, _name : null),
new NRegister<float>(90, _name : null),
new NRegister<TimeSpan>(100, _name : null),
//boolean
new BRegister(100),
new BRegister(5, RegisterType.X),
new BRegister(SpecialAddress.A, RegisterType.X),
//string
new SRegister(999, 5),
};
List<string> expcectedIdents = new List<string> {
"DT50",
"DT60",
"DDT70",
"DDT80",
"DDT90",
"DDT100",
"R100",
"X5",
"XA",
"DT999"
};
//test mewtocol idents
for (int i = 0; i < registers.Count; i++) {
IRegister? reg = registers[i];
string expect = expcectedIdents[i];
Assert.Equal(expect, reg.GetRegisterPLCName());
}
}
[Fact(DisplayName = "Non allowed (Overflow address)")]
public void OverFlowRegisterAddress () {
var ex = Assert.Throws<NotSupportedException>(() => {
new NRegister<short>(100000, _name: null);
});
output.WriteLine(ex.Message.ToString());
var ex1 = Assert.Throws<NotSupportedException>(() => {
new BRegister(100000);
});
output.WriteLine(ex1.Message.ToString());
var ex2 = Assert.Throws<NotSupportedException>(() => {
new SRegister(100000, 5);
});
output.WriteLine(ex2.Message.ToString());
}
[Fact(DisplayName = "Non allowed (Wrong data type)")]
public void WrongDataTypeRegister () {
var ex = Assert.Throws<NotSupportedException>(() => {
new NRegister<double>(100, _name: null);
});
output.WriteLine(ex.Message.ToString());
}
[Fact(DisplayName = "Non allowed (Wrong bool type address)")]
public void WrongDataTypeRegisterBool1 () {
var ex = Assert.Throws<NotSupportedException>(() => {
new BRegister(100, RegisterType.DDT_int);
});
output.WriteLine(ex.Message.ToString());
}
[Fact(DisplayName = "Non allowed (Wrong bool special address)")]
public void WrongDataTypeRegisterBool2 () {
var ex = Assert.Throws<NotSupportedException>(() => {
new BRegister(SpecialAddress.None, RegisterType.X);
});
output.WriteLine(ex.Message.ToString());
}
}
}

BIN
PLC_Test/test_c30_fpx_h.ini Normal file

Binary file not shown.

BIN
PLC_Test/test_c30_fpx_h.pro Normal file

Binary file not shown.

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<ProjectConfiguration CompactMode="1">
<PaneContents>
<Pane-1 PaneBaseID="410" PaneInstanceNumber="1">
<Contents BinaryData="B21A0000789CE599516F9B3010C7FB39F684E843DF164C429B484D251A688794940A58BA699A22065E6A0D6C844DD3EC43ED33CE86A66D68B384AA04A4BD4418B8BB3FF7E3EC33F9F3E1E0E07442306224BD4138240B6944E2C40FD884847028035972835B18FB43B9D7E5C7B7646105044F115C0C65453E3B1D91288B315D1D481683B18543782F2E4B166630C57E74E5C7DC991542CCD04F045359D22334C7311F73C760D0EB2A3DEE9DF90C05372864B73CB2C6EDF58C91E7E34FD00F61FACC96EB33EF139F070C45C0CE6B3A4059C7D48F32B8260128BDBE76D22F4950C1604D423E7E9304B52CE1E25A0FC31452BA838CFE7A22FA6FCD43B72C829316D63BC05095750DF9F84D227A2F4444BEC8C21451F42382F95BB3FDDD28C9019BE4D851F8E0F829FC56895A59A2B74C60830A3B8F355638C8FD16E75CF41BE1792E292F4DFE10D4C29729C912FA60EDC080A4A194A779283BE6C8760C591A93E05711403243C4FCFCC1B89CEB14DE3DD6B670B732B43C73224B1EBCE7B23D48D9392111288C5DB6CCADD5BE88B7D1C8F96CBEB85F72633F65793DBA308201430417934E3E1296361636E264C9392F21C3704CD75D4570789295B5102A38392E9B8DECC9C4BCF2B6DF37D69F5C4F75677639B6CFF5F1563BEFEBB5B9323BB7EDD70D3A0595DAE8185912A1C067B00A21A55E3C86E1B50E90616D08543720513E164E32F6C5A882E8421FBB3557514950D3841A2B21BE0480E336958FD71F0C5A85A6A9DAE164BA6A9BC818DE89A28056B1696C62BB88885F15CFC7BD00EAB60A90636E30A81B90CB52DE325602747454F7D4C6F168ADC2E37A8E7575F94DFBDE4C77809803E78832B153DE9D13383E54CA1D562DB040BBBAB89B3CEB7BE6243661330E6A81289CF1D656157BC0D6B17A21EBBF64256ACA4371A5DD9077A8D03D2C4B406D151ECF9A988DE0317116B7ABA7D3DA35C935D3D151C1A652B3B097BA012DFB8E5063D94806A249E42FC54748716D73E6E7B3C568194428E0AB920B196F1EAA6D60D57C459A153FE0E1A8EE054A6B595F5EE3FA245954340D16A63065302CFC6C42BA0BB6B2CA7742F3BE3476CAFDBFF2DD59FB7BEAEC2F8D4435CD">
<FilterHistory/>
</Contents>
</Pane-1>
<Summary PaneCount="1"/>
</PaneContents>
</ProjectConfiguration>

View File

@@ -1,7 +1,6 @@
[![.NET Windows](https://github.com/WOmed/MewtocolNet/actions/workflows/dotnet-windows.yml/badge.svg)](https://github.com/WOmed/MewtocolNet/actions/workflows/dotnet-windows.yml)
![Nuget](https://img.shields.io/nuget/v/MewtocolNet)
![Lines of code](https://img.shields.io/tokei/lines/github/WOmed/MewtocolNet)
![Nuget](https://img.shields.io/nuget/dt/MewtocolNet)
[![Build pipeline](https://github.com/WOmed/MewtocolNet/actions/workflows/build-pipeline.yml/badge.svg)](https://github.com/WOmed/MewtocolNet/actions/workflows/build-pipeline.yml)
[![Nuget](https://img.shields.io/nuget/v/Mewtocol.NET)](https://www.nuget.org/packages/Mewtocol.NET)
[![codecov](https://codecov.io/gh/WOmed/MewtocolNet/branch/master/graph/badge.svg?token=M50U8EZPC6)](https://codecov.io/gh/WOmed/MewtocolNet)
![GitHub](https://img.shields.io/github/license/WOmed/MewtocolNet)
![Status](https://img.shields.io/badge/Status-In%20dev-orange)
@@ -51,14 +50,14 @@ Where is the RS232/Serial support?
# Installing
Install this package by using [Nuget](https://www.nuget.org/packages/MewtocolNet/) or reference
Install this package by using [Nuget](https://www.nuget.org/packages/Mewtocol.NET) or reference
```XML
<PackageReference Include="MewtocolNet" Version="0.5.2" />
<PackageReference Include="Mewtocol.NET" Version="0.7.0"/>
```
in your dependencies.
Alternatively use the dotnet CLI and run
```Shell
dotnet add package MewtocolNet
dotnet add package Mewtocol.NET
```
# Protocol description
@@ -142,12 +141,29 @@ await plc.ConnectAsync(
## Writing data registers / contacts
⚠ **Never set a register by setting the property, always use one of the provided methods**
Registers are stored in an underlying layer for automatic handling, each register has a unique name and address.
Classes that derive from `RegisterCollectionBase` reference these registers automatically using attributes.
All the heavy lifting is done automatically for you, setting this up is described [here](https://github.com/WOmed/MewtocolNet/wiki/Attribute-handled-reading)
### Asynchronous
This operations awaits a task to make sure the register was actually set to your desired value before progressing
```C#
//sets the register to false
await plc.SetRegisterAsync(nameof(registers.TestBool1), false);
//set the current second to the PLCs TIME register
await plc.SetRegisterAsync(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Second));
```
### Synchronous
Sets the register without feedback if it was set
You can use the method to set a register
```C#
//inverts the boolean register
plc.SetRegister(nameof(registers.TestBool1), !registers.TestBool1);
@@ -159,6 +175,13 @@ plc.SetRegister(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Se
plc.SetRegister(nameof(registers.TestString1), "Test");
```
or write to a register in your `RegisterCollectionBase` directly (you need to attach a register collection to your interface beforehand)
```C#
//inverts the boolean register
registers.TestBool1 = true;
```
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