mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 11:11:23 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bd07bb520 | ||
|
|
f99af9511b | ||
|
|
b13b2d0199 | ||
|
|
364e0637bb | ||
|
|
bba90106cb | ||
|
|
3dea18d285 | ||
|
|
ad61361008 | ||
|
|
0e0659000a | ||
|
|
8cc47b496a | ||
|
|
9eb09fc7ec | ||
|
|
9b8fca6561 | ||
|
|
6205f81931 | ||
|
|
a8960f12c8 | ||
|
|
0b670b1a27 | ||
|
|
63a1348704 | ||
|
|
61fe2aff65 | ||
|
|
314ce9c053 | ||
|
|
7864915967 | ||
|
|
62f4a48bf9 | ||
|
|
a78a405c25 | ||
|
|
09f4da54a9 | ||
|
|
6ca8e9de96 | ||
|
|
53a0856634 | ||
|
|
2b173aeb95 | ||
|
|
2680bbb07b | ||
|
|
9fd178424f | ||
|
|
e96496cff8 | ||
|
|
39ff1a5c5b | ||
|
|
6c50324696 | ||
|
|
affa2ea83f | ||
|
|
af101812a8 | ||
|
|
35f2786129 | ||
|
|
062eb75876 | ||
|
|
c771e747eb | ||
|
|
dc24b33297 | ||
|
|
68aa2ad12a | ||
|
|
bb1cc8c011 | ||
|
|
24525521af | ||
|
|
435624f8a2 | ||
|
|
7c3f279576 | ||
|
|
6001402991 | ||
|
|
fb2bd8d56d | ||
|
|
a71d545bf3 | ||
|
|
43c7f72ac4 | ||
|
|
2e35ed87af | ||
|
|
a7f97a72ea | ||
|
|
51870166e4 | ||
|
|
b43e9bd201 | ||
|
|
e313dbc3ec | ||
|
|
bdf9f93f97 | ||
|
|
fe816ab78e | ||
|
|
48a5977185 | ||
|
|
c69f63c191 | ||
|
|
6a2f278dd1 | ||
|
|
19159ed183 | ||
|
|
635823a66f | ||
|
|
c7a6559f97 | ||
|
|
88a453355c | ||
|
|
6f8f891760 | ||
|
|
8fb8d4989d |
70
.github/workflows/codeql.yml
vendored
Normal file
70
.github/workflows/codeql.yml
vendored
Normal 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}}"
|
||||
23
.github/workflows/dotnet-windows.yml
vendored
23
.github/workflows/dotnet-windows.yml
vendored
@@ -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
63
.github/workflows/publish-pipeline.yml
vendored
Normal 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
50
.github/workflows/test-pipeline.yml
vendored
Normal 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 }}
|
||||
183
Examples/ExampleScenarios.cs
Normal file
183
Examples/ExampleScenarios.cs
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +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");
|
||||
|
||||
static void Scenario1 () {
|
||||
} else if (line == "exit") {
|
||||
|
||||
Task.Factory.StartNew(async () => {
|
||||
Environment.Exit(0);
|
||||
|
||||
//attaching the logger
|
||||
Logger.LogLevel = LogLevel.Verbose;
|
||||
Logger.OnNewLogMessage((date, msg) => {
|
||||
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}");
|
||||
});
|
||||
} else if (line == "clear") {
|
||||
|
||||
//setting up a new PLC interface and register collection
|
||||
MewtocolInterface interf = new MewtocolInterface("10.237.191.3");
|
||||
TestRegisters registers = new TestRegisters();
|
||||
Console.Clear();
|
||||
|
||||
//attaching the register collection and an automatic poller
|
||||
interf.WithRegisterCollection(registers).WithPoller();
|
||||
} else if (int.TryParse(line, out var lineNum)) {
|
||||
|
||||
await interf.ConnectAsync(
|
||||
(plcinf) => {
|
||||
var index = Math.Clamp(lineNum - 1, 0, invokeableMethods.Count - 1);
|
||||
|
||||
//reading a value from the register collection
|
||||
Console.WriteLine($"BitValue is: {registers.BitValue}");
|
||||
Console.WriteLine($"TestEnum is: {registers.TestEnum}");
|
||||
var task = (Task)invokeableMethods.ElementAt(index).Invoke(ExampleSzenarios, null);
|
||||
|
||||
//writing a value to the registers
|
||||
Task.Factory.StartNew(async () => {
|
||||
task.Wait();
|
||||
|
||||
//set plc to run mode if not already
|
||||
await interf.SetOperationMode(OPMode.Run);
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("The program ran to completition");
|
||||
Console.ResetColor();
|
||||
|
||||
} else {
|
||||
|
||||
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));
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
Console.WriteLine("Wrong input");
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
15
Examples/ScenarioAttribute.cs
Normal file
15
Examples/ScenarioAttribute.cs
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
106
Examples/TestRegistersEnumBitwise.cs
Normal file
106
Examples/TestRegistersEnumBitwise.cs
Normal 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; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
@@ -14,15 +16,69 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
public partial class MewtocolInterface {
|
||||
|
||||
/// <summary>
|
||||
/// True if the auto poller is currently paused
|
||||
/// </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 bool ContinousReaderRunning;
|
||||
|
||||
internal volatile bool pollerTaskRunning;
|
||||
internal volatile bool pollerTaskStopped;
|
||||
internal volatile bool pollerIsPaused;
|
||||
internal volatile bool pollerFirstCycle = false;
|
||||
|
||||
internal bool usePoller = false;
|
||||
|
||||
#region Register Polling
|
||||
|
||||
/// <summary>
|
||||
/// Kills the poller completely
|
||||
/// </summary>
|
||||
internal void KillPoller () {
|
||||
|
||||
ContinousReaderRunning = false;
|
||||
pollerTaskRunning = false;
|
||||
pollerTaskStopped = true;
|
||||
|
||||
ClearRegisterVals();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses the polling and waits for the last message to be sent
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task PausePollingAsync () {
|
||||
|
||||
if (!pollerTaskRunning)
|
||||
return;
|
||||
|
||||
pollerTaskRunning = false;
|
||||
|
||||
while (!pollerIsPaused) {
|
||||
|
||||
if (pollerIsPaused)
|
||||
break;
|
||||
|
||||
await Task.Delay(10);
|
||||
|
||||
}
|
||||
|
||||
pollerTaskRunning = false;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resumes the polling
|
||||
/// </summary>
|
||||
public void ResumePolling () {
|
||||
|
||||
pollerTaskRunning = true;
|
||||
|
||||
}
|
||||
|
||||
@@ -31,32 +87,39 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
internal void AttachPoller () {
|
||||
|
||||
if (ContinousReaderRunning)
|
||||
if (pollerTaskRunning)
|
||||
return;
|
||||
|
||||
pollerFirstCycle = true;
|
||||
|
||||
Task.Factory.StartNew(async () => {
|
||||
|
||||
Logger.Log("Poller is attaching", LogLevel.Info, this);
|
||||
|
||||
int it = 0;
|
||||
ContinousReaderRunning = true;
|
||||
int iteration = 0;
|
||||
|
||||
while (ContinousReaderRunning) {
|
||||
pollerTaskStopped = false;
|
||||
pollerTaskRunning = true;
|
||||
pollerIsPaused = false;
|
||||
|
||||
if (it >= Registers.Count + 1) {
|
||||
it = 0;
|
||||
while (!pollerTaskStopped) {
|
||||
|
||||
while (pollerTaskRunning) {
|
||||
|
||||
if (iteration >= Registers.Count + 1) {
|
||||
iteration = 0;
|
||||
//invoke cycle polled event
|
||||
InvokePolledCycleDone();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (it >= Registers.Count) {
|
||||
if (iteration >= Registers.Count) {
|
||||
await GetPLCInfoAsync();
|
||||
it++;
|
||||
iteration++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var reg = Registers[it];
|
||||
var reg = Registers[iteration];
|
||||
|
||||
if (reg is NRegister<short> shortReg) {
|
||||
var lastVal = shortReg.Value;
|
||||
@@ -115,14 +178,29 @@ namespace MewtocolNet {
|
||||
}
|
||||
}
|
||||
|
||||
it++;
|
||||
iteration++;
|
||||
pollerFirstCycle = false;
|
||||
|
||||
await Task.Delay(pollerDelayMs);
|
||||
|
||||
}
|
||||
|
||||
pollerIsPaused = !pollerTaskRunning;
|
||||
|
||||
}
|
||||
|
||||
pollerIsPaused = false;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
internal void PropertyRegisterWasSet (string propName, object value) {
|
||||
|
||||
SetRegister(propName, value);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Register Adding
|
||||
@@ -144,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) {
|
||||
@@ -173,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);
|
||||
|
||||
}
|
||||
@@ -258,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);
|
||||
@@ -289,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);
|
||||
|
||||
@@ -297,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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -346,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);
|
||||
|
||||
@@ -357,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
|
||||
@@ -373,7 +462,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// Gets a list of all added registers
|
||||
/// </summary>
|
||||
public List<Register> GetAllRegisters () {
|
||||
public List<IRegister> GetAllRegisters () {
|
||||
|
||||
return Registers;
|
||||
|
||||
@@ -383,7 +472,7 @@ namespace MewtocolNet {
|
||||
|
||||
#region Event Invoking
|
||||
|
||||
internal void InvokeRegisterChanged (Register reg) {
|
||||
internal void InvokeRegisterChanged (IRegister reg) {
|
||||
|
||||
RegisterChanged?.Invoke(reg);
|
||||
|
||||
|
||||
101
MewtocolNet/Mewtocol/IRegister.cs
Normal file
101
MewtocolNet/Mewtocol/IRegister.cs
Normal 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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -52,6 +55,26 @@ namespace MewtocolNet {
|
||||
set { connectTimeout = value; }
|
||||
}
|
||||
|
||||
private volatile int pollerDelayMs = 0;
|
||||
/// <summary>
|
||||
/// Delay for each poller cycle in milliseconds, default = 0
|
||||
/// </summary>
|
||||
public int PollerDelayMs {
|
||||
get => pollerDelayMs;
|
||||
set {
|
||||
pollerDelayMs = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PollerDelayMs)));
|
||||
}
|
||||
}
|
||||
|
||||
private volatile int queuedMessages;
|
||||
/// <summary>
|
||||
/// Currently queued Messages
|
||||
/// </summary>
|
||||
public int QueuedMessages {
|
||||
get => queuedMessages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The host ip endpoint, leave it null to use an automatic interface
|
||||
/// </summary>
|
||||
@@ -94,13 +117,16 @@ 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;
|
||||
private int stationNumber;
|
||||
private int cycleTimeMs = 25;
|
||||
|
||||
private int bytesTotalCountedUpstream = 0;
|
||||
private int bytesTotalCountedDownstream = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The current IP of the PLC connection
|
||||
/// </summary>
|
||||
@@ -125,6 +151,30 @@ namespace MewtocolNet {
|
||||
}
|
||||
}
|
||||
|
||||
private int bytesPerSecondUpstream = 0;
|
||||
/// <summary>
|
||||
/// The current transmission speed in bytes per second
|
||||
/// </summary>
|
||||
public int BytesPerSecondUpstream {
|
||||
get { return bytesPerSecondUpstream; }
|
||||
private set {
|
||||
bytesPerSecondUpstream = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BytesPerSecondUpstream)));
|
||||
}
|
||||
}
|
||||
|
||||
private int bytesPerSecondDownstream = 0;
|
||||
/// <summary>
|
||||
/// The current transmission speed in bytes per second
|
||||
/// </summary>
|
||||
public int BytesPerSecondDownstream {
|
||||
get { return bytesPerSecondDownstream; }
|
||||
private set {
|
||||
bytesPerSecondDownstream = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BytesPerSecondDownstream)));
|
||||
}
|
||||
}
|
||||
|
||||
internal NetworkStream stream;
|
||||
internal TcpClient client;
|
||||
internal readonly SerialQueue queue = new SerialQueue();
|
||||
@@ -132,6 +182,9 @@ namespace MewtocolNet {
|
||||
internal int SendExceptionsInRow = 0;
|
||||
internal bool ImportantTaskRunning = false;
|
||||
|
||||
private Stopwatch speedStopwatchUpstr;
|
||||
private Stopwatch speedStopwatchDownstr;
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <summary>
|
||||
@@ -159,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}) " : "")}" +
|
||||
@@ -345,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
|
||||
@@ -381,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -425,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -444,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) {
|
||||
@@ -496,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -682,8 +718,13 @@ namespace MewtocolNet {
|
||||
//send request
|
||||
try {
|
||||
|
||||
queuedMessages++;
|
||||
|
||||
var response = await queue.Enqueue(() => SendSingleBlock(_msg));
|
||||
|
||||
if (queuedMessages > 0)
|
||||
queuedMessages--;
|
||||
|
||||
if (response == null) {
|
||||
return new CommandResult {
|
||||
Success = false,
|
||||
@@ -736,6 +777,16 @@ namespace MewtocolNet {
|
||||
|
||||
var message = _blockString.ToHexASCIIBytes();
|
||||
|
||||
//time measuring
|
||||
if(speedStopwatchUpstr == null) {
|
||||
speedStopwatchUpstr = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
if(speedStopwatchUpstr.Elapsed.TotalSeconds >= 1) {
|
||||
speedStopwatchUpstr.Restart();
|
||||
bytesTotalCountedUpstream = 0;
|
||||
}
|
||||
|
||||
//send request
|
||||
using (var sendStream = new MemoryStream(message)) {
|
||||
await sendStream.CopyToAsync(stream);
|
||||
@@ -743,6 +794,13 @@ namespace MewtocolNet {
|
||||
Logger.Log($"--> OUT MSG: {_blockString}", LogLevel.Critical, this);
|
||||
}
|
||||
|
||||
//calc upstream speed
|
||||
bytesTotalCountedUpstream += message.Length;
|
||||
|
||||
var perSecUpstream = (double)((bytesTotalCountedUpstream / speedStopwatchUpstr.Elapsed.TotalMilliseconds) * 1000);
|
||||
if (perSecUpstream <= 10000)
|
||||
BytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
|
||||
|
||||
//await result
|
||||
StringBuilder response = new StringBuilder();
|
||||
try {
|
||||
@@ -755,6 +813,17 @@ namespace MewtocolNet {
|
||||
while (!endLineCode && !startMsgCode) {
|
||||
|
||||
do {
|
||||
|
||||
//time measuring
|
||||
if (speedStopwatchDownstr == null) {
|
||||
speedStopwatchDownstr = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
if (speedStopwatchDownstr.Elapsed.TotalSeconds >= 1) {
|
||||
speedStopwatchDownstr.Restart();
|
||||
bytesTotalCountedDownstream = 0;
|
||||
}
|
||||
|
||||
int bytes = await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
|
||||
|
||||
endLineCode = responseBuffer.Any(x => x == 0x0D);
|
||||
@@ -777,8 +846,18 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
if(!string.IsNullOrEmpty(response.ToString())) {
|
||||
|
||||
Logger.Log($"<-- IN MSG: {response}", LogLevel.Critical, this);
|
||||
|
||||
bytesTotalCountedDownstream += Encoding.ASCII.GetByteCount(response.ToString());
|
||||
|
||||
var perSecDownstream = (double)((bytesTotalCountedDownstream / speedStopwatchDownstr.Elapsed.TotalMilliseconds) * 1000);
|
||||
|
||||
if(perSecUpstream <= 10000)
|
||||
BytesPerSecondDownstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
|
||||
|
||||
return response.ToString();
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -112,8 +112,9 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
/// <param name="start">Start adress</param>
|
||||
/// <param name="count">Number of bytes to get</param>
|
||||
/// <param name="onProgress">Gets invoked when the progress changes, contains the progress as a double</param>
|
||||
/// <returns>A byte array or null of there was an error</returns>
|
||||
public async Task<byte[]> ReadByteRange (int start, int count) {
|
||||
public async Task<byte[]> ReadByteRange (int start, int count, Action<double> onProgress = null) {
|
||||
|
||||
var byteList = new List<byte>();
|
||||
|
||||
@@ -146,6 +147,9 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
if(onProgress != null)
|
||||
onProgress((double)i / wordLength);
|
||||
|
||||
}
|
||||
|
||||
return byteList.ToArray();
|
||||
@@ -162,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) {
|
||||
@@ -194,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);
|
||||
|
||||
@@ -216,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> {
|
||||
@@ -331,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);
|
||||
|
||||
@@ -358,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());
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()}";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()}";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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}";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()}";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageId>MewtocolNet</PackageId>
|
||||
<Version>0.5.2</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>
|
||||
|
||||
@@ -8,47 +8,6 @@ namespace MewtocolNet.Queue {
|
||||
readonly object _locker = new object();
|
||||
readonly WeakReference<Task> _lastTask = new WeakReference<Task>(null);
|
||||
|
||||
internal Task Enqueue (Action action) {
|
||||
return Enqueue<bool>(() => {
|
||||
action();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
internal Task<T> Enqueue<T> (Func<T> function) {
|
||||
lock (_locker) {
|
||||
Task lastTask;
|
||||
Task<T> resultTask;
|
||||
|
||||
if (_lastTask.TryGetTarget(out lastTask)) {
|
||||
resultTask = lastTask.ContinueWith(_ => function(), TaskContinuationOptions.ExecuteSynchronously);
|
||||
} else {
|
||||
resultTask = Task.Run(function);
|
||||
}
|
||||
|
||||
_lastTask.SetTarget(resultTask);
|
||||
|
||||
return resultTask;
|
||||
}
|
||||
}
|
||||
|
||||
internal Task Enqueue (Func<Task> asyncAction) {
|
||||
lock (_locker) {
|
||||
Task lastTask;
|
||||
Task resultTask;
|
||||
|
||||
if (_lastTask.TryGetTarget(out lastTask)) {
|
||||
resultTask = lastTask.ContinueWith(_ => asyncAction(), TaskContinuationOptions.ExecuteSynchronously).Unwrap();
|
||||
} else {
|
||||
resultTask = Task.Run(asyncAction);
|
||||
}
|
||||
|
||||
_lastTask.SetTarget(resultTask);
|
||||
|
||||
return resultTask;
|
||||
}
|
||||
}
|
||||
|
||||
internal Task<T> Enqueue<T> (Func<Task<T>> asyncFunction) {
|
||||
lock (_locker) {
|
||||
Task lastTask;
|
||||
|
||||
306
MewtocolTests/AutomatedPropertyRegisters.cs
Normal file
306
MewtocolTests/AutomatedPropertyRegisters.cs
Normal 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");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
32
MewtocolTests/MewtocolTests.csproj
Normal file
32
MewtocolTests/MewtocolTests.csproj
Normal 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>
|
||||
114
MewtocolTests/TestLivePLC.cs
Normal file
114
MewtocolTests/TestLivePLC.cs
Normal 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; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
164
MewtocolTests/TestRegisterInterface.cs
Normal file
164
MewtocolTests/TestRegisterInterface.cs
Normal 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
BIN
PLC_Test/test_c30_fpx_h.ini
Normal file
Binary file not shown.
BIN
PLC_Test/test_c30_fpx_h.pro
Normal file
BIN
PLC_Test/test_c30_fpx_h.pro
Normal file
Binary file not shown.
11
PLC_Test/test_c30_fpx_h.xml
Normal file
11
PLC_Test/test_c30_fpx_h.xml
Normal 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>
|
||||
39
README.md
39
README.md
@@ -1,7 +1,6 @@
|
||||
[](https://github.com/WOmed/MewtocolNet/actions/workflows/dotnet-windows.yml)
|
||||

|
||||

|
||||

|
||||
[](https://github.com/WOmed/MewtocolNet/actions/workflows/build-pipeline.yml)
|
||||
[](https://www.nuget.org/packages/Mewtocol.NET)
|
||||
[](https://codecov.io/gh/WOmed/MewtocolNet)
|
||||

|
||||

|
||||
|
||||
@@ -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.0" />
|
||||
<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
|
||||
|
||||
Reference in New Issue
Block a user