Added new conversions

- added compile target for net6
This commit is contained in:
Felix Weiß
2023-06-28 18:59:28 +02:00
parent a9bd2962b4
commit bc765b870a
24 changed files with 730 additions and 509 deletions

View File

@@ -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);
} }
} }

View File

@@ -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);

View File

@@ -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);
} }

View 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;
}
}
}

View File

@@ -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
} }
} }

View File

@@ -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
} }
} }

View File

@@ -48,7 +48,9 @@ namespace MewtocolNet {
return retInfo; return retInfo;
} }
return null; return null;
} }
#endregion #endregion
@@ -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;

View File

@@ -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>

View File

@@ -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);

View File

@@ -3,8 +3,7 @@ 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 {

View File

@@ -1,4 +1,5 @@
namespace MewtocolNet.RegisterBuilding { namespace MewtocolNet.RegisterBuilding {
internal struct ParseResult { internal struct ParseResult {
public ParseResultState state; public ParseResultState state;

View File

@@ -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) {

View File

@@ -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));
} }

View File

@@ -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);
} }

View File

@@ -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));
} }

View File

@@ -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;
} }

View File

@@ -9,4 +9,13 @@
} }
internal enum CommandState {
Intial,
LineFeed,
RequestedNextFrame,
Complete
}
} }

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -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.

View File

@@ -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>