mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Added new conversions
- added compile target for net6
This commit is contained in:
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
|||||||
using MewtocolNet.Registers;
|
using MewtocolNet.Registers;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
namespace Examples;
|
namespace Examples;
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ public class ExampleScenarios {
|
|||||||
public void SetupLogger () {
|
public void SetupLogger () {
|
||||||
|
|
||||||
//attaching the logger
|
//attaching the logger
|
||||||
Logger.LogLevel = LogLevel.Info;
|
Logger.LogLevel = LogLevel.Error;
|
||||||
Logger.OnNewLogMessage((date, level, msg) => {
|
Logger.OnNewLogMessage((date, level, msg) => {
|
||||||
|
|
||||||
if (level == LogLevel.Error) Console.ForegroundColor = ConsoleColor.Red;
|
if (level == LogLevel.Error) Console.ForegroundColor = ConsoleColor.Red;
|
||||||
@@ -46,6 +47,7 @@ public class ExampleScenarios {
|
|||||||
interf.WithRegisterCollection(registers).WithPoller();
|
interf.WithRegisterCollection(registers).WithPoller();
|
||||||
|
|
||||||
await interf.ConnectAsync();
|
await interf.ConnectAsync();
|
||||||
|
await interf.AwaitFirstDataAsync();
|
||||||
|
|
||||||
_ = Task.Factory.StartNew(async () => {
|
_ = Task.Factory.StartNew(async () => {
|
||||||
|
|
||||||
@@ -54,22 +56,18 @@ public class ExampleScenarios {
|
|||||||
//flip the bool register each tick and wait for it to be registered
|
//flip the bool register each tick and wait for it to be registered
|
||||||
//await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1);
|
//await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1);
|
||||||
|
|
||||||
Console.Title = $"Polling Paused: {interf.PollingPaused}, " +
|
Console.Title =
|
||||||
$"Poller active: {interf.PollerActive}, " +
|
|
||||||
$"Speed UP: {interf.BytesPerSecondUpstream} B/s, " +
|
$"Speed UP: {interf.BytesPerSecondUpstream} B/s, " +
|
||||||
$"Speed DOWN: {interf.BytesPerSecondDownstream} B/s, " +
|
$"Speed DOWN: {interf.BytesPerSecondDownstream} B/s, " +
|
||||||
$"Poll delay: {interf.PollerDelayMs} ms, " +
|
$"Poll cycle: {interf.PollerCycleDurationMs} ms, " +
|
||||||
$"Queued MSGs: {interf.QueuedMessages}";
|
$"Queued MSGs: {interf.QueuedMessages}";
|
||||||
|
|
||||||
Console.Clear();
|
Console.Clear();
|
||||||
Console.WriteLine("Underlying registers on tick: \n");
|
Console.WriteLine("Underlying registers on tick: \n");
|
||||||
|
|
||||||
foreach (var register in interf.Registers) {
|
foreach (var register in interf.Registers)
|
||||||
|
|
||||||
Console.WriteLine($"{register.ToString(true)}");
|
Console.WriteLine($"{register.ToString(true)}");
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine($"{registers.TestBool1}");
|
Console.WriteLine($"{registers.TestBool1}");
|
||||||
Console.WriteLine($"{registers.TestDuplicate}");
|
Console.WriteLine($"{registers.TestDuplicate}");
|
||||||
|
|
||||||
@@ -192,12 +190,12 @@ public class ExampleScenarios {
|
|||||||
public async Task RunReadTest () {
|
public async Task RunReadTest () {
|
||||||
|
|
||||||
//setting up a new PLC interface and register collection
|
//setting up a new PLC interface and register collection
|
||||||
MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
|
MewtocolInterface interf = new MewtocolInterface("192.168.115.210").WithPoller();
|
||||||
|
|
||||||
//auto add all built registers to the interface
|
//auto add all built registers to the interface
|
||||||
var builder = RegBuilder.ForInterface(interf);
|
var builder = RegBuilder.ForInterface(interf);
|
||||||
var r0reg = builder.FromPlcRegName("R0").Build();
|
var r0reg = builder.FromPlcRegName("R0").Build();
|
||||||
builder.FromPlcRegName("R1").Build();
|
builder.FromPlcRegName("R1", "Testname").Build();
|
||||||
builder.FromPlcRegName("R1F").Build();
|
builder.FromPlcRegName("R1F").Build();
|
||||||
builder.FromPlcRegName("R101A").Build();
|
builder.FromPlcRegName("R101A").Build();
|
||||||
|
|
||||||
@@ -205,35 +203,57 @@ public class ExampleScenarios {
|
|||||||
builder.FromPlcRegName("DDT36").AsPlcType(PlcVarType.DINT).Build();
|
builder.FromPlcRegName("DDT36").AsPlcType(PlcVarType.DINT).Build();
|
||||||
builder.FromPlcRegName("DT200").AsBytes(30).Build();
|
builder.FromPlcRegName("DT200").AsBytes(30).Build();
|
||||||
|
|
||||||
builder.FromPlcRegName("DDT38").AsPlcType(PlcVarType.TIME).Build();
|
var timeReg = builder.FromPlcRegName("DDT38").AsPlcType(PlcVarType.TIME).Build();
|
||||||
//builder.FromPlcRegName("DT40").AsPlcType(PlcVarType.STRING).Build();
|
var stringReg = builder.FromPlcRegName("DT40").AsPlcType(PlcVarType.STRING).Build();
|
||||||
|
|
||||||
//connect
|
//connect
|
||||||
await interf.ConnectAsync();
|
await interf.ConnectAsync();
|
||||||
|
|
||||||
//var res = await interf.SendCommandAsync("%01#RCSR000F");
|
//await first register data
|
||||||
|
await interf.AwaitFirstDataAsync();
|
||||||
|
|
||||||
while(true) {
|
_ = Task.Factory.StartNew(async () => {
|
||||||
|
|
||||||
await interf.SetRegisterAsync(r0reg, !(bool)r0reg.Value);
|
void setTitle () {
|
||||||
await interf.SetRegisterAsync(shortReg, (short)new Random().Next(0, 100));
|
|
||||||
|
|
||||||
var sw = Stopwatch.StartNew();
|
Console.Title =
|
||||||
|
$"Speed UP: {interf.BytesPerSecondUpstream} B/s, " +
|
||||||
foreach (var reg in interf.Registers) {
|
$"Speed DOWN: {interf.BytesPerSecondDownstream} B/s, " +
|
||||||
|
$"Poll cycle: {interf.PollerCycleDurationMs} ms, " +
|
||||||
await reg.ReadAsync();
|
$"Queued MSGs: {interf.QueuedMessages}";
|
||||||
|
|
||||||
Console.WriteLine($"Register {reg.ToString()}");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (interf.IsConnected) {
|
||||||
|
setTitle();
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTitle();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
while (interf.IsConnected) {
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
//set bool
|
||||||
|
await r0reg.WriteAsync(!(bool)r0reg.Value);
|
||||||
|
|
||||||
|
//set random num
|
||||||
|
await shortReg.WriteAsync((short)new Random().Next(0, 100));
|
||||||
|
await stringReg.WriteAsync($"_{DateTime.Now.Second}s_");
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|
||||||
Console.WriteLine($"Total read time for registers: {sw.Elapsed.TotalMilliseconds:N0}ms");
|
foreach (var reg in interf.Registers)
|
||||||
|
Console.WriteLine(reg.ToString());
|
||||||
|
|
||||||
|
Console.WriteLine($"Total write time for registers: {sw.Elapsed.TotalMilliseconds:N0}ms");
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
|
//await Task.Delay(new Random().Next(0, 10000));
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -244,28 +264,24 @@ public class ExampleScenarios {
|
|||||||
public async Task MultiFrameTest() {
|
public async Task MultiFrameTest() {
|
||||||
|
|
||||||
var preLogLevel = Logger.LogLevel;
|
var preLogLevel = Logger.LogLevel;
|
||||||
Logger.LogLevel = LogLevel.Error;
|
Logger.LogLevel = LogLevel.Critical;
|
||||||
|
|
||||||
//setting up a new PLC interface and register collection
|
//setting up a new PLC interface and register collection
|
||||||
MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
|
MewtocolInterface interf = new MewtocolInterface("192.168.115.210") {
|
||||||
|
ConnectTimeout = 3000,
|
||||||
|
};
|
||||||
|
|
||||||
//auto add all built registers to the interface
|
//auto add all built registers to the interface
|
||||||
var builder = RegBuilder.ForInterface(interf);
|
var builder = RegBuilder.ForInterface(interf);
|
||||||
var r0reg = builder.FromPlcRegName("R0").Build();
|
var r0reg = builder.FromPlcRegName("R0").Build();
|
||||||
builder.FromPlcRegName("R1").Build();
|
builder.FromPlcRegName("R1").Build();
|
||||||
builder.FromPlcRegName("R1F").Build();
|
builder.FromPlcRegName("DT0").AsBytes(100).Build();
|
||||||
builder.FromPlcRegName("R60A").Build();
|
|
||||||
builder.FromPlcRegName("R61A").Build();
|
for (int i = 1; i < 100; i++) {
|
||||||
builder.FromPlcRegName("R62A").Build();
|
|
||||||
builder.FromPlcRegName("R63A").Build();
|
builder.FromPlcRegName($"R{i}A").Build();
|
||||||
builder.FromPlcRegName("R64A").Build();
|
|
||||||
builder.FromPlcRegName("R65A").Build();
|
}
|
||||||
builder.FromPlcRegName("R66A").Build();
|
|
||||||
builder.FromPlcRegName("R67A").Build();
|
|
||||||
builder.FromPlcRegName("R68A").Build();
|
|
||||||
builder.FromPlcRegName("R69A").Build();
|
|
||||||
builder.FromPlcRegName("R70A").Build();
|
|
||||||
builder.FromPlcRegName("R71A").Build();
|
|
||||||
|
|
||||||
//connect
|
//connect
|
||||||
await interf.ConnectAsync();
|
await interf.ConnectAsync();
|
||||||
@@ -273,36 +289,18 @@ public class ExampleScenarios {
|
|||||||
Console.WriteLine("Poller cycle started");
|
Console.WriteLine("Poller cycle started");
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
await interf.RunPollerCylceManual(false);
|
int cmdCount = await interf.RunPollerCylceManual();
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|
||||||
Console.WriteLine("Poller cycle finished");
|
Console.WriteLine("Poller cycle finished");
|
||||||
|
|
||||||
Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms");
|
Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms for {cmdCount} commands");
|
||||||
|
|
||||||
interf.Disconnect();
|
interf.Disconnect();
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
await interf.ConnectAsync();
|
|
||||||
|
|
||||||
sw = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
Console.WriteLine("Poller cycle started");
|
|
||||||
|
|
||||||
await interf.RunPollerCylceManual(true);
|
|
||||||
|
|
||||||
sw.Stop();
|
|
||||||
|
|
||||||
Console.WriteLine("Poller cycle finished");
|
|
||||||
|
|
||||||
Console.WriteLine($"Multi frame excec time: {sw.ElapsedMilliseconds:N0}ms");
|
|
||||||
|
|
||||||
Logger.LogLevel = preLogLevel;
|
|
||||||
|
|
||||||
await Task.Delay(10000);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using MewtocolNet.Registers;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -19,25 +20,32 @@ namespace MewtocolNet
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MewtocolInterface {
|
public partial class MewtocolInterface {
|
||||||
|
|
||||||
/// <summary>
|
internal event Action PolledCycle;
|
||||||
/// True if the auto poller is currently paused
|
|
||||||
/// </summary>
|
internal volatile bool pollerTaskStopped = true;
|
||||||
public bool PollingPaused => pollerIsPaused;
|
internal volatile bool pollerFirstCycle;
|
||||||
|
|
||||||
|
internal bool usePoller = false;
|
||||||
|
|
||||||
|
private int tcpMessagesSentThisCycle = 0;
|
||||||
|
|
||||||
|
private int pollerCycleDurationMs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the poller is actvice (can be paused)
|
/// True if the poller is actvice (can be paused)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool PollerActive => !pollerTaskStopped;
|
public bool PollerActive => !pollerTaskStopped;
|
||||||
|
|
||||||
internal event Action PolledCycle;
|
/// <summary>
|
||||||
|
/// Current poller cycle duration
|
||||||
internal volatile bool pollerTaskRunning;
|
/// </summary>
|
||||||
internal volatile bool pollerTaskStopped;
|
public int PollerCycleDurationMs {
|
||||||
internal volatile bool pollerIsPaused;
|
get => pollerCycleDurationMs;
|
||||||
internal volatile bool pollerFirstCycle = false;
|
private set {
|
||||||
|
pollerCycleDurationMs = value;
|
||||||
internal bool usePoller = false;
|
OnPropChange();
|
||||||
internal bool pollerUseMultiFrame = false;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region Register Polling
|
#region Register Polling
|
||||||
|
|
||||||
@@ -46,54 +54,20 @@ namespace MewtocolNet
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal void KillPoller() {
|
internal void KillPoller() {
|
||||||
|
|
||||||
pollerTaskRunning = false;
|
|
||||||
pollerTaskStopped = true;
|
pollerTaskStopped = true;
|
||||||
|
|
||||||
ClearRegisterVals();
|
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attaches a continous reader that reads back the Registers and Contacts
|
/// Attaches a continous reader that reads back the Registers and Contacts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void AttachPoller() {
|
internal void AttachPoller() {
|
||||||
|
|
||||||
if (pollerTaskRunning)
|
if (!pollerTaskStopped)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
PollerCycleDurationMs = 0;
|
||||||
pollerFirstCycle = true;
|
pollerFirstCycle = true;
|
||||||
|
|
||||||
Task.Run(Poll);
|
Task.Run(Poll);
|
||||||
@@ -104,14 +78,18 @@ namespace MewtocolNet
|
|||||||
/// Runs a single poller cycle manually,
|
/// Runs a single poller cycle manually,
|
||||||
/// useful if you want to use a custom update frequency
|
/// useful if you want to use a custom update frequency
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns>The number of inidvidual mewtocol commands sent</returns>
|
||||||
public async Task RunPollerCylceManual (bool useMultiFrame = false) {
|
public async Task<int> RunPollerCylceManual () {
|
||||||
|
|
||||||
|
if (!pollerTaskStopped)
|
||||||
|
throw new NotSupportedException($"The poller is already running, " +
|
||||||
|
$"please make sure there is no polling active before calling {nameof(RunPollerCylceManual)}");
|
||||||
|
|
||||||
|
tcpMessagesSentThisCycle = 0;
|
||||||
|
|
||||||
if (useMultiFrame) {
|
|
||||||
await OnMultiFrameCycle();
|
await OnMultiFrameCycle();
|
||||||
} else {
|
|
||||||
await OnSingleFrameCycle();
|
return tcpMessagesSentThisCycle;
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,78 +98,49 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
Logger.Log("Poller is attaching", LogLevel.Info, this);
|
Logger.Log("Poller is attaching", LogLevel.Info, this);
|
||||||
|
|
||||||
int iteration = 0;
|
|
||||||
|
|
||||||
pollerTaskStopped = false;
|
pollerTaskStopped = false;
|
||||||
pollerTaskRunning = true;
|
|
||||||
pollerIsPaused = false;
|
|
||||||
|
|
||||||
while (!pollerTaskStopped) {
|
while (!pollerTaskStopped) {
|
||||||
|
|
||||||
if (iteration >= Registers.Count + 1) {
|
tcpMessagesSentThisCycle = 0;
|
||||||
iteration = 0;
|
|
||||||
//invoke cycle polled event
|
|
||||||
InvokePolledCycleDone();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pollerUseMultiFrame) {
|
|
||||||
await OnMultiFrameCycle();
|
await OnMultiFrameCycle();
|
||||||
} else {
|
|
||||||
await OnSingleFrameCycle();
|
if(!IsConnected) {
|
||||||
|
pollerTaskStopped = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pollerFirstCycle = false;
|
pollerFirstCycle = false;
|
||||||
|
InvokePolledCycleDone();
|
||||||
iteration++;
|
|
||||||
|
|
||||||
pollerIsPaused = !pollerTaskRunning;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pollerIsPaused = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnSingleFrameCycle () {
|
|
||||||
|
|
||||||
foreach (var reg in Registers) {
|
|
||||||
|
|
||||||
if (reg.IsAllowedRegisterGenericType()) {
|
|
||||||
|
|
||||||
var lastVal = reg.Value;
|
|
||||||
|
|
||||||
var rwReg = (IRegisterInternal)reg;
|
|
||||||
|
|
||||||
var readout = await rwReg.ReadAsync();
|
|
||||||
|
|
||||||
if (lastVal != readout) {
|
|
||||||
|
|
||||||
rwReg.SetValueFromPLC(readout);
|
|
||||||
InvokeRegisterChanged(reg);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
await GetPLCInfoAsync();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnMultiFrameCycle () {
|
private async Task OnMultiFrameCycle () {
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
await UpdateRCPRegisters();
|
await UpdateRCPRegisters();
|
||||||
|
|
||||||
|
await UpdateDTRegisters();
|
||||||
|
|
||||||
await GetPLCInfoAsync();
|
await GetPLCInfoAsync();
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
PollerCycleDurationMs = (int)sw.ElapsedMilliseconds;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Smart register polling methods
|
||||||
|
|
||||||
private async Task UpdateRCPRegisters () {
|
private async Task UpdateRCPRegisters () {
|
||||||
|
|
||||||
//build booleans
|
//build booleans
|
||||||
var rcpList = Registers.Where(x => x.GetType() == typeof(BoolRegister))
|
var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister))
|
||||||
.Select(x => (BoolRegister)x)
|
.Select(x => (BoolRegister)x)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
@@ -216,6 +165,8 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
string rcpRequest = rcpString.ToString();
|
string rcpRequest = rcpString.ToString();
|
||||||
var result = await SendCommandAsync(rcpRequest);
|
var result = await SendCommandAsync(rcpRequest);
|
||||||
|
if (!result.Success) return;
|
||||||
|
|
||||||
var resultBitArray = result.Response.ParseRCMultiBit();
|
var resultBitArray = result.Response.ParseRCMultiBit();
|
||||||
|
|
||||||
for (int k = 0; k < resultBitArray.Length; k++) {
|
for (int k = 0; k < resultBitArray.Length; k++) {
|
||||||
@@ -233,9 +184,27 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void PropertyRegisterWasSet(string propName, object value) {
|
private async Task UpdateDTRegisters () {
|
||||||
|
|
||||||
_ = SetRegisterAsync(GetRegister(propName), value);
|
foreach (var reg in RegistersUnderlying) {
|
||||||
|
|
||||||
|
var type = reg.GetType();
|
||||||
|
|
||||||
|
if(reg.RegisterType.IsNumericDTDDT() || reg.RegisterType == RegisterType.DT_BYTE_RANGE) {
|
||||||
|
|
||||||
|
var lastVal = reg.Value;
|
||||||
|
var rwReg = (IRegisterInternal)reg;
|
||||||
|
var readout = await rwReg.ReadAsync();
|
||||||
|
if (readout == null) return;
|
||||||
|
|
||||||
|
if (lastVal != readout) {
|
||||||
|
rwReg.SetValueFromPLC(readout);
|
||||||
|
InvokeRegisterChanged(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,7 +378,7 @@ namespace MewtocolNet
|
|||||||
throw MewtocolException.DupeNameRegister(builtRegister);
|
throw MewtocolException.DupeNameRegister(builtRegister);
|
||||||
|
|
||||||
builtRegister.attachedInterface = this;
|
builtRegister.attachedInterface = this;
|
||||||
Registers.Add(builtRegister);
|
RegistersUnderlying.Add(builtRegister);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,7 +391,7 @@ namespace MewtocolNet
|
|||||||
throw MewtocolException.DupeNameRegister(register);
|
throw MewtocolException.DupeNameRegister(register);
|
||||||
|
|
||||||
register.attachedInterface = this;
|
register.attachedInterface = this;
|
||||||
Registers.Add(register);
|
RegistersUnderlying.Add(register);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +427,7 @@ namespace MewtocolNet
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public IRegister GetRegister(string name) {
|
public IRegister GetRegister(string name) {
|
||||||
|
|
||||||
return Registers.FirstOrDefault(x => x.Name == name);
|
return RegistersUnderlying.FirstOrDefault(x => x.Name == name);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,7 +440,7 @@ namespace MewtocolNet
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<IRegister> GetAllRegisters() {
|
public IEnumerable<IRegister> GetAllRegisters() {
|
||||||
|
|
||||||
return Registers.Cast<IRegister>();
|
return RegistersUnderlying.Cast<IRegister>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,6 +448,12 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
#region Event Invoking
|
#region Event Invoking
|
||||||
|
|
||||||
|
internal void PropertyRegisterWasSet(string propName, object value) {
|
||||||
|
|
||||||
|
_ = SetRegisterAsync(GetRegister(propName), value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
internal void InvokeRegisterChanged(IRegister reg) {
|
internal void InvokeRegisterChanged(IRegister reg) {
|
||||||
|
|
||||||
RegisterChanged?.Invoke(reg);
|
RegisterChanged?.Invoke(reg);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MewtocolNet {
|
namespace MewtocolNet {
|
||||||
@@ -38,6 +39,12 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
int MemoryAddress { get; }
|
int MemoryAddress { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the register as the plc representation string
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
string GetAsPLC();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a readable string with all important register informations
|
/// Builds a readable string with all important register informations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -49,16 +56,17 @@ namespace MewtocolNet {
|
|||||||
string ToString(bool detailed);
|
string ToString(bool detailed);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the register value in the plc async
|
/// Reads the register value async from the plc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if successful</returns>
|
/// <returns>The register value</returns>
|
||||||
Task<bool> SetValueAsync();
|
Task<object> ReadAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the register value from the plc async
|
/// Writes the register content async to the plc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The value or null if failed</returns>
|
/// <returns>True if successfully set</returns>
|
||||||
Task<object> GetValueAsync();
|
Task<bool> WriteAsync(object data);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
MewtocolNet/MewtocolFrameResponse.cs
Normal file
46
MewtocolNet/MewtocolFrameResponse.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
public struct MewtocolFrameResponse {
|
||||||
|
|
||||||
|
public bool Success { get; private set; }
|
||||||
|
|
||||||
|
public string Response { get; private set; }
|
||||||
|
|
||||||
|
public int ErrorCode { get; private set; }
|
||||||
|
|
||||||
|
public string Error { get; private set; }
|
||||||
|
|
||||||
|
public MewtocolFrameResponse (string response) {
|
||||||
|
|
||||||
|
Success = true;
|
||||||
|
ErrorCode = 0;
|
||||||
|
Response = response;
|
||||||
|
Error = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public MewtocolFrameResponse(int errorCode) {
|
||||||
|
|
||||||
|
Success = false;
|
||||||
|
Response = null;
|
||||||
|
ErrorCode = errorCode;
|
||||||
|
Error = CodeDescriptions.Error[errorCode];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public MewtocolFrameResponse(int errorCode, string exceptionMsg) {
|
||||||
|
|
||||||
|
Success = false;
|
||||||
|
Response = null;
|
||||||
|
ErrorCode = errorCode;
|
||||||
|
Error = exceptionMsg;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,6 +13,37 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class MewtocolHelpers {
|
public static class MewtocolHelpers {
|
||||||
|
|
||||||
|
#region Value PLC Humanizers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the TimeSpan as a PLC representation string fe.
|
||||||
|
/// <code>
|
||||||
|
/// T#1h10m30s20ms
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timespan"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string AsPLCTime (this TimeSpan timespan) {
|
||||||
|
|
||||||
|
if (timespan == null || timespan == TimeSpan.Zero)
|
||||||
|
return $"T#0s";
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder("T#");
|
||||||
|
|
||||||
|
int millis = timespan.Milliseconds;
|
||||||
|
int seconds = timespan.Seconds;
|
||||||
|
int minutes = timespan.Minutes;
|
||||||
|
int hours = timespan.Hours;
|
||||||
|
|
||||||
|
if (hours > 0) sb.Append($"{hours}h");
|
||||||
|
if (minutes > 0) sb.Append($"{minutes}m");
|
||||||
|
if (seconds > 0) sb.Append($"{seconds}s");
|
||||||
|
if (millis > 0) sb.Append($"{millis}ms");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Turns a bit array into a 0 and 1 string
|
/// Turns a bit array into a 0 and 1 string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -24,6 +55,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Byte and string operation helpers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a string (after converting to upper case) to ascii bytes
|
/// Converts a string (after converting to upper case) to ascii bytes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -35,7 +70,12 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string BuildBCCFrame(this string asciiArr) {
|
/// <summary>
|
||||||
|
/// Builds the BCC / Checksum for the mewtocol command
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asciiArr">The mewtocol command (%01#RCS0001)</param>
|
||||||
|
/// <returns>The mewtocol command with the appended checksum</returns>
|
||||||
|
public static string BuildBCCFrame(this string asciiArr) {
|
||||||
|
|
||||||
Encoding ae = Encoding.ASCII;
|
Encoding ae = Encoding.ASCII;
|
||||||
byte[] b = ae.GetBytes(asciiArr);
|
byte[] b = ae.GetBytes(asciiArr);
|
||||||
@@ -63,8 +103,13 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a return message as RCS single bit
|
||||||
|
/// </summary>
|
||||||
internal static bool? ParseRCSingleBit(this string _onString) {
|
internal static bool? ParseRCSingleBit(this string _onString) {
|
||||||
|
|
||||||
|
_onString = _onString.Replace("\r", "");
|
||||||
|
|
||||||
var res = new Regex(@"\%([0-9]{2})\$RC(.)").Match(_onString);
|
var res = new Regex(@"\%([0-9]{2})\$RC(.)").Match(_onString);
|
||||||
if (res.Success) {
|
if (res.Success) {
|
||||||
string val = res.Groups[2].Value;
|
string val = res.Groups[2].Value;
|
||||||
@@ -74,8 +119,13 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a return message as RCS multiple bits
|
||||||
|
/// </summary>
|
||||||
internal static BitArray ParseRCMultiBit(this string _onString) {
|
internal static BitArray ParseRCMultiBit(this string _onString) {
|
||||||
|
|
||||||
|
_onString = _onString.Replace("\r", "");
|
||||||
|
|
||||||
var res = new Regex(@"\%([0-9]{2})\$RC(?<bits>(?:0|1){0,8})(..)").Match(_onString);
|
var res = new Regex(@"\%([0-9]{2})\$RC(?<bits>(?:0|1){0,8})(..)").Match(_onString);
|
||||||
if (res.Success) {
|
if (res.Success) {
|
||||||
|
|
||||||
@@ -124,7 +174,7 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Splits a string in even parts
|
/// Splits a string into even parts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static IEnumerable<string> SplitInParts(this string s, int partLength) {
|
internal static IEnumerable<string> SplitInParts(this string s, int partLength) {
|
||||||
|
|
||||||
@@ -138,9 +188,11 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a hex string (AB01C1) to a byte array
|
||||||
|
/// </summary>
|
||||||
internal static byte[] HexStringToByteArray (this string hex) {
|
internal static byte[] HexStringToByteArray (this string hex) {
|
||||||
if (hex == null)
|
if (hex == null) return null;
|
||||||
return null;
|
|
||||||
return Enumerable.Range(0, hex.Length)
|
return Enumerable.Range(0, hex.Length)
|
||||||
.Where(x => x % 2 == 0)
|
.Where(x => x % 2 == 0)
|
||||||
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
|
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
|
||||||
@@ -166,24 +218,9 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string AsPLC (this TimeSpan timespan) {
|
/// <summary>
|
||||||
|
/// Switches byte order from mixed to big endian
|
||||||
StringBuilder sb = new StringBuilder("T#");
|
/// </summary>
|
||||||
|
|
||||||
int millis = timespan.Milliseconds;
|
|
||||||
int seconds = timespan.Seconds;
|
|
||||||
int minutes = timespan.Minutes;
|
|
||||||
int hours = timespan.Hours;
|
|
||||||
|
|
||||||
if (hours > 0) sb.Append($"{hours}h");
|
|
||||||
if (minutes > 0) sb.Append($"{minutes}m");
|
|
||||||
if (seconds > 0) sb.Append($"{seconds}s");
|
|
||||||
if (millis > 0) sb.Append($"{millis}ms");
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static byte[] BigToMixedEndian(this byte[] arr) {
|
internal static byte[] BigToMixedEndian(this byte[] arr) {
|
||||||
|
|
||||||
List<byte> oldBL = new List<byte>(arr);
|
List<byte> oldBL = new List<byte>(arr);
|
||||||
@@ -206,6 +243,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Comparerers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the register type is boolean
|
/// Checks if the register type is boolean
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -249,6 +290,8 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,11 +7,13 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.Design;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -52,18 +54,6 @@ namespace MewtocolNet {
|
|||||||
set { connectTimeout = value; }
|
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;
|
private volatile int queuedMessages;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Currently queued Messages
|
/// Currently queued Messages
|
||||||
@@ -114,9 +104,11 @@ namespace MewtocolNet {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The registered data registers of the PLC
|
/// The registered data registers of the PLC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<BaseRegister> Registers { get; private set; } = new List<BaseRegister>();
|
internal List<BaseRegister> RegistersUnderlying { get; private set; } = new List<BaseRegister>();
|
||||||
|
|
||||||
internal IEnumerable<IRegisterInternal> RegistersInternal => Registers.Cast<IRegisterInternal>();
|
public IEnumerable<IRegister> Registers => RegistersUnderlying.Cast<IRegister>();
|
||||||
|
|
||||||
|
internal IEnumerable<IRegisterInternal> RegistersInternal => RegistersUnderlying.Cast<IRegisterInternal>();
|
||||||
|
|
||||||
private string ip;
|
private string ip;
|
||||||
private int port;
|
private int port;
|
||||||
@@ -184,6 +176,8 @@ namespace MewtocolNet {
|
|||||||
private Stopwatch speedStopwatchUpstr;
|
private Stopwatch speedStopwatchUpstr;
|
||||||
private Stopwatch speedStopwatchDownstr;
|
private Stopwatch speedStopwatchDownstr;
|
||||||
|
|
||||||
|
private Task firstPollTask = new Task(() => { });
|
||||||
|
|
||||||
#region Initialization
|
#region Initialization
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -238,6 +232,8 @@ namespace MewtocolNet {
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<MewtocolInterface> ConnectAsync(Action<PLCInfo> OnConnected = null, Action OnFailed = null) {
|
public async Task<MewtocolInterface> ConnectAsync(Action<PLCInfo> OnConnected = null, Action OnFailed = null) {
|
||||||
|
|
||||||
|
firstPollTask = new Task(() => { });
|
||||||
|
|
||||||
Logger.Log("Connecting to PLC...", LogLevel.Info, this);
|
Logger.Log("Connecting to PLC...", LogLevel.Info, this);
|
||||||
|
|
||||||
var plcinf = await GetPLCInfoAsync();
|
var plcinf = await GetPLCInfoAsync();
|
||||||
@@ -249,18 +245,19 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
Connected?.Invoke(plcinf);
|
Connected?.Invoke(plcinf);
|
||||||
|
|
||||||
if (OnConnected != null) {
|
|
||||||
|
|
||||||
if (!usePoller) {
|
if (!usePoller) {
|
||||||
OnConnected(plcinf);
|
if (OnConnected != null) OnConnected(plcinf);
|
||||||
|
firstPollTask.RunSynchronously();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PolledCycle += OnPollCycleDone;
|
PolledCycle += OnPollCycleDone;
|
||||||
void OnPollCycleDone() {
|
void OnPollCycleDone() {
|
||||||
OnConnected(plcinf);
|
|
||||||
|
if (OnConnected != null) OnConnected(plcinf);
|
||||||
|
firstPollTask.RunSynchronously();
|
||||||
PolledCycle -= OnPollCycleDone;
|
PolledCycle -= OnPollCycleDone;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -268,6 +265,7 @@ namespace MewtocolNet {
|
|||||||
if (OnFailed != null) {
|
if (OnFailed != null) {
|
||||||
OnFailed();
|
OnFailed();
|
||||||
Disconnected?.Invoke();
|
Disconnected?.Invoke();
|
||||||
|
firstPollTask.RunSynchronously();
|
||||||
Logger.Log("Initial connection failed", LogLevel.Info, this);
|
Logger.Log("Initial connection failed", LogLevel.Info, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +275,12 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use this to await the first poll iteration after connecting,
|
||||||
|
/// This also completes if the initial connection fails
|
||||||
|
/// </summary>
|
||||||
|
public async Task AwaitFirstDataAsync () => await firstPollTask;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changes the connections parameters of the PLC, only applyable when the connection is offline
|
/// Changes the connections parameters of the PLC, only applyable when the connection is offline
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -310,10 +314,9 @@ namespace MewtocolNet {
|
|||||||
/// Attaches a poller to the interface that continously
|
/// Attaches a poller to the interface that continously
|
||||||
/// polls the registered data registers and writes the values to them
|
/// polls the registered data registers and writes the values to them
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MewtocolInterface WithPoller(bool useMultiFrame = false) {
|
public MewtocolInterface WithPoller() {
|
||||||
|
|
||||||
usePoller = true;
|
usePoller = true;
|
||||||
pollerUseMultiFrame = useMultiFrame;
|
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -375,41 +378,6 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMajorSocketExceptionWhileConnecting() {
|
|
||||||
|
|
||||||
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
|
|
||||||
CycleTimeMs = 0;
|
|
||||||
IsConnected = false;
|
|
||||||
KillPoller();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMajorSocketExceptionWhileConnected() {
|
|
||||||
|
|
||||||
if (IsConnected) {
|
|
||||||
|
|
||||||
Logger.Log("The PLC connection was closed", LogLevel.Error, this);
|
|
||||||
CycleTimeMs = 0;
|
|
||||||
IsConnected = false;
|
|
||||||
Disconnected?.Invoke();
|
|
||||||
KillPoller();
|
|
||||||
client.Close();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearRegisterVals() {
|
|
||||||
|
|
||||||
for (int i = 0; i < Registers.Count; i++) {
|
|
||||||
|
|
||||||
var reg = (IRegisterInternal)Registers[i];
|
|
||||||
reg.ClearValue();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Low level command handling
|
#region Low level command handling
|
||||||
@@ -418,91 +386,26 @@ namespace MewtocolNet {
|
|||||||
/// Calculates the checksum automatically and sends a command to the PLC then awaits results
|
/// Calculates the checksum automatically and sends a command to the PLC then awaits results
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
|
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
|
||||||
|
/// <param name="withTerminator">Append the checksum and bcc automatically</param>
|
||||||
/// <returns>Returns the result</returns>
|
/// <returns>Returns the result</returns>
|
||||||
public async Task<CommandResult> SendCommandAsync(string _msg) {
|
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true) {
|
||||||
|
|
||||||
_msg = _msg.BuildBCCFrame();
|
|
||||||
_msg += "\r";
|
|
||||||
|
|
||||||
//send request
|
//send request
|
||||||
try {
|
|
||||||
|
|
||||||
queuedMessages++;
|
queuedMessages++;
|
||||||
|
var tempResponse = await queue.Enqueue(() => SendFrameAsync(_msg, withTerminator, withTerminator));
|
||||||
|
|
||||||
TCPMessageResult tcpResult = TCPMessageResult.Waiting;
|
tcpMessagesSentThisCycle++;
|
||||||
string response = "";
|
|
||||||
|
|
||||||
int lineFeedFails = 0;
|
|
||||||
|
|
||||||
//recursively try to get a response on failed line feeds
|
|
||||||
while (tcpResult == TCPMessageResult.Waiting || tcpResult == TCPMessageResult.FailedLineFeed) {
|
|
||||||
|
|
||||||
if (lineFeedFails >= 5)
|
|
||||||
throw new MewtocolException($"The message ${_msg} had {lineFeedFails} linefeed fails");
|
|
||||||
|
|
||||||
var tempResponse = await queue.Enqueue(() => SendSingleBlock(_msg));
|
|
||||||
tcpResult = tempResponse.Item1;
|
|
||||||
response = tempResponse.Item2;
|
|
||||||
|
|
||||||
if(tcpResult == TCPMessageResult.FailedLineFeed) {
|
|
||||||
lineFeedFails++;
|
|
||||||
Logger.Log($"Linefeed fail, retrying...", LogLevel.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queuedMessages > 0)
|
|
||||||
queuedMessages--;
|
queuedMessages--;
|
||||||
|
|
||||||
if (tcpResult == TCPMessageResult.FailedWithException)
|
return tempResponse;
|
||||||
throw new MewtocolException("The connection to the device was terminated");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//error catching
|
private async Task<MewtocolFrameResponse> SendFrameAsync (string frame, bool useBcc = true, bool useCr = true) {
|
||||||
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
|
|
||||||
Match m = errorcheck.Match(response);
|
|
||||||
|
|
||||||
if (m.Success) {
|
try {
|
||||||
string eCode = m.Groups[1].Value;
|
|
||||||
string eDes = CodeDescriptions.Error[Convert.ToInt32(eCode)];
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
|
||||||
Console.WriteLine($"Response is: {response}");
|
|
||||||
Logger.Log($"Error on command {_msg.Replace("\r", "")} the PLC returned error code: {eCode}, {eDes}", LogLevel.Error);
|
|
||||||
Console.ResetColor();
|
|
||||||
return new CommandResult {
|
|
||||||
Success = false,
|
|
||||||
Error = eCode,
|
|
||||||
ErrorDescription = eDes
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CommandResult {
|
//stop time
|
||||||
Success = true,
|
|
||||||
Error = "0000",
|
|
||||||
Response = response,
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
return new CommandResult {
|
|
||||||
Success = false,
|
|
||||||
Error = "0000",
|
|
||||||
ErrorDescription = "null result"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<(TCPMessageResult, string)> SendSingleBlock(string _blockString) {
|
|
||||||
|
|
||||||
if (client == null || !client.Connected) {
|
|
||||||
await ConnectTCP();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client == null || !client.Connected)
|
|
||||||
return (TCPMessageResult.NotConnected, null);
|
|
||||||
|
|
||||||
var message = _blockString.BytesFromHexASCIIString();
|
|
||||||
|
|
||||||
//time measuring
|
|
||||||
if (speedStopwatchUpstr == null) {
|
if (speedStopwatchUpstr == null) {
|
||||||
speedStopwatchUpstr = Stopwatch.StartNew();
|
speedStopwatchUpstr = Stopwatch.StartNew();
|
||||||
}
|
}
|
||||||
@@ -512,32 +415,41 @@ namespace MewtocolNet {
|
|||||||
bytesTotalCountedUpstream = 0;
|
bytesTotalCountedUpstream = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//send request
|
const char CR = '\r';
|
||||||
using (var sendStream = new MemoryStream(message)) {
|
const char DELIMITER = '&';
|
||||||
await sendStream.CopyToAsync(stream);
|
|
||||||
Logger.Log($"[--------------------------------]", LogLevel.Critical, this);
|
if (client == null || !client.Connected) await ConnectTCP();
|
||||||
Logger.Log($"--> OUT MSG: {_blockString}", LogLevel.Critical, this);
|
|
||||||
}
|
if (useBcc)
|
||||||
|
frame = $"{frame.BuildBCCFrame()}";
|
||||||
|
|
||||||
|
if (useCr)
|
||||||
|
frame = $"{frame}\r";
|
||||||
|
|
||||||
|
//write inital command
|
||||||
|
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
||||||
|
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
||||||
|
|
||||||
//calc upstream speed
|
//calc upstream speed
|
||||||
bytesTotalCountedUpstream += message.Length;
|
bytesTotalCountedUpstream += writeBuffer.Length;
|
||||||
|
|
||||||
var perSecUpstream = (double)((bytesTotalCountedUpstream / speedStopwatchUpstr.Elapsed.TotalMilliseconds) * 1000);
|
var perSecUpstream = (double)((bytesTotalCountedUpstream / speedStopwatchUpstr.Elapsed.TotalMilliseconds) * 1000);
|
||||||
if (perSecUpstream <= 10000)
|
if (perSecUpstream <= 10000)
|
||||||
BytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
|
BytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
//await result
|
|
||||||
StringBuilder response = new StringBuilder();
|
|
||||||
try {
|
|
||||||
|
|
||||||
byte[] responseBuffer = new byte[128 * 16];
|
Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this);
|
||||||
|
Logger.Log($"--> OUT MSG: {frame.Replace("\r", "(CR)")}", LogLevel.Critical, this);
|
||||||
|
|
||||||
bool endLineCode = false;
|
//read
|
||||||
bool startMsgCode = false;
|
List<byte> totalResponse = new List<byte>();
|
||||||
|
byte[] responseBuffer = new byte[512];
|
||||||
|
|
||||||
while (!endLineCode && !startMsgCode) {
|
bool wasMultiFramedResponse = false;
|
||||||
|
CommandState cmdState = CommandState.Intial;
|
||||||
|
|
||||||
do {
|
//read until command complete
|
||||||
|
while (cmdState != CommandState.Complete) {
|
||||||
|
|
||||||
//time measuring
|
//time measuring
|
||||||
if (speedStopwatchDownstr == null) {
|
if (speedStopwatchDownstr == null) {
|
||||||
@@ -549,55 +461,146 @@ namespace MewtocolNet {
|
|||||||
bytesTotalCountedDownstream = 0;
|
bytesTotalCountedDownstream = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bytes = await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
|
responseBuffer = new byte[128];
|
||||||
|
|
||||||
endLineCode = responseBuffer.Any(x => x == 0x0D);
|
await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
|
||||||
startMsgCode = responseBuffer.Count(x => x == 0x25) > 1;
|
|
||||||
|
|
||||||
if (!endLineCode && !startMsgCode) break;
|
bool terminatorReceived = responseBuffer.Any(x => x == (byte)CR);
|
||||||
|
var delimiterTerminatorIdx = SearchBytePattern(responseBuffer, new byte[] { (byte)DELIMITER, (byte)CR });
|
||||||
|
|
||||||
response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes));
|
if (terminatorReceived && delimiterTerminatorIdx == -1) {
|
||||||
|
cmdState = CommandState.Complete;
|
||||||
|
} else if (delimiterTerminatorIdx != -1) {
|
||||||
|
cmdState = CommandState.RequestedNextFrame;
|
||||||
|
} else {
|
||||||
|
cmdState = CommandState.LineFeed;
|
||||||
}
|
}
|
||||||
while (stream.DataAvailable);
|
|
||||||
|
//log message parts
|
||||||
|
var tempMsg = Encoding.UTF8.GetString(responseBuffer).Replace("\r", "(CR)");
|
||||||
|
Logger.Log($">> IN PART: {tempMsg}, Command state: {cmdState}", LogLevel.Critical, this);
|
||||||
|
|
||||||
|
//error response
|
||||||
|
int errorCode = CheckForErrorMsg(tempMsg);
|
||||||
|
if (errorCode != 0) return new MewtocolFrameResponse(errorCode);
|
||||||
|
|
||||||
|
//add complete response to collector without empty bytes
|
||||||
|
totalResponse.AddRange(responseBuffer.Where(x => x != (byte)0x0));
|
||||||
|
|
||||||
|
//request next part of the command if the delimiter was received
|
||||||
|
if (cmdState == CommandState.RequestedNextFrame) {
|
||||||
|
|
||||||
|
Logger.Log($"Requesting next frame...", LogLevel.Critical, this);
|
||||||
|
|
||||||
|
wasMultiFramedResponse = true;
|
||||||
|
writeBuffer = Encoding.UTF8.GetBytes("%01**&\r");
|
||||||
|
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException) {
|
|
||||||
OnMajorSocketExceptionWhileConnected();
|
|
||||||
return (TCPMessageResult.FailedWithException, null);
|
|
||||||
} catch (SocketException) {
|
|
||||||
OnMajorSocketExceptionWhileConnected();
|
|
||||||
return (TCPMessageResult.FailedWithException, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//build final result
|
||||||
|
string resString = Encoding.UTF8.GetString(totalResponse.ToArray());
|
||||||
|
|
||||||
string resString = response.ToString();
|
if (wasMultiFramedResponse) {
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(resString) && resString != "\r" ) {
|
var split = resString.Split('&');
|
||||||
|
|
||||||
Logger.Log($"<-- IN MSG: {response}", LogLevel.Critical, this);
|
for (int j = 0; j < split.Length; j++) {
|
||||||
|
|
||||||
bytesTotalCountedDownstream += Encoding.ASCII.GetByteCount(response.ToString());
|
split[j] = split[j].Replace("\r", "");
|
||||||
|
split[j] = split[j].Substring(0, split[j].Length - 2);
|
||||||
|
if (j > 0) split[j] = split[j].Replace($"%{GetStationNumber()}", "");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
resString = string.Join("", split);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesTotalCountedDownstream += Encoding.ASCII.GetByteCount(resString);
|
||||||
|
|
||||||
var perSecDownstream = (double)((bytesTotalCountedDownstream / speedStopwatchDownstr.Elapsed.TotalMilliseconds) * 1000);
|
var perSecDownstream = (double)((bytesTotalCountedDownstream / speedStopwatchDownstr.Elapsed.TotalMilliseconds) * 1000);
|
||||||
|
|
||||||
if (perSecUpstream <= 10000)
|
if (perSecUpstream <= 10000)
|
||||||
BytesPerSecondDownstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
|
BytesPerSecondDownstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
return (TCPMessageResult.Success, resString);
|
Logger.Log($"<-- IN MSG: {resString.Replace("\r", "(CR)")}", LogLevel.Critical, this);
|
||||||
|
Logger.Log($"Total bytes parsed: {resString.Length}", LogLevel.Critical, this);
|
||||||
|
Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this);
|
||||||
|
|
||||||
} else {
|
return new MewtocolFrameResponse(resString);
|
||||||
|
|
||||||
return (TCPMessageResult.FailedLineFeed, null);
|
} catch (Exception ex) {
|
||||||
|
|
||||||
|
return new MewtocolFrameResponse(400, ex.Message.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int CheckForErrorMsg (string msg) {
|
||||||
|
|
||||||
|
//error catching
|
||||||
|
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
|
||||||
|
Match m = errorcheck.Match(msg);
|
||||||
|
|
||||||
|
if (m.Success) {
|
||||||
|
|
||||||
|
string eCode = m.Groups[1].Value;
|
||||||
|
return Convert.ToInt32(eCode);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SearchBytePattern (byte[] src, byte[] pattern) {
|
||||||
|
|
||||||
|
int maxFirstCharSlot = src.Length - pattern.Length + 1;
|
||||||
|
for (int i = 0; i < maxFirstCharSlot; i++) {
|
||||||
|
if (src[i] != pattern[0]) // compare only first byte
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// found a match on first byte, now try to match rest of the pattern
|
||||||
|
for (int j = pattern.Length - 1; j >= 1; j--) {
|
||||||
|
if (src[i + j] != pattern[j]) break;
|
||||||
|
if (j == 1) return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Disposing
|
#region Disposing
|
||||||
|
|
||||||
|
private void OnMajorSocketExceptionWhileConnecting() {
|
||||||
|
|
||||||
|
if (IsConnected) {
|
||||||
|
|
||||||
|
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
|
||||||
|
OnDisconnect();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMajorSocketExceptionWhileConnected() {
|
||||||
|
|
||||||
|
if (IsConnected) {
|
||||||
|
|
||||||
|
Logger.Log("The PLC connection was closed", LogLevel.Error, this);
|
||||||
|
OnDisconnect();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes the current interface and clears all its members
|
/// Disposes the current interface and clears all its members
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -607,12 +610,43 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
Disconnect();
|
Disconnect();
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
//GC.SuppressFinalize(this);
|
||||||
|
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDisconnect () {
|
||||||
|
|
||||||
|
if (IsConnected) {
|
||||||
|
|
||||||
|
BytesPerSecondDownstream = 0;
|
||||||
|
BytesPerSecondUpstream = 0;
|
||||||
|
CycleTimeMs = 0;
|
||||||
|
|
||||||
|
IsConnected = false;
|
||||||
|
ClearRegisterVals();
|
||||||
|
|
||||||
|
Disconnected?.Invoke();
|
||||||
|
KillPoller();
|
||||||
|
client.Close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void ClearRegisterVals() {
|
||||||
|
|
||||||
|
for (int i = 0; i < RegistersUnderlying.Count; i++) {
|
||||||
|
|
||||||
|
var reg = (IRegisterInternal)RegistersUnderlying[i];
|
||||||
|
reg.ClearValue();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Accessing Info
|
#region Accessing Info
|
||||||
@@ -628,6 +662,20 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Property change evnts
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers a property changed event
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyName">Name of the property to trigger for</param>
|
||||||
|
private void OnPropChange ([CallerMemberName]string propertyName = null) {
|
||||||
|
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,9 @@ namespace MewtocolNet {
|
|||||||
return retInfo;
|
return retInfo;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -89,7 +91,7 @@ namespace MewtocolNet {
|
|||||||
/// /// <param name="start">start address of the array</param>
|
/// /// <param name="start">start address of the array</param>
|
||||||
/// <param name="byteArr"></param>
|
/// <param name="byteArr"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<bool> WriteByteRange(int start, byte[] byteArr) {
|
public async Task<bool> WriteByteRange (int start, byte[] byteArr) {
|
||||||
|
|
||||||
string byteString = byteArr.BigToMixedEndian().ToHexString();
|
string byteString = byteArr.BigToMixedEndian().ToHexString();
|
||||||
var wordLength = byteArr.Length / 2;
|
var wordLength = byteArr.Length / 2;
|
||||||
@@ -111,9 +113,10 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="start">Start adress</param>
|
/// <param name="start">Start adress</param>
|
||||||
/// <param name="count">Number of bytes to get</param>
|
/// <param name="count">Number of bytes to get</param>
|
||||||
|
/// <param name="flipBytes">Flips bytes from big to mixed endian</param>
|
||||||
/// <param name="onProgress">Gets invoked when the progress changes, contains the progress as a double</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>
|
/// <returns>A byte array or null of there was an error</returns>
|
||||||
public async Task<byte[]> ReadByteRange(int start, int count, Action<double> onProgress = null) {
|
public async Task<byte[]> ReadByteRange(int start, int count, bool flipBytes = true, Action<double> onProgress = null) {
|
||||||
|
|
||||||
var byteList = new List<byte>();
|
var byteList = new List<byte>();
|
||||||
|
|
||||||
@@ -138,11 +141,13 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
var bytes = result.Response.ParseDTByteString(8 * 4).HexStringToByteArray();
|
var bytes = result.Response.ParseDTByteString(8 * 4).HexStringToByteArray();
|
||||||
|
|
||||||
if (bytes == null) {
|
if (bytes == null) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (flipBytes) {
|
||||||
byteList.AddRange(bytes.BigToMixedEndian().Take(count).ToArray());
|
byteList.AddRange(bytes.BigToMixedEndian().Take(count).ToArray());
|
||||||
|
} else {
|
||||||
|
byteList.AddRange(bytes.Take(count).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +173,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}";
|
string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}";
|
||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
if (!result.Success) return null;
|
||||||
|
|
||||||
var resultBool = result.Response.ParseRCSingleBit();
|
var resultBool = result.Response.ParseRCSingleBit();
|
||||||
if (resultBool != null) {
|
if (resultBool != null) {
|
||||||
@@ -183,9 +189,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
|
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
|
||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
if (!result.Success) return null;
|
||||||
if (!result.Success)
|
|
||||||
throw new Exception($"Failed to load the byte data for: {_toRead}");
|
|
||||||
|
|
||||||
if(_toRead.RegisterType == RegisterType.DT) {
|
if(_toRead.RegisterType == RegisterType.DT) {
|
||||||
|
|
||||||
@@ -204,9 +208,19 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
|
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
|
||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
if (!result.Success) return null;
|
||||||
|
|
||||||
if (!result.Success)
|
var resBytes = result.Response.ParseDTRawStringAsBytes();
|
||||||
throw new Exception($"Failed to load the byte data for: {_toRead}");
|
|
||||||
|
return resBytes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toreadType == typeof(StringRegister)) {
|
||||||
|
|
||||||
|
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
|
||||||
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
if (!result.Success) return null;
|
||||||
|
|
||||||
var resBytes = result.Response.ParseDTRawStringAsBytes();
|
var resBytes = result.Response.ParseDTRawStringAsBytes();
|
||||||
|
|
||||||
@@ -220,8 +234,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
internal async Task<bool> WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) {
|
internal async Task<bool> WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) {
|
||||||
|
|
||||||
|
var toWriteType = _toWrite.GetType();
|
||||||
|
|
||||||
//returns a byte array 1 long and with the byte beeing 0 or 1
|
//returns a byte array 1 long and with the byte beeing 0 or 1
|
||||||
if (_toWrite.GetType() == typeof(BoolRegister)) {
|
if (toWriteType == typeof(BoolRegister)) {
|
||||||
|
|
||||||
string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}";
|
string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}";
|
||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
@@ -229,8 +245,8 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//returns a byte array 2 bytes or 4 bytes long depending on the data size
|
//writes a byte array 2 bytes or 4 bytes long depending on the data size
|
||||||
if (_toWrite.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) {
|
if (toWriteType.IsGenericType && toWriteType.GetGenericTypeDefinition() == typeof(NumberRegister<>)) {
|
||||||
|
|
||||||
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}";
|
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}";
|
||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
@@ -239,15 +255,18 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//returns a byte array with variable size
|
//returns a byte array with variable size
|
||||||
if (_toWrite.GetType() == typeof(BytesRegister)) {
|
if (toWriteType == typeof(BytesRegister)) {
|
||||||
|
|
||||||
//string stationNum = GetStationNumber();
|
throw new NotImplementedException("Not imp");
|
||||||
//string dataString = gotBytes.BuildDTString(_toWrite.ReservedSize);
|
|
||||||
//string dataArea = _toWrite.BuildCustomIdent(dataString.Length / 4);
|
|
||||||
|
|
||||||
//string requeststring = $"%{stationNum}#WD{dataArea}{dataString}";
|
}
|
||||||
|
|
||||||
//var result = await SendCommandAsync(requeststring);
|
//writes to the string area
|
||||||
|
if (toWriteType == typeof(StringRegister)) {
|
||||||
|
|
||||||
|
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}";
|
||||||
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
return result.Success;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +278,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#region Register reading / writing
|
#region Register reading / writing
|
||||||
|
|
||||||
public async Task<bool> SetRegisterAsync (IRegister register, object value) {
|
internal async Task<bool> SetRegisterAsync (IRegister register, object value) {
|
||||||
|
|
||||||
var internalReg = (IRegisterInternal)register;
|
var internalReg = (IRegisterInternal)register;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>netstandard2.0;net6.0;</TargetFrameworks>
|
||||||
<PackageId>Mewtocol.NET</PackageId>
|
<PackageId>Mewtocol.NET</PackageId>
|
||||||
<Version>0.7.1</Version>
|
<Version>0.7.1</Version>
|
||||||
<Authors>Felix Weiss</Authors>
|
<Authors>Felix Weiss</Authors>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace MewtocolNet
|
namespace MewtocolNet {
|
||||||
{
|
|
||||||
|
|
||||||
internal struct RegisterBuildInfo {
|
internal struct RegisterBuildInfo {
|
||||||
|
|
||||||
@@ -24,24 +23,21 @@ namespace MewtocolNet
|
|||||||
Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType();
|
Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType();
|
||||||
|
|
||||||
bool isBytesRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister);
|
bool isBytesRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister);
|
||||||
|
bool isStringRegister = !registerClassType.IsGenericType && registerClassType == typeof(StringRegister);
|
||||||
|
|
||||||
if (regType.IsNumericDTDDT() && (dotnetCastType == typeof(bool))) {
|
if (regType.IsNumericDTDDT() && (dotnetCastType == typeof(bool))) {
|
||||||
|
|
||||||
//-------------------------------------------
|
//-------------------------------------------
|
||||||
//as numeric register with boolean bit target
|
//as numeric register with boolean bit target
|
||||||
//create a new bregister instance
|
//create a new bregister instance
|
||||||
var flags = BindingFlags.Public | BindingFlags.Instance;
|
var instance = new BytesRegister(memoryAddress, memorySizeBytes, name);
|
||||||
|
|
||||||
//int _adress, int _reservedByteSize, string _name = null
|
|
||||||
var parameters = new object[] { memoryAddress, memorySizeBytes, name };
|
|
||||||
var instance = (BaseRegister)Activator.CreateInstance(typeof(BytesRegister), flags, null, parameters, null);
|
|
||||||
|
|
||||||
if (collectionType != null)
|
if (collectionType != null)
|
||||||
instance.WithCollectionType(collectionType);
|
instance.WithCollectionType(collectionType);
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
|
|
||||||
} else if (regType.IsNumericDTDDT() && !isBytesRegister) {
|
} else if (regType.IsNumericDTDDT() && !isBytesRegister && !isStringRegister) {
|
||||||
|
|
||||||
//-------------------------------------------
|
//-------------------------------------------
|
||||||
//as numeric register
|
//as numeric register
|
||||||
@@ -66,12 +62,20 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
//-------------------------------------------
|
//-------------------------------------------
|
||||||
//as byte range register
|
//as byte range register
|
||||||
|
var instance = new BytesRegister(memoryAddress, memorySizeBytes, name);
|
||||||
|
|
||||||
//create a new bregister instance
|
if (collectionType != null)
|
||||||
var flags = BindingFlags.Public | BindingFlags.Instance;
|
instance.WithCollectionType(collectionType);
|
||||||
//int _adress, int _reservedSize, string _name = null
|
|
||||||
var parameters = new object[] { memoryAddress, memorySizeBytes, name };
|
return instance;
|
||||||
var instance = (BaseRegister)Activator.CreateInstance(typeof(BytesRegister), flags, null, parameters, null);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStringRegister) {
|
||||||
|
|
||||||
|
//-------------------------------------------
|
||||||
|
//as byte range register
|
||||||
|
var instance = (BaseRegister)new StringRegister(memoryAddress, name);
|
||||||
|
|
||||||
if (collectionType != null)
|
if (collectionType != null)
|
||||||
instance.WithCollectionType(collectionType);
|
instance.WithCollectionType(collectionType);
|
||||||
@@ -89,10 +93,7 @@ namespace MewtocolNet
|
|||||||
var spAddr = specialAddress;
|
var spAddr = specialAddress;
|
||||||
var areaAddr = memoryAddress;
|
var areaAddr = memoryAddress;
|
||||||
|
|
||||||
//create a new bregister instance
|
var instance = new BoolRegister(io, spAddr.Value, areaAddr, name);
|
||||||
var flags = BindingFlags.Public | BindingFlags.Instance;
|
|
||||||
var parameters = new object[] { io, spAddr.Value, areaAddr, name };
|
|
||||||
var instance = (BoolRegister)Activator.CreateInstance(typeof(BoolRegister), flags, null, parameters, null);
|
|
||||||
|
|
||||||
if (collectionType != null)
|
if (collectionType != null)
|
||||||
((IRegisterInternal)instance).WithCollectionType(collectionType);
|
((IRegisterInternal)instance).WithCollectionType(collectionType);
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace MewtocolNet.RegisterBuilding
|
namespace MewtocolNet.RegisterBuilding {
|
||||||
{
|
|
||||||
|
|
||||||
public static class FinalizerExtensions {
|
public static class FinalizerExtensions {
|
||||||
|
|
||||||
public static IRegister Build (this RegisterBuilderStep step) {
|
public static IRegister Build(this RegisterBuilderStep step) {
|
||||||
|
|
||||||
//if no casting method in builder was called => autocast the type from the RegisterType
|
//if no casting method in builder was called => autocast the type from the RegisterType
|
||||||
if (!step.wasCasted)
|
if (!step.wasCasted)
|
||||||
@@ -34,7 +33,7 @@ namespace MewtocolNet.RegisterBuilding
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GetFallbackDotnetType (this RegisterBuilderStep step) {
|
private static void GetFallbackDotnetType(this RegisterBuilderStep step) {
|
||||||
|
|
||||||
bool isBoolean = step.RegType.IsBoolean();
|
bool isBoolean = step.RegType.IsBoolean();
|
||||||
bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null;
|
bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null;
|
||||||
@@ -66,7 +65,7 @@ namespace MewtocolNet.RegisterBuilding
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddToRegisterList (this RegisterBuilderStep step, BaseRegister instance) {
|
private static void AddToRegisterList(this RegisterBuilderStep step, BaseRegister instance) {
|
||||||
|
|
||||||
if (step.forInterface == null) return;
|
if (step.forInterface == null) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
namespace MewtocolNet.RegisterBuilding {
|
namespace MewtocolNet.RegisterBuilding {
|
||||||
|
|
||||||
internal struct ParseResult {
|
internal struct ParseResult {
|
||||||
|
|
||||||
public ParseResultState state;
|
public ParseResultState state;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -83,6 +82,8 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
public virtual string GetValueString() => Value?.ToString() ?? "null";
|
public virtual string GetValueString() => Value?.ToString() ?? "null";
|
||||||
|
|
||||||
|
public virtual string GetAsPLC () => Value?.ToString() ?? "null";
|
||||||
|
|
||||||
public virtual string GetRegisterString() => RegisterType.ToString();
|
public virtual string GetRegisterString() => RegisterType.ToString();
|
||||||
|
|
||||||
public virtual string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
|
public virtual string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
|
||||||
@@ -99,13 +100,9 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
public virtual async Task<bool> WriteAsync(object data) => throw new NotImplementedException();
|
public virtual async Task<bool> WriteAsync(object data) => throw new NotImplementedException();
|
||||||
|
|
||||||
public virtual Task<bool> SetValueAsync() => throw new NotImplementedException();
|
|
||||||
|
|
||||||
public virtual Task<object> GetValueAsync() => throw new NotImplementedException();
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
|
public override string ToString() => $"{GetRegisterPLCName()}{(Name != null ? $" ({Name})" : "")} - Value: {GetValueString()}";
|
||||||
|
|
||||||
public virtual string ToString(bool additional) {
|
public virtual string ToString(bool additional) {
|
||||||
|
|
||||||
|
|||||||
@@ -62,8 +62,11 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<object> ReadAsync() {
|
public override async Task<object> ReadAsync() {
|
||||||
|
|
||||||
|
if (!attachedInterface.IsConnected) return null;
|
||||||
|
|
||||||
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
||||||
var parsed = PlcValueParser.Parse<bool>(read);
|
if(read == null) return null;
|
||||||
|
var parsed = PlcValueParser.Parse<bool>(this, read);
|
||||||
|
|
||||||
SetValueFromPLC(parsed);
|
SetValueFromPLC(parsed);
|
||||||
return parsed;
|
return parsed;
|
||||||
@@ -73,7 +76,9 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<bool> WriteAsync(object data) {
|
public override async Task<bool> WriteAsync(object data) {
|
||||||
|
|
||||||
return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode((bool)data));
|
if (!attachedInterface.IsConnected) return false;
|
||||||
|
|
||||||
|
return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (bool)data));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MewtocolNet.Registers {
|
namespace MewtocolNet.Registers {
|
||||||
@@ -71,8 +72,12 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<object> ReadAsync() {
|
public override async Task<object> ReadAsync() {
|
||||||
|
|
||||||
|
if (!attachedInterface.IsConnected) return null;
|
||||||
|
|
||||||
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
||||||
var parsed = PlcValueParser.Parse<byte[]>(read);
|
if (read == null) return null;
|
||||||
|
|
||||||
|
var parsed = PlcValueParser.Parse<byte[]>(this, read);
|
||||||
|
|
||||||
SetValueFromPLC(parsed);
|
SetValueFromPLC(parsed);
|
||||||
return parsed;
|
return parsed;
|
||||||
@@ -82,6 +87,8 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<bool> WriteAsync(object data) {
|
public override async Task<bool> WriteAsync(object data) {
|
||||||
|
|
||||||
|
if (!attachedInterface.IsConnected) return false;
|
||||||
|
|
||||||
return await attachedInterface.WriteRawRegisterAsync(this, (byte[])data);
|
return await attachedInterface.WriteRawRegisterAsync(this, (byte[])data);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,12 +92,15 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string GetAsPLC() => ((TimeSpan)Value).AsPLCTime();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string GetValueString() {
|
public override string GetValueString() {
|
||||||
|
|
||||||
if(typeof(T) == typeof(TimeSpan)) {
|
if(typeof(T) == typeof(TimeSpan)) {
|
||||||
|
|
||||||
return $"{Value} [{((TimeSpan)Value).AsPLC()}]";
|
return $"{Value} [{((TimeSpan)Value).AsPLCTime()}]";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,8 +161,12 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<object> ReadAsync() {
|
public override async Task<object> ReadAsync() {
|
||||||
|
|
||||||
|
if (!attachedInterface.IsConnected) return null;
|
||||||
|
|
||||||
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
||||||
var parsed = PlcValueParser.Parse<T>(read);
|
if (read == null) return null;
|
||||||
|
|
||||||
|
var parsed = PlcValueParser.Parse<T>(this, read);
|
||||||
|
|
||||||
SetValueFromPLC(parsed);
|
SetValueFromPLC(parsed);
|
||||||
return parsed;
|
return parsed;
|
||||||
@@ -169,7 +176,9 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<bool> WriteAsync(object data) {
|
public override async Task<bool> WriteAsync(object data) {
|
||||||
|
|
||||||
return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode((T)data));
|
if (!attachedInterface.IsConnected) return false;
|
||||||
|
|
||||||
|
return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (T)data));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -11,32 +12,23 @@ namespace MewtocolNet.Registers {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class StringRegister : BaseRegister {
|
public class StringRegister : BaseRegister {
|
||||||
|
|
||||||
internal int addressLength;
|
|
||||||
/// <summary>
|
|
||||||
/// The rgisters memory length
|
|
||||||
/// </summary>
|
|
||||||
public int AddressLength => addressLength;
|
|
||||||
|
|
||||||
internal short ReservedSize { get; set; }
|
internal short ReservedSize { get; set; }
|
||||||
|
|
||||||
|
internal short UsedSize { get; set; }
|
||||||
|
|
||||||
|
internal int WordsSize { get; set; }
|
||||||
|
|
||||||
|
private bool isCalibrated = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a register containing a string
|
/// Defines a register containing a string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StringRegister (int _adress, int _reservedByteSize, string _name = null) {
|
public StringRegister (int _address, string _name = null) {
|
||||||
|
|
||||||
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
||||||
name = _name;
|
name = _name;
|
||||||
memoryAddress = _adress;
|
memoryAddress = _address;
|
||||||
ReservedSize = (short)_reservedByteSize;
|
|
||||||
|
|
||||||
//calc mem length
|
|
||||||
var wordsize = (double)_reservedByteSize / 2;
|
|
||||||
if (wordsize % 2 != 0) {
|
|
||||||
wordsize++;
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterType = RegisterType.DT_BYTE_RANGE;
|
RegisterType = RegisterType.DT_BYTE_RANGE;
|
||||||
addressLength = (int)Math.Round(wordsize + 1);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,15 +38,18 @@ namespace MewtocolNet.Registers {
|
|||||||
StringBuilder asciistring = new StringBuilder("D");
|
StringBuilder asciistring = new StringBuilder("D");
|
||||||
|
|
||||||
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
|
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
|
||||||
asciistring.Append((MemoryAddress + AddressLength).ToString().PadLeft(5, '0'));
|
asciistring.Append((MemoryAddress + WordsSize - 1).ToString().PadLeft(5, '0'));
|
||||||
|
|
||||||
return asciistring.ToString();
|
return asciistring.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string GetValueString() => $"'{Value}'";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void SetValueFromPLC (object val) {
|
public override void SetValueFromPLC (object val) {
|
||||||
|
|
||||||
lastValue = (byte[])val;
|
lastValue = (string)val;
|
||||||
|
|
||||||
TriggerChangedEvnt(this);
|
TriggerChangedEvnt(this);
|
||||||
TriggerNotifyChange();
|
TriggerNotifyChange();
|
||||||
@@ -65,20 +60,47 @@ namespace MewtocolNet.Registers {
|
|||||||
public override string GetRegisterString() => "DT";
|
public override string GetRegisterString() => "DT";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void ClearValue() => SetValueFromPLC(null);
|
public override void ClearValue() => SetValueFromPLC("");
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<object> ReadAsync() {
|
public override async Task<object> ReadAsync() {
|
||||||
|
|
||||||
|
if (!attachedInterface.IsConnected) return null;
|
||||||
|
|
||||||
|
//get the string params first
|
||||||
|
|
||||||
|
if(!isCalibrated) await Calibrate();
|
||||||
|
|
||||||
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
||||||
return PlcValueParser.Parse<byte[]>(read);
|
if (read == null) return null;
|
||||||
|
|
||||||
|
return PlcValueParser.Parse<string>(this, read);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Calibrate () {
|
||||||
|
|
||||||
|
//get the string describer bytes
|
||||||
|
var bytes = await attachedInterface.ReadByteRange(MemoryAddress, 4, false);
|
||||||
|
|
||||||
|
ReservedSize = BitConverter.ToInt16(bytes, 0);
|
||||||
|
UsedSize = BitConverter.ToInt16(bytes, 2);
|
||||||
|
WordsSize = 2 + (ReservedSize + 1) / 2;
|
||||||
|
|
||||||
|
isCalibrated = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<bool> WriteAsync(object data) {
|
public override async Task<bool> WriteAsync(object data) {
|
||||||
|
|
||||||
return await attachedInterface.WriteRawRegisterAsync(this, (byte[])data);
|
if (!attachedInterface.IsConnected) return false;
|
||||||
|
|
||||||
|
var res = await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (string)data));
|
||||||
|
|
||||||
|
if (res) UsedSize = (short)((string)Value).Length;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,13 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal enum CommandState {
|
||||||
|
|
||||||
|
Intial,
|
||||||
|
LineFeed,
|
||||||
|
RequestedNextFrame,
|
||||||
|
Complete
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace MewtocolNet.TypeConversion {
|
namespace MewtocolNet.TypeConversion {
|
||||||
@@ -32,11 +33,11 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
new PlcTypeConversion<bool>(RegisterType.R) {
|
new PlcTypeConversion<bool>(RegisterType.R) {
|
||||||
HoldingRegisterType = typeof(BoolRegister),
|
HoldingRegisterType = typeof(BoolRegister),
|
||||||
PlcVarType = PlcVarType.BOOL,
|
PlcVarType = PlcVarType.BOOL,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
return (bool)(bytes[0] == 1);
|
return (bool)(bytes[0] == 1);
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
return new byte[] { (byte)(value ? 1 : 0) };
|
return new byte[] { (byte)(value ? 1 : 0) };
|
||||||
|
|
||||||
@@ -45,11 +46,11 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
new PlcTypeConversion<bool>(RegisterType.X) {
|
new PlcTypeConversion<bool>(RegisterType.X) {
|
||||||
HoldingRegisterType = typeof(BoolRegister),
|
HoldingRegisterType = typeof(BoolRegister),
|
||||||
PlcVarType = PlcVarType.BOOL,
|
PlcVarType = PlcVarType.BOOL,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
return bytes[0] == 1;
|
return bytes[0] == 1;
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
return new byte[] { (byte)(value ? 1 : 0) };
|
return new byte[] { (byte)(value ? 1 : 0) };
|
||||||
|
|
||||||
@@ -58,11 +59,11 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
new PlcTypeConversion<bool>(RegisterType.Y) {
|
new PlcTypeConversion<bool>(RegisterType.Y) {
|
||||||
HoldingRegisterType = typeof(BoolRegister),
|
HoldingRegisterType = typeof(BoolRegister),
|
||||||
PlcVarType = PlcVarType.BOOL,
|
PlcVarType = PlcVarType.BOOL,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
return bytes[0] == 1;
|
return bytes[0] == 1;
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
return new byte[] { (byte)(value ? 1 : 0) };
|
return new byte[] { (byte)(value ? 1 : 0) };
|
||||||
|
|
||||||
@@ -71,11 +72,11 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
new PlcTypeConversion<short>(RegisterType.DT) {
|
new PlcTypeConversion<short>(RegisterType.DT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<short>),
|
HoldingRegisterType = typeof(NumberRegister<short>),
|
||||||
PlcVarType = PlcVarType.INT,
|
PlcVarType = PlcVarType.INT,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
return BitConverter.ToInt16(bytes, 0);
|
return BitConverter.ToInt16(bytes, 0);
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
return BitConverter.GetBytes(value);
|
return BitConverter.GetBytes(value);
|
||||||
|
|
||||||
@@ -84,11 +85,11 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
new PlcTypeConversion<ushort>(RegisterType.DT) {
|
new PlcTypeConversion<ushort>(RegisterType.DT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<ushort>),
|
HoldingRegisterType = typeof(NumberRegister<ushort>),
|
||||||
PlcVarType = PlcVarType.UINT,
|
PlcVarType = PlcVarType.UINT,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
return BitConverter.ToUInt16(bytes, 0);
|
return BitConverter.ToUInt16(bytes, 0);
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
return BitConverter.GetBytes(value);
|
return BitConverter.GetBytes(value);
|
||||||
|
|
||||||
@@ -97,11 +98,11 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
new PlcTypeConversion<int>(RegisterType.DDT) {
|
new PlcTypeConversion<int>(RegisterType.DDT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<int>),
|
HoldingRegisterType = typeof(NumberRegister<int>),
|
||||||
PlcVarType = PlcVarType.DINT,
|
PlcVarType = PlcVarType.DINT,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
return BitConverter.ToInt32(bytes, 0);
|
return BitConverter.ToInt32(bytes, 0);
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
return BitConverter.GetBytes(value);
|
return BitConverter.GetBytes(value);
|
||||||
|
|
||||||
@@ -110,11 +111,11 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
new PlcTypeConversion<uint>(RegisterType.DDT) {
|
new PlcTypeConversion<uint>(RegisterType.DDT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<uint>),
|
HoldingRegisterType = typeof(NumberRegister<uint>),
|
||||||
PlcVarType = PlcVarType.UDINT,
|
PlcVarType = PlcVarType.UDINT,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
return BitConverter.ToUInt32(bytes, 0);
|
return BitConverter.ToUInt32(bytes, 0);
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
return BitConverter.GetBytes(value);
|
return BitConverter.GetBytes(value);
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
new PlcTypeConversion<float>(RegisterType.DDT) {
|
new PlcTypeConversion<float>(RegisterType.DDT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<float>),
|
HoldingRegisterType = typeof(NumberRegister<float>),
|
||||||
PlcVarType = PlcVarType.REAL,
|
PlcVarType = PlcVarType.REAL,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
var val = BitConverter.ToUInt32(bytes, 0);
|
var val = BitConverter.ToUInt32(bytes, 0);
|
||||||
byte[] floatVals = BitConverter.GetBytes(val);
|
byte[] floatVals = BitConverter.GetBytes(val);
|
||||||
@@ -132,7 +133,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
return finalFloat;
|
return finalFloat;
|
||||||
|
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
return BitConverter.GetBytes(value);
|
return BitConverter.GetBytes(value);
|
||||||
|
|
||||||
@@ -141,7 +142,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
new PlcTypeConversion<TimeSpan>(RegisterType.DDT) {
|
new PlcTypeConversion<TimeSpan>(RegisterType.DDT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<TimeSpan>),
|
HoldingRegisterType = typeof(NumberRegister<TimeSpan>),
|
||||||
PlcVarType = PlcVarType.TIME,
|
PlcVarType = PlcVarType.TIME,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
var vallong = BitConverter.ToUInt32(bytes, 0);
|
var vallong = BitConverter.ToUInt32(bytes, 0);
|
||||||
var valMillis = vallong * 10;
|
var valMillis = vallong * 10;
|
||||||
@@ -149,7 +150,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
return ts;
|
return ts;
|
||||||
|
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
var tLong = (uint)(value.TotalMilliseconds / 10);
|
var tLong = (uint)(value.TotalMilliseconds / 10);
|
||||||
return BitConverter.GetBytes(tLong);
|
return BitConverter.GetBytes(tLong);
|
||||||
@@ -158,19 +159,52 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
},
|
},
|
||||||
new PlcTypeConversion<byte[]>(RegisterType.DT) {
|
new PlcTypeConversion<byte[]>(RegisterType.DT) {
|
||||||
HoldingRegisterType = typeof(BytesRegister),
|
HoldingRegisterType = typeof(BytesRegister),
|
||||||
FromRaw = bytes => bytes,
|
FromRaw = (reg, bytes) => bytes,
|
||||||
ToRaw = value => value,
|
ToRaw = (reg, value) => value,
|
||||||
|
},
|
||||||
|
new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) {
|
||||||
|
HoldingRegisterType = typeof(StringRegister),
|
||||||
|
PlcVarType = PlcVarType.STRING,
|
||||||
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
|
//get actual showed size
|
||||||
|
short actualLen = BitConverter.ToInt16(bytes, 2);
|
||||||
|
|
||||||
|
//skip 4 bytes because they only describe the length
|
||||||
|
return Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray());
|
||||||
|
|
||||||
|
},
|
||||||
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
|
var sReg = (StringRegister)reg;
|
||||||
|
|
||||||
|
if(value.Length > sReg.ReservedSize)
|
||||||
|
value = value.Substring(0, sReg.ReservedSize);
|
||||||
|
|
||||||
|
int padLen = sReg.ReservedSize;
|
||||||
|
if(sReg.ReservedSize % 2 != 0) padLen++;
|
||||||
|
|
||||||
|
var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0'));
|
||||||
|
|
||||||
|
List<byte> finalBytes = new List<byte>();
|
||||||
|
finalBytes.AddRange(BitConverter.GetBytes(sReg.ReservedSize));
|
||||||
|
finalBytes.AddRange(BitConverter.GetBytes((short)value.Length));
|
||||||
|
finalBytes.AddRange(strBytes);
|
||||||
|
|
||||||
|
return finalBytes.ToArray();
|
||||||
|
|
||||||
|
},
|
||||||
},
|
},
|
||||||
new PlcTypeConversion<BitArray>(RegisterType.DT) {
|
new PlcTypeConversion<BitArray>(RegisterType.DT) {
|
||||||
HoldingRegisterType = typeof(BytesRegister),
|
HoldingRegisterType = typeof(BytesRegister),
|
||||||
PlcVarType = PlcVarType.WORD,
|
PlcVarType = PlcVarType.WORD,
|
||||||
FromRaw = bytes => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
BitArray bitAr = new BitArray(bytes);
|
BitArray bitAr = new BitArray(bytes);
|
||||||
return bitAr;
|
return bitAr;
|
||||||
|
|
||||||
},
|
},
|
||||||
ToRaw = value => {
|
ToRaw = (reg, value) => {
|
||||||
|
|
||||||
byte[] ret = new byte[(value.Length - 1) / 8 + 1];
|
byte[] ret = new byte[(value.Length - 1) / 8 + 1];
|
||||||
value.CopyTo(ret, 0);
|
value.CopyTo(ret, 0);
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
internal interface IPlcTypeConverter {
|
internal interface IPlcTypeConverter {
|
||||||
|
|
||||||
object FromRawData(byte[] data);
|
object FromRawData(IRegister register, byte[] data);
|
||||||
|
|
||||||
byte[] ToRawData(object value);
|
byte[] ToRawData(IRegister register, object value);
|
||||||
|
|
||||||
Type GetDotnetType();
|
Type GetDotnetType();
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
public Type HoldingRegisterType { get; set; }
|
public Type HoldingRegisterType { get; set; }
|
||||||
|
|
||||||
public Func<byte[], T> FromRaw { get; set; }
|
public Func<IRegister, byte[], T> FromRaw { get; set; }
|
||||||
|
|
||||||
public Func<T, byte[]> ToRaw { get; set; }
|
public Func<IRegister, T, byte[]> ToRaw { get; set; }
|
||||||
|
|
||||||
public PlcTypeConversion(RegisterType plcType) {
|
public PlcTypeConversion(RegisterType plcType) {
|
||||||
|
|
||||||
@@ -32,9 +32,9 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
public PlcVarType GetPlcVarType() => PlcVarType;
|
public PlcVarType GetPlcVarType() => PlcVarType;
|
||||||
|
|
||||||
public object FromRawData(byte[] data) => FromRaw.Invoke(data);
|
public object FromRawData(IRegister register, byte[] data) => FromRaw.Invoke(register, data);
|
||||||
|
|
||||||
public byte[] ToRawData(object value) => ToRaw.Invoke((T)value);
|
public byte[] ToRawData(IRegister register, object value) => ToRaw.Invoke(register, (T)value);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,25 +12,25 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
private static List<IPlcTypeConverter> conversions => Conversions.items;
|
private static List<IPlcTypeConverter> conversions => Conversions.items;
|
||||||
|
|
||||||
public static T Parse<T>(byte[] bytes) {
|
public static T Parse<T>(IRegister register, byte[] bytes) {
|
||||||
|
|
||||||
var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
|
var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
|
||||||
|
|
||||||
if (converter == null)
|
if (converter == null)
|
||||||
throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist");
|
throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist");
|
||||||
|
|
||||||
return (T)converter.FromRawData(bytes);
|
return (T)converter.FromRawData(register, bytes);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] Encode <T>(T value) {
|
public static byte[] Encode <T>(IRegister register, T value) {
|
||||||
|
|
||||||
var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
|
var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
|
||||||
|
|
||||||
if (converter == null)
|
if (converter == null)
|
||||||
throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist");
|
throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist");
|
||||||
|
|
||||||
return converter.ToRawData(value);
|
return converter.ToRawData(register, value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ namespace MewtocolTests {
|
|||||||
"D0005000051", //double word register
|
"D0005000051", //double word register
|
||||||
"D0005000051", //double word register
|
"D0005000051", //double word register
|
||||||
"D0005000051", //double word register
|
"D0005000051", //double word register
|
||||||
"D0005000065", //variable len register even bytes
|
"D0005000064", //variable len register even bytes
|
||||||
"D0005000066", //variable len register odd bytes
|
"D0005000065", //variable len register odd bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
//test mewtocol idents
|
//test mewtocol idents
|
||||||
|
|||||||
Binary file not shown.
@@ -2,17 +2,17 @@
|
|||||||
<ProjectConfiguration CompactMode="1">
|
<ProjectConfiguration CompactMode="1">
|
||||||
<PaneContents>
|
<PaneContents>
|
||||||
<Pane-1 PaneBaseID="410" PaneInstanceNumber="1">
|
<Pane-1 PaneBaseID="410" PaneInstanceNumber="1">
|
||||||
<Contents BinaryData="9B1A0000789CE599D16EDA301486FB1CBB8A72D34B92405A904A2B4AD2CE12345512C1A669425EEC516B891D254E297B91BDEEECA4B42594C1AAB258DA1D26FCE7FC9C8FE31C875F1F8E8ECEC68C12CEB229A1882DB4214B5218F13143B8AF9BBA16447738817DBDD316AFEFD802448C4E085EF475433F3F1BB2B84868BE7AA1018E1340117E9097354039CE288C6F60228201842927DF09CE746D1093394DC45A04367B9DB6D111D139E4249A12C4EF44665BE80705672FD71F3144387BA115FEDC87148A8448266CBDE6C3ACFB98C0B8C06B164CA3D3B54FBB350B96D95BB350AEDF64C1AA5BB8BA1D2094E13CDFC34677BD10DDB7D6A15D3721484BF51E302C63DD43B97E9389CE868918CA2A4C484EBEC5B8FCD5ECFE6DD4EC98DBEC78317A0CFC9C7EA745BB6E315CA6B84187ADA71EAB029471ABF702F293D07969A96C4DF1257240AF3356A4F9A3DAC711CB905696B9AFFBEED0F31D5D1BB1E84795407311E1B0FC62C2CE6D86EF9F7A5B865B0941E88E752DC40FC27688737EC9586C56E2802F4BB5D595F9B6892E2E2E363EAE0509CC78D98E018E71C409A3D59E53AEA4D0A35223DFACC5161DE438BE1B04AB04BEA8B1B196C2324F4FEAB2A1371EBB37E1EECF8D06CFA127037F763DF22E07A39DBAF0F3ADBB925D7ADEEB825605E560709C228D490439560A90E384CA2172C09644874624FB07D0B4E09F1CA520D5EC34CDA7B116127700F34429324ED8EDF59482D354EF08366D4B2D364E786A18A652741ADBDAAE6206D504D4560A90EF6E111C1A50C0333135FE15A0E3E3436F6E028FAD149E20F4C1CDF517FB6B33F301E13E9E939CCBC3B24A8D2440996ACD70D3B2E2FF98913C83CD04A405C9F14C0CB6963C022AC569C3D27FC949F6524812E54E42A28B2CA5E88460EC3642C7A545A21A1D5BAD1DAE99512E9774941BE34CC59E201CB06F3487E4690C97F2F9A3BCB6BDF0F3D962B88C6212893B5280B9181A943BBCDA8A4DDF07BC1B692097E301A039CE3846559C6D00F7815477F94E68DE97C65EB5FF53BD5B6BFF439DFF0625CE315E">
|
<Contents BinaryData="9B1A0000789CE599D16E9B301486FB1CBB42DCF43240429B484D2B1A688794940A50BA699A220F7BA935B01198A6D98BEC756743D336B45992AA144BBB8B43FE73FE9C2F071F933F9F0E0E4E26946046B31B4C205D28239AA42062130AD150D55525886E5102866AAFCB5FDFD2851B5132C568315435F5F46444E32221F9EA85E23294B804A27B71597109431901F1154878301722C2F04F8C3255B1623C27095FF3C0FAA0D7D57A3C3A030C473718B25B9ED9E47AAB60F4F9FA33021065CFB4DC9F739F029E108A849DD77CE8751F53101768CD82AEF5FAE671BF66C1D0076B16CAF59B2C18750B17D7168419CAF31D6CF4D70BD17F6B1DBA75139CB450EF00C3D0D63D94EB3799E8BD301103518529CEF18F1895BF9AEDBF8D9A1D7D931D2F860F819FD26FB568D62D86CB14B5E8B0F3D8635580326EF55E807F63322F2D95ADC9BF44EE92CB8C1669FEA0F6514433A894651EAABE33F27C5B55C634FA5525501C881928BF18B7739DA1BBC7DE16E15642377426AA12A27B6E3B44393BA734D62B71C096A5DAE88B7C9B4417D638705E08942001192B1B3240318A18A6A4BAEB942B21F588D088376BD1790FD9B6EF04C12A85CFABACADA530F4E3A3BA6CE44D26CE55B8FD7363EB29F4D4F2679763EFDC1A6FD5855FAF9D95ECDCF35E17742A2C8DE1B18B34C61160681F445AB3786C3B940E90ED6E48D43420D13F2E490BF6C5DE07D1D9D959B3906A76DAE6D35A03F11D403F928A8C1DF60703A9E0B4D53B9C4DD7908B8D1D1E6B9A2E159DD66E6D1731057202EA4A05C87736089A0614B08C4F8D7B013A3C6CFAE6C6F19852E10942DFBDBAFC667E6F673EC0CC47739C33715896A99138285DAE19EEA6ACF807331267B01987B4C0399AF1C1D6104740A938BDB0F45F7212BD14E264AF73D0C7EC46BA21159DD09D38ADD0714891C846C794EB0ED7CE28970B3AD28D71BA644F101AEC1BC5C6791A83A578FE28AE6D2EFC7CB6182DA318477C470A10E3438374875753B2E9BBC1DD487173311EB824471943B08AB309E02E90EA2EDF09CDFBD2D8A9F6FFAA7767ED7FA8D3BFE894317F">
|
||||||
<FilterHistory/>
|
<FilterHistory/>
|
||||||
</Contents>
|
</Contents>
|
||||||
</Pane-1>
|
</Pane-1>
|
||||||
<Pane-2 PaneBaseID="410" PaneInstanceNumber="4">
|
<Pane-2 PaneBaseID="410" PaneInstanceNumber="4">
|
||||||
<Contents BinaryData="56170000789CED985D4FE2401486FD177B3BE95E7827FD023111930A559B8035D32EC61863C6CE2C4CB69D21EDB0C8FEFA9DB6205250C0156C36DC904EE979CF3BE7E9994E0BBF1D1C9C7638A382C7B794613E024D1E0D50203A1C9386A229C00BFA24420DC534E4719F8F9C80B32E25A386A22A67A74D1E0E23964C0F802348E4304C9ED3BF81C30489190AAF5124C51C4C98A03F2989156085B4C7223996C2DA8969A8A6541748D0E09662D19799AB32DE1A0AFE7A7C451026F1AB58E9CF7E1E209910A7092BCB7C68451F5D140EC99C054D35EBD5E37AC182AE9DCC59C8C61FB2A0172D5CDC5818C72449D6B0519F2F44FDA375308A2624E9347A0D18BA3AEF211B7FC884B96022446915BA34A14F21C9EE9AD5F746C18EF6961D37C413E159FA9516AB458BFE7840BED061E5A5C772814C373FE7D13F94F5324B596BCA49240EBB8CF970904CA22109788C4156E68602EDA60B5B0A68F3E0579E00D8980A944D4CDAB989C9EF97DE4EE5A6818E6F7714E0936769BBF7F8944F765A26484234D672294F8C332DBD9E667F4BC2873FEC85EB8117A15864DDE99190048272962F41D9288D74591A939E2C88CB866AB5A0ED79D30C509DD3D7B5E35A31A6E9763AF6B5BFFABAB635D3ED5AF0F1B2ED9E5BED9571FEDD8D3D0D3B77DDE501951CD00E41E92503A5ED412D0765940DD4C59ED4725266D948A99AB567D57BA413568559AE0074BC88F353F1B47CA35A2A3AC5FAEC060E9ED0696D88C7306A5BE62301D54A0568A144BB212472403E8DC8460BDC77BDAFA99191C89F64FBA8EAA542E53B1DFB0B5025392A4FC4F2856013588757240CF9E1B6573CB35CDB71CF87CEF5E57DF5614BA8C0154A9A7D1ACA77FCAC6AEFA143A3F3B12010B1DE9A5D5674FC49A84A84C782D0BA03F7EAD191663E00F702DC66855FA2302DF2BFB072922E8DC510856F91FA6FA0AC85E0BD86A8CCEAFD094D32FBD0F15E87C09A6A6DF4F8D9FEFEBAE8685768B6BE9996BD907E5D725842624170AEB36F8B956D31F709FDEC2FBD3D3AC7">
|
<Contents BinaryData="0B380000789CED9B6D4FAB3014C77D77BF06992F7CA73C4E96A8090EA6249B33C0D598C5983A7AB5B90C16A857773FFD2DB0A9638F34D4D55CDE8DADE7F4CFF9D1D29ED3FDD0F7F64E7A51887014DFA2D08F5E8576341A8321EE453E3C6D480DC11D3EC311386DA80AF9FC1CBDDAC328BC41F0F5B42136CE4EDA51F0320A93D907C1C67064873E7C4B7F16EC10C33804C115181167B60F438C7E2118134F518CED70184390A0F0296B6C04E8291C9126A42FA9A52AA24A9A6180D1F016F9F89988D1D2562F38FA7C7D09810FE34FB644B2F5360644839FBA3D5A264D2A4ABB01C10BDCA44A12555D3BD60BAA64A935A72ABBA65225175575AE0DDF8F61926C21439F8F8D4E1B1AA528823C0FA9F5167C64715E43764D25425D101180340A3728418F01DCF27129C89156C9E907FED4F147F71B256A4589DE640C77A8F0E87D24E60E32BFF9772EFA3B7B9AB3014C6E22B1C38B387A1927536B070EA3D817B2309F361CABDD77CC86D08D86BFF30E04CB4718643746E45CC7F0CFFB0C90BA9B19DA9ED56B081E7C23B29F1E707EB31E1AC1DC818B279907594FFB5C65E8ED2B7AB26020B82310E36C9CBA3080438CA2309F9FB2ABD4B41FA636E99705EF641C99A663B9EEAC0BD3F4147DAE0B593A6E16CDDAFD5ECFBAF236B7EB1A1FAE6F0CE7E1A2DB3F37BA1BEDBCBB6BEBFDA6ED9EB5D4E02847C30851922372714C9E9032900E1EC4E4E18031254F15B982E47A8E7D7531D0EE77400AE5A4ECC2AD6EC0A4EBAC11291A57888AF1F91A38FE948E59128FA234D94F744DAE002D84E86B083D4E87CFF495EDC0004CD4526F25E7A7C59695238992C115ABF37E7FB9C1D7B3527863D5A9492D2725F346AA06B51C94540654C7E8BAAC49F1B5D863484AB80449FB1905646F9F856B1D39F07A3EC1D001E1D3969BA8A2E28A187144C6701CE34E18888787927A2FF43BC26D16F8251E66416632AA06E2F27DC0AAF652735F14C5C55157F1823CCBBA70446B251C9633DE40A261B3F8EAAA9C0D5F6FA3DDB09169D82C2E002B6723D76C060A0D9BC58D54E56C949ACD40A561A3B167A3D66C56E504D7B3619DFC216CF8CACEED864D9386CD317B367C25E676C3E698860DF3A4B65CC0FF7FB2D169D8B4D8B3E1AB72B71B362D1A36067B36AD9ACD40A24A0C9C338723D5890102872A33D0660FA7CE0C103854A901933D9C3A3540E050E50618170D5238756E80C0A14A0E74D8C3F96EC981A38F9A4105859E8F437AEBAA3C4E73C9D26CC7C5B8A2A40AC895E0C4B2F8F6998960A2641C80497A78333DFBBA3AE6A6279641948FAF45938AC757E52BBA128CD81DE4A146A4F1C848AB21CD412A1628B7A2F41DCBA6BC6392D762524B51625E66A8FE28F037C753EAF80EF30A9D5AF9F695773CEB2739B5D4313832C729A2C67C35AE56BE8DE503929DA47F76B1C304C618FAB99F552BEE6DA014557279846AABD0AFDDE9CCFDEFEFEC1F1FE51A44">
|
||||||
<FilterHistory/>
|
<FilterHistory/>
|
||||||
</Contents>
|
</Contents>
|
||||||
</Pane-2>
|
</Pane-2>
|
||||||
<Pane-3 PaneBaseID="410" PaneInstanceNumber="5">
|
<Pane-3 PaneBaseID="410" PaneInstanceNumber="5">
|
||||||
<Contents BinaryData="C3150000789CED98DF4EE24014C67D89BD9F742FBC5BFA0FC5444C2A546D02D6B45D8C31C68C9D5998EC7486B4C322FB72FB6A3B9D824A414157B4D970437A4ACF77BE39BF9E49DB3F5F76760EBB9C11C1D34BC2101F83164F8630165D8E7053333410C6039CC0A6665BF278C0C75ECC598FE07153D3B5A3C316A7A38465B303E0099C780CE1FBFC6FE031815306E9394CA498873013E407C1A9061C4AFA2C91B114360E6C4BB7A5BA8082C497048981AC5C97F9CE48F0A7F1198608A74F72A53FF77E0865419417AC2DF361947DF4201DE1390B866E37EAFB8D9205D33898B3A0E2375930CB164E2E1C84529C656BD868CC37A2F1D63E58651392749EBD060C539FF7A0E23799B0174C509877A147327247B1BA6B56DF1B253BC673767C8AA6C28FE5575AAC972D469321FE4487B587192B04946E712E24BF09EB2B4B6A34E522328F9DA67C34CCA6D9018E798A806A73530BDC961FB435D0E1F1CFA200701111502D4CDAB948F1AF87D9CEE566895EE4763510E17B69BB7F7B572C76D6A6005338310AA9504C9496D9C8AB3F271105DFDD85EB4198C054A8E90C31C5B1209C155B908AF24C9FE539F9C992B81CA8763B70C3705621D0E7F44D637FAF9CD3F2BB5DF73C5A7D5DC779D4ED39C1ED69C73F763A2BF3A2AB0B779676ECFBCB136A05A00F0465560C94B105B51C94553550275B52CB49D95523A51BCE9655FF964C599556B902D0FE22CE77C5D38EAC7AA5E894FBF33170D0944EFB95782C6B6FC37C24A0BD4A015A68D1C7101205A08824F8551BDC577360E88995C99F6CF3A81A954215795DF713506505AA50A4F285E035B076CF30A57C77D33B9E5DADC7F1300ABCF3D3EBFACD8650813398B50684CA777CD5B597D0C1F1F144E000B2FE9A535676FC4EA82A84C70902E70A5CEBDFBE19F60DF04FC0A56AFC12855993FF859597F5482A46903E47EABF81B216829706A2F6D8EF7718122FCB3F65782CC3A9C0A8D0D93258C960EE7BEDD15FFED4C0BE">
|
<Contents BinaryData="C4150000789CED98DF4EE24014C67D89BD9F742FBC93FE433011930A459B80356D17638C31636716263B9D21ED20B24FB78FB6D316540A0AB8A2CD861BD2297CDFF9E6FC7A1ADA3FDFF6F68EBB9C11C1E32BC2101F83268F8630145D8E7043D114E087031CC186621AF278C0C74EC8598FE07143519593E326A7A38825B303E0081C390CE1C7F46BE030816306E9058CA499833013E427C1B1022C4AFA2C926B69AC1D99866A4A77010509AF08120359B92AF5D648F097EB730C118E5F68653EFB710865419416AC2CCBA11573F4201DE1B9089A6AD6ABB57A2182AE1DCD45C8D6EF8AA01723B42F2D84629C246BC4A8CF37A2FEDE3E18C5109274AA5E0386AECE67C8D6EF0A612E84A030ED428F24E49EE2ECAA597D6D14E268AFC571299A1A3F975F19B15A8C184C86F80B13569E662C37C87CF3733EF94D583F8B948DA6DC44E2B0B3988F86C954EDE190C708646D6E289EDD74BD96023A3CFC951700362202661B93712E63FCF034DBA9DD4CE804765701017E94B1FB77F7F966676DF23085132DB7F2C524F3D2EB69F5D72CDA56C7B71704C08F602CB2F1F431C5A1209CE5F7A06C954A5D966AD29305773951AD9667FBFEAC84A7CEF9EB5AEDB0A869BADDAE7D11ACFE5DC77AF6ED59DEDD59C73DB53A2B75C1F5A53D939DBAEE72412527F489A4F44D4805DE8F6D83D276A0968332CA06AABD23B59C94593652AA66ED58F5EFC8945561972B00D516717E289E5660544B45A7D89FCF8183A6745A1BE2318CC32DF391800E4B0568A1459F4348E4800212E18D6E70DFF581A64646223F92EDA3AA970A55E074ED2F4095E4A87C11CB27824D60ED9F634AF9FEB6EF7866B9FE8EFB81E75C9CDD546FB7840A9CC3A43920543EE4675D7B0B1D1C9F4E04F620EBAF3965C5C41F84AA44782CCFB3AEC18D7A70A099B7C06D83ABACF14B1C664DFE17564ED223B11841FA1AA9FF06CA5A08DE1A88CA73BF3F60489C247D97E1B004C702A3DC67C7602583B917B6277F016BD6C0EA">
|
||||||
<FilterHistory/>
|
<FilterHistory/>
|
||||||
</Contents>
|
</Contents>
|
||||||
</Pane-3>
|
</Pane-3>
|
||||||
|
|||||||
Reference in New Issue
Block a user