Change datatypes to uint for registers

- add new tests / fix old tests
- change register builder pattern
- add register attribute from string name
- fix register update triggers
This commit is contained in:
Felix Weiß
2023-07-11 01:37:44 +02:00
parent d8c18bbf34
commit fbd53c850f
31 changed files with 1315 additions and 725 deletions

View File

@@ -19,13 +19,20 @@ namespace MewtocolNet.Exceptions {
internal static MewtocolException DupeRegister (IRegisterInternal register) {
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetRegisterPLCName()}");
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}");
}
internal static MewtocolException DupeNameRegister (IRegisterInternal register) {
return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.GetRegisterPLCName()}");
return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.GetMewName()}");
}
internal static MewtocolException OverlappingRegister (IRegisterInternal registerA, IRegisterInternal registerB) {
throw new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " +
$"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}");
}

View File

@@ -14,50 +14,6 @@ namespace MewtocolNet {
/// </summary>
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>
/// Turns a bit array into a 0 and 1 string
/// </summary>
public static string ToBitString(this BitArray arr) {
var bits = new bool[arr.Length];
arr.CopyTo(bits, 0);
return string.Join("", bits.Select(x => x ? "1" : "0"));
}
#endregion
#region Byte and string operation helpers
/// <summary>

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace MewtocolNet {
public static class PlcFormat {
/// <summary>
/// Gets the TimeSpan as a PLC representation string fe.
/// <code>
/// T#1h10m30s20ms
/// </code>
/// </summary>
/// <param name="timespan"></param>
/// <returns></returns>
public static string ToPlcTime (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;
int days = timespan.Days;
if (days > 0) sb.Append($"{days}d");
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();
}
public static TimeSpan ParsePlcTime (string plcTimeFormat) {
var reg = new Regex(@"(?:T|t)#(?:(?<d>[0-9]{1,2})d)?(?:(?<h>[0-9]{1,2})h)?(?:(?<m>[0-9]{1,2})m)?(?:(?<s>[0-9]{1,2})s)?(?:(?<ms>[0-9]{1,3})ms)?");
var match = reg.Match(plcTimeFormat);
if(match.Success) {
var days = match.Groups["d"].Value;
var hours = match.Groups["h"].Value;
var minutes = match.Groups["m"].Value;
var seconds = match.Groups["s"].Value;
var milliseconds = match.Groups["ms"].Value;
TimeSpan retTime = TimeSpan.Zero;
if (!string.IsNullOrEmpty(days)) retTime += TimeSpan.FromDays(int.Parse(days));
if (!string.IsNullOrEmpty(hours)) retTime += TimeSpan.FromHours(int.Parse(hours));
if (!string.IsNullOrEmpty(minutes)) retTime += TimeSpan.FromMinutes(int.Parse(minutes));
if (!string.IsNullOrEmpty(seconds)) retTime += TimeSpan.FromSeconds(int.Parse(seconds));
if (!string.IsNullOrEmpty(milliseconds)) retTime += TimeSpan.FromMilliseconds(int.Parse(milliseconds));
if ((retTime.TotalMilliseconds % 10) != 0)
throw new NotSupportedException("Plc times can't have a millisecond component lower than 10ms");
return retTime;
}
return TimeSpan.Zero;
}
/// <summary>
/// Turns a bit array into a 0 and 1 string
/// </summary>
public static string ToBitString(this BitArray arr) {
var bits = new bool[arr.Length];
arr.CopyTo(bits, 0);
return string.Join("", bits.Select(x => x ? "1" : "0"));
}
}
}

View File

@@ -77,7 +77,14 @@ namespace MewtocolNet {
/// <param name="withTerminator">Append the checksum and bcc automatically</param>
/// <param name="timeoutMs">Timout to wait for a response</param>
/// <returns>Returns the result</returns>
Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1);
Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1, Action<double> onReceiveProgress = null);
/// <summary>
/// Changes the PLCs operation mode to the given one
/// </summary>
/// <param name="setRun">True for run mode, false for prog mode</param>
/// <returns>The success state of the write operation</returns>
Task<bool> SetOperationModeAsync(bool setRun);
/// <summary>
/// Use this to await the first poll iteration after connecting,

View File

@@ -143,6 +143,7 @@ namespace MewtocolNet {
private protected MewtocolInterface () {
Connected += MewtocolInterface_Connected;
RegisterChanged += OnRegisterChanged;
void MewtocolInterface_Connected(PLCInfo obj) {
@@ -153,16 +154,15 @@ namespace MewtocolNet {
}
RegisterChanged += (o) => {
}
var asInternal = (IRegisterInternal)o;
private void OnRegisterChanged(IRegister o) {
string address = $"{asInternal.GetRegisterString()}{asInternal.GetStartingMemoryArea()}".PadRight(5, (char)32);
var asInternal = (IRegisterInternal)o;
Logger.Log($"{address} " +
$"{(o.Name != null ? $"({o.Name}) " : "")}" +
$"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this);
};
Logger.Log($"{asInternal.GetMewName()} " +
$"{(o.Name != null ? $"({o.Name}) " : "")}" +
$"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this);
}
@@ -195,15 +195,15 @@ namespace MewtocolNet {
}
/// <inheritdoc/>
public virtual string GetConnectionInfo() => throw new NotImplementedException();
public virtual string GetConnectionInfo () => throw new NotImplementedException();
/// <inheritdoc/>
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1) {
public async Task<MewtocolFrameResponse> SendCommandAsync (string _msg, bool withTerminator = true, int timeoutMs = -1, Action<double> onReceiveProgress = null) {
//send request
queuedMessages++;
var tempResponse = queue.Enqueue(async () => await SendFrameAsync(_msg, withTerminator, withTerminator));
var tempResponse = queue.Enqueue(async () => await SendFrameAsync(_msg, withTerminator, withTerminator, onReceiveProgress));
if (await Task.WhenAny(tempResponse, Task.Delay(timeoutMs)) != tempResponse) {
// timeout logic
@@ -217,7 +217,7 @@ namespace MewtocolNet {
}
private protected async Task<MewtocolFrameResponse> SendFrameAsync (string frame, bool useBcc = true, bool useCr = true) {
private protected async Task<MewtocolFrameResponse> SendFrameAsync (string frame, bool useBcc = true, bool useCr = true, Action<double> onReceiveProgress = null) {
try {
@@ -236,13 +236,27 @@ namespace MewtocolNet {
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
stream.Write(writeBuffer, 0, writeBuffer.Length);
//calculate the expected number of frames from the message request
int? wordsCountRequested = null;
if(onReceiveProgress != null) {
var match = Regex.Match(frame, @"RDD(?<from>[0-9]{5})(?<to>[0-9]{5})");
if (match.Success) {
var from = int.Parse(match.Groups["from"].Value);
var to = int.Parse(match.Groups["to"].Value);
wordsCountRequested = (to - from) + 1;
}
}
//calc upstream speed
CalcUpstreamSpeed(writeBuffer.Length);
Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this);
Logger.Log($"--> OUT MSG: {frame.Replace("\r", "(CR)")}", LogLevel.Critical, this);
var readResult = await ReadCommandAsync();
var readResult = await ReadCommandAsync(wordsCountRequested, onReceiveProgress);
//did not receive bytes but no errors, the com port was not configured right
if (readResult.Item1.Length == 0) {
@@ -294,7 +308,7 @@ namespace MewtocolNet {
}
private protected async Task<(byte[], bool)> ReadCommandAsync () {
private protected async Task<(byte[], bool)> ReadCommandAsync (int? wordsCountRequested = null, Action<double> onReceiveProgress = null) {
//read total
List<byte> totalResponse = new List<byte>();
@@ -303,6 +317,7 @@ namespace MewtocolNet {
try {
bool needsRead = false;
int readFrames = 0;
do {
@@ -327,6 +342,15 @@ namespace MewtocolNet {
if (commandRes == CommandState.RequestedNextFrame) {
//calc frame progress
if(onReceiveProgress != null && wordsCountRequested != null) {
var frameBytesCount = tempMsg.Length - 6;
double prog = (double)frameBytesCount / wordsCountRequested.Value;
onReceiveProgress(prog);
}
//request next frame
var writeBuffer = Encoding.UTF8.GetBytes("%01**&\r");
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
@@ -334,6 +358,8 @@ namespace MewtocolNet {
}
readFrames++;
} while (needsRead);
} catch (OperationCanceledException) { }
@@ -342,7 +368,7 @@ namespace MewtocolNet {
}
private protected CommandState ParseBufferFrame(byte[] received) {
private protected CommandState ParseBufferFrame (byte[] received) {
const char CR = '\r';
const char DELIMITER = '&';
@@ -405,8 +431,8 @@ namespace MewtocolNet {
private protected virtual void OnConnected (PLCInfo plcinf) {
Logger.Log("Connected", LogLevel.Info, this);
Logger.Log($"\n\n{plcinf.ToString()}\n\n", LogLevel.Verbose, this);
Logger.Log("Connected to PLC", LogLevel.Info, this);
Logger.Log($"{plcinf.ToString()}", LogLevel.Verbose, this);
IsConnected = true;
@@ -440,17 +466,6 @@ namespace MewtocolNet {
}
private protected void ClearRegisterVals() {
for (int i = 0; i < RegistersUnderlying.Count; i++) {
var reg = (IRegisterInternal)RegistersUnderlying[i];
reg.ClearValue();
}
}
private void SetUpstreamStopWatchStart () {
if (speedStopwatchUpstr == null) {

View File

@@ -168,7 +168,6 @@ namespace MewtocolNet {
if((bool)register.Value != resultBitArray[k]) {
register.SetValueFromPLC(resultBitArray[k]);
InvokeRegisterChanged(register);
}
}
@@ -192,7 +191,6 @@ namespace MewtocolNet {
if (lastVal != readout) {
rwReg.SetValueFromPLC(readout);
InvokeRegisterChanged(reg);
}
}
@@ -218,16 +216,21 @@ namespace MewtocolNet {
string propName = prop.Name;
foreach (var attr in attributes) {
if (attr is RegisterAttribute cAttribute && prop.PropertyType.IsAllowedPlcCastingType()) {
if (attr is RegisterAttribute cAttribute) {
if(!prop.PropertyType.IsAllowedPlcCastingType()) {
throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})");
}
var dotnetType = prop.PropertyType;
AddRegister(new RegisterBuildInfo {
mewAddress = cAttribute.MewAddress,
memoryAddress = cAttribute.MemoryArea,
specialAddress = cAttribute.SpecialAddress,
memorySizeBytes = cAttribute.ByteLength,
registerType = cAttribute.RegisterType,
dotnetCastType = dotnetType,
dotnetCastType = dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType,
collectionType = collection.GetType(),
name = prop.Name,
});
@@ -345,6 +348,9 @@ namespace MewtocolNet {
#region Register Adding
/// <inheritdoc/>
public void AddRegister(IRegister register) => AddRegister(register as BaseRegister);
/// <inheritdoc/>
public void AddRegister(BaseRegister register) {
@@ -354,14 +360,14 @@ namespace MewtocolNet {
if (CheckDuplicateNameRegister(register))
throw MewtocolException.DupeNameRegister(register);
if (CheckOverlappingRegister(register, out var regB))
throw MewtocolException.OverlappingRegister(register, regB);
register.attachedInterface = this;
RegistersUnderlying.Add(register);
}
/// <inheritdoc/>
public void AddRegister(IRegister register) => AddRegister(register as BaseRegister);
internal void AddRegister (RegisterBuildInfo buildInfo) {
var builtRegister = buildInfo.Build();
@@ -379,6 +385,9 @@ namespace MewtocolNet {
if(CheckDuplicateNameRegister(builtRegister))
throw MewtocolException.DupeNameRegister(builtRegister);
if (CheckOverlappingRegister(builtRegister, out var regB))
throw MewtocolException.OverlappingRegister(builtRegister, regB);
builtRegister.attachedInterface = this;
RegistersUnderlying.Add(builtRegister);
@@ -406,6 +415,38 @@ namespace MewtocolNet {
}
private bool CheckOverlappingRegister (IRegisterInternal instance, out IRegisterInternal regB) {
//ignore bool registers, they have their own address spectrum
regB = null;
if (instance is BoolRegister) return false;
uint addressFrom = instance.MemoryAddress;
uint addressTo = addressFrom + instance.GetRegisterAddressLen();
var foundOverlapping = RegistersInternal.FirstOrDefault(x => {
//ignore bool registers, they have their own address spectrum
if (x is BoolRegister) return false;
uint addressF = x.MemoryAddress;
uint addressT = addressF + x.GetRegisterAddressLen();
bool matchingBaseAddress = addressFrom < addressT && addressF < addressTo;
return matchingBaseAddress;
});
if (foundOverlapping != null) {
regB = foundOverlapping;
return true;
}
return false;
}
#endregion
#region Register accessing
@@ -428,6 +469,18 @@ namespace MewtocolNet {
#region Event Invoking
private protected void ClearRegisterVals() {
for (int i = 0; i < RegistersUnderlying.Count; i++) {
var reg = (IRegisterInternal)RegistersUnderlying[i];
reg.ClearValue();
}
}
internal void PropertyRegisterWasSet(string propName, object value) {
_ = SetRegisterAsync(GetRegister(propName), value);

View File

@@ -64,12 +64,8 @@ namespace MewtocolNet {
#region Operation mode changing
/// <summary>
/// Changes the PLCs operation mode to the given one
/// </summary>
/// <param name="mode">The mode to change to</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> SetOperationMode (bool setRun) {
/// <inheritdoc/>
public async Task<bool> SetOperationModeAsync (bool setRun) {
string modeChar = setRun ? "R" : "P";
@@ -77,7 +73,7 @@ namespace MewtocolNet {
var result = await SendCommandAsync(requeststring);
if (result.Success) {
Logger.Log($"operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this);
Logger.Log($"Operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this);
} else {
Logger.Log("Operation mode change failed", LogLevel.Error, this);
}
@@ -116,14 +112,15 @@ namespace MewtocolNet {
}
/// <summary>
/// Reads the bytes from the start adress for counts byte length
/// Reads the bytes from the start adress for counts byte length,
/// doesn't block the receive thread
/// </summary>
/// <param name="start">Start adress</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>
/// <returns>A byte array or null of there was an error</returns>
public async Task<byte[]> ReadByteRange(int start, int count, bool flipBytes = true, Action<double> onProgress = null) {
public async Task<byte[]> ReadByteRangeNonBlocking (int start, int count, bool flipBytes = true, Action<double> onProgress = null) {
var byteList = new List<byte>();
@@ -131,12 +128,13 @@ namespace MewtocolNet {
if (count % 2 != 0)
wordLength++;
int blockSize = 8;
//read blocks of max 4 words per msg
for (int i = 0; i < wordLength; i += 8) {
for (int i = 0; i < wordLength; i += blockSize) {
int curWordStart = start + i;
int curWordEnd = curWordStart + 7;
int curWordEnd = curWordStart + blockSize - 1;
string startStr = curWordStart.ToString().PadLeft(5, '0');
string endStr = (curWordEnd).ToString().PadLeft(5, '0');
@@ -146,7 +144,7 @@ namespace MewtocolNet {
if (result.Success && !string.IsNullOrEmpty(result.Response)) {
var bytes = result.Response.ParseDTByteString(8 * 4).HexStringToByteArray();
var bytes = result.Response.ParseDTByteString(blockSize * 4).HexStringToByteArray();
if (bytes == null) return null;
@@ -246,38 +244,15 @@ namespace MewtocolNet {
//returns a byte array 1 long and with the byte beeing 0 or 1
if (toWriteType == typeof(BoolRegister)) {
string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}";
var result = await SendCommandAsync(requeststring);
return result.Success;
string reqStr = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}";
var res = await SendCommandAsync(reqStr);
return res.Success;
}
//writes a byte array 2 bytes or 4 bytes long depending on the data size
if (toWriteType.IsGenericType && toWriteType.GetGenericTypeDefinition() == typeof(NumberRegister<>)) {
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}";
var result = await SendCommandAsync(requeststring);
return result.Success;
}
//returns a byte array with variable size
if (toWriteType == typeof(BytesRegister)) {
throw new NotImplementedException("Not imp");
}
//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;
}
return false;
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}";
var result = await SendCommandAsync(requeststring);
return result.Success;
}

View File

@@ -1,4 +1,5 @@
using System;
using MewtocolNet.RegisterBuilding;
using System;
namespace MewtocolNet.RegisterAttributes {
@@ -10,31 +11,39 @@ namespace MewtocolNet.RegisterAttributes {
internal RegisterType? RegisterType;
internal int MemoryArea = 0;
internal int ByteLength = 2;
internal uint MemoryArea = 0;
internal uint ByteLength = 2;
internal byte SpecialAddress = 0x0;
internal BitCount BitCount;
internal int AssignedBitIndex = -1;
internal string MewAddress = null;
public RegisterAttribute(string mewAddress) {
MewAddress = mewAddress;
}
/// <summary>
/// Attribute for string type or numeric registers
/// </summary>
/// <param name="memoryArea">The area in the plcs memory</param>
public RegisterAttribute(int memoryArea) {
public RegisterAttribute(uint memoryArea) {
MemoryArea = memoryArea;
}
public RegisterAttribute(int memoryArea, int byteLength) {
public RegisterAttribute(uint memoryArea, uint byteLength) {
MemoryArea = memoryArea;
ByteLength = byteLength;
}
public RegisterAttribute(int memoryArea, BitCount bitCount) {
public RegisterAttribute(uint memoryArea, BitCount bitCount) {
MemoryArea = memoryArea;
BitCount = bitCount;
@@ -44,7 +53,7 @@ namespace MewtocolNet.RegisterAttributes {
}
public RegisterAttribute(int memoryArea, BitCount bitCount, int bitIndex) {
public RegisterAttribute(uint memoryArea, BitCount bitCount, int bitIndex) {
MemoryArea = memoryArea;
BitCount = bitCount;
@@ -68,7 +77,7 @@ namespace MewtocolNet.RegisterAttributes {
/// <summary>
/// Attribute for boolean registers
/// </summary>
public RegisterAttribute(IOType type, int memoryArea, byte spAdress = 0x0) {
public RegisterAttribute(IOType type, uint memoryArea, byte spAdress = 0x0) {
MemoryArea = memoryArea;
RegisterType = (RegisterType)(int)type;

View File

@@ -0,0 +1,267 @@
using MewtocolNet.Registers;
using System;
using System.Collections;
using System.Runtime.InteropServices;
namespace MewtocolNet.RegisterBuilding {
public static class BuilderStepExtensions {
public static BuilderStep<T> AsType<T> (this BuilderStepBase baseStep) {
if (!typeof(T).IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting");
}
var castStp = new BuilderStep<T>();
if (baseStep.SpecialAddress != null) castStp.SpecialAddress = baseStep.SpecialAddress;
castStp.Name = baseStep.Name;
castStp.RegType = baseStep.RegType;
castStp.MemAddress = baseStep.MemAddress;
castStp.MemByteSize = baseStep.MemByteSize;
castStp.dotnetVarType = typeof(T);
castStp.plcVarType = null;
castStp.wasCasted = true;
return castStp;
}
public static BuilderStep AsType (this BuilderStepBase baseStep, Type type) {
if (!type.IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting");
}
var castStp = new BuilderStep();
if (baseStep.SpecialAddress != null) castStp.SpecialAddress = baseStep.SpecialAddress;
castStp.Name = baseStep.Name;
castStp.RegType = baseStep.RegType;
castStp.MemAddress = baseStep.MemAddress;
castStp.MemByteSize = baseStep.MemByteSize;
castStp.dotnetVarType = type;
castStp.plcVarType = null;
castStp.wasCasted = true;
return castStp;
}
public static IRegister Build (this BuilderStepBase step) {
//if no casting method in builder was called => autocast the type from the RegisterType
if (!step.wasCasted && step.MemByteSize == null) step.AutoType();
//fallbacks if no casting builder was given
BuilderStepBase.GetFallbackDotnetType(step);
BaseRegister builtReg;
var bInfo = new RegisterBuildInfo {
name = step.Name,
specialAddress = step.SpecialAddress,
memoryAddress = step.MemAddress,
memorySizeBytes = step.MemByteSize,
memorySizeBits = step.MemBitSize,
registerType = step.RegType,
dotnetCastType = step.dotnetVarType,
};
builtReg = bInfo.Build();
BuilderStepBase.AddToRegisterList(step, builtReg);
return builtReg;
}
public static IRegister Build<T>(this BuilderStep<T> step) {
//fallbacks if no casting builder was given
BuilderStepBase.GetFallbackDotnetType(step);
BaseRegister builtReg;
var bInfo = new RegisterBuildInfo {
name = step.Name,
specialAddress = step.SpecialAddress,
memoryAddress = step.MemAddress,
memorySizeBytes = step.MemByteSize,
memorySizeBits = step.MemBitSize,
registerType = step.RegType,
dotnetCastType = step.dotnetVarType,
};
if (step.dotnetVarType.IsEnum) {
builtReg = bInfo.Build();
} else {
builtReg = bInfo.Build();
}
BuilderStepBase.AddToRegisterList(step, builtReg);
return builtReg;
}
}
public abstract class BuilderStepBase {
internal MewtocolInterface forInterface;
internal bool wasCasted = false;
internal string OriginalInput;
internal string Name;
internal RegisterType RegType;
internal uint MemAddress;
internal uint? MemByteSize;
internal ushort? MemBitSize;
internal byte? SpecialAddress;
internal PlcVarType? plcVarType;
internal Type dotnetVarType;
internal BuilderStepBase AutoType() {
switch (RegType) {
case RegisterType.X:
case RegisterType.Y:
case RegisterType.R:
dotnetVarType = typeof(bool);
break;
case RegisterType.DT:
dotnetVarType = typeof(short);
break;
case RegisterType.DDT:
dotnetVarType = typeof(int);
break;
case RegisterType.DT_BYTE_RANGE:
dotnetVarType = typeof(string);
break;
}
plcVarType = null;
wasCasted = true;
return this;
}
internal static void GetFallbackDotnetType (BuilderStepBase step) {
bool isBoolean = step.RegType.IsBoolean();
bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null;
if (isTypeNotDefined && step.RegType == RegisterType.DT) {
step.dotnetVarType = typeof(short);
}
if (isTypeNotDefined && step.RegType == RegisterType.DDT) {
step.dotnetVarType = typeof(int);
} else if (isTypeNotDefined && isBoolean) {
step.dotnetVarType = typeof(bool);
} else if (isTypeNotDefined && step.RegType == RegisterType.DT_BYTE_RANGE) {
step.dotnetVarType = typeof(string);
}
if (step.plcVarType != null) {
step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType();
}
}
internal static void AddToRegisterList(BuilderStepBase step, BaseRegister instance) {
if (step.forInterface == null) return;
step.forInterface.AddRegister(instance);
}
}
public class BuilderStep<T> : BuilderStepBase { }
public class BuilderStep : BuilderStepBase {
public BuilderStep AsPlcType (PlcVarType varType) {
dotnetVarType = null;
plcVarType = varType;
wasCasted = true;
return this;
}
public BuilderStep AsBytes (uint byteLength) {
if (RegType != RegisterType.DT) {
throw new NotSupportedException($"Cant use the {nameof(AsBytes)} converter on a non {nameof(RegisterType.DT)} register");
}
MemByteSize = byteLength;
dotnetVarType = typeof(byte[]);
plcVarType = null;
wasCasted = true;
return this;
}
public BuilderStep AsBits(ushort bitCount = 16) {
if (RegType != RegisterType.DT) {
throw new NotSupportedException($"Cant use the {nameof(AsBits)} converter on a non {nameof(RegisterType.DT)} register");
}
MemBitSize = bitCount;
dotnetVarType = typeof(BitArray);
plcVarType = null;
wasCasted = true;
return this;
}
}
}

View File

@@ -1,78 +0,0 @@
using MewtocolNet.Registers;
using System;
using System.Linq;
using System.Reflection;
namespace MewtocolNet.RegisterBuilding {
public static class FinalizerExtensions {
public static IRegister Build(this RegisterBuilderStep step) {
//if no casting method in builder was called => autocast the type from the RegisterType
if (!step.wasCasted)
step.AutoType();
//fallbacks if no casting builder was given
step.GetFallbackDotnetType();
var builtReg = new RegisterBuildInfo {
name = step.Name,
specialAddress = step.SpecialAddress,
memoryAddress = step.MemAddress,
memorySizeBytes = step.MemByteSize,
registerType = step.RegType,
dotnetCastType = step.dotnetVarType,
}.Build();
step.AddToRegisterList(builtReg);
return builtReg;
}
private static void GetFallbackDotnetType(this RegisterBuilderStep step) {
bool isBoolean = step.RegType.IsBoolean();
bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null;
if (isTypeNotDefined && step.RegType == RegisterType.DT) {
step.dotnetVarType = typeof(short);
}
if (isTypeNotDefined && step.RegType == RegisterType.DDT) {
step.dotnetVarType = typeof(int);
} else if (isTypeNotDefined && isBoolean) {
step.dotnetVarType = typeof(bool);
} else if (isTypeNotDefined && step.RegType == RegisterType.DT_BYTE_RANGE) {
step.dotnetVarType = typeof(string);
}
if (step.plcVarType != null) {
step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType();
}
}
private static void AddToRegisterList(this RegisterBuilderStep step, BaseRegister instance) {
if (step.forInterface == null) return;
step.forInterface.AddRegister(instance);
}
}
}

View File

@@ -6,7 +6,7 @@
public string hardFailReason;
public RegisterBuilderStep stepData;
public BuilderStep stepData;
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
@@ -18,6 +19,7 @@ namespace MewtocolNet.RegisterBuilding {
(x) => TryBuildBoolean(x),
(x) => TryBuildNumericBased(x),
(x) => TryBuildByteRangeBased(x),
};
@@ -32,7 +34,7 @@ namespace MewtocolNet.RegisterBuilding {
public static RegBuilder Factory { get; private set; } = new RegBuilder();
public RegisterBuilderStep FromPlcRegName (string plcAddrName, string name = null) {
public BuilderStep FromPlcRegName (string plcAddrName, string name = null) {
foreach (var method in parseMethods) {
@@ -45,6 +47,7 @@ namespace MewtocolNet.RegisterBuilding {
res.stepData.OriginalInput = plcAddrName;
res.stepData.forInterface = forInterface;
return res.stepData;
} else if(res.state == ParseResultState.FailedHard) {
@@ -77,7 +80,7 @@ namespace MewtocolNet.RegisterBuilding {
string special = match.Groups["special"].Value;
IOType regType;
int areaAdd = 0;
uint areaAdd = 0;
byte specialAdd = 0x0;
//try cast the prefix
@@ -90,7 +93,7 @@ namespace MewtocolNet.RegisterBuilding {
}
if(!string.IsNullOrEmpty(area) && !int.TryParse(area, out areaAdd) ) {
if(!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd) ) {
return new ParseResult {
state = ParseResultState.FailedHard,
@@ -102,7 +105,7 @@ namespace MewtocolNet.RegisterBuilding {
//special address not given
if(string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) {
var isAreaInt = int.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt);
var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt);
if (isAreaInt && areaInt >= 0 && areaInt <= 9) {
@@ -142,7 +145,11 @@ namespace MewtocolNet.RegisterBuilding {
return new ParseResult {
state = ParseResultState.Success,
stepData = new RegisterBuilderStep ((RegisterType)(int)regType, areaAdd, specialAdd),
stepData = new BuilderStep {
RegType = (RegisterType)(int)regType,
MemAddress = areaAdd,
SpecialAddress = specialAdd,
}
};
}
@@ -150,7 +157,7 @@ namespace MewtocolNet.RegisterBuilding {
// one to two word registers
private static ParseResult TryBuildNumericBased (string plcAddrName) {
var patternByte = new Regex(@"(?<prefix>DT|DDT)(?<area>[0-9]{1,5})");
var patternByte = new Regex(@"^(?<prefix>DT|DDT)(?<area>[0-9]{1,5})$");
var match = patternByte.Match(plcAddrName);
@@ -159,12 +166,11 @@ namespace MewtocolNet.RegisterBuilding {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
int areaAdd = 0;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
@@ -176,7 +182,7 @@ namespace MewtocolNet.RegisterBuilding {
}
if (!string.IsNullOrEmpty(area) && !int.TryParse(area, out areaAdd)) {
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
@@ -187,7 +193,76 @@ namespace MewtocolNet.RegisterBuilding {
return new ParseResult {
state = ParseResultState.Success,
stepData = new RegisterBuilderStep(regType, areaAdd),
stepData = new BuilderStep {
RegType = regType,
MemAddress = areaAdd,
},
};
}
// one to two word registers
private static ParseResult TryBuildByteRangeBased (string plcAddrName) {
var split = plcAddrName.Split('-');
if(split.Length > 2)
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'"
};
uint[] addresses = new uint[2];
for (int i = 0; i < split.Length; i++) {
string addr = split[i];
var patternByte = new Regex(@"(?<prefix>DT|DDT)(?<area>[0-9]{1,5})");
var match = patternByte.Match(addr);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType) || regType != RegisterType.DT) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
addresses[i] = areaAdd;
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new BuilderStep {
RegType = RegisterType.DT_BYTE_RANGE,
dotnetVarType = typeof(byte[]),
MemAddress = addresses[0],
MemByteSize = (addresses[1] - addresses[0] + 1) * 2,
}
};
}

View File

@@ -1,15 +1,22 @@
using MewtocolNet.Registers;
using MewtocolNet.Exceptions;
using MewtocolNet.Registers;
using System;
using System.Collections;
using System.Data.Common;
using System.Reflection;
using System.Runtime.InteropServices;
namespace MewtocolNet.RegisterBuilding {
internal struct RegisterBuildInfo {
internal string mewAddress;
internal string name;
internal int memoryAddress;
internal int memorySizeBytes;
internal uint memoryAddress;
internal uint? memorySizeBytes;
internal ushort? memorySizeBits;
internal byte? specialAddress;
internal RegisterType? registerType;
@@ -18,10 +25,37 @@ namespace MewtocolNet.RegisterBuilding {
internal BaseRegister Build () {
//Has mew address use this before the default checks
if (mewAddress != null) return BuildFromMewAddress();
//parse enums
if (dotnetCastType.IsEnum) {
//-------------------------------------------
//as numeric register with enum target
var underlying = Enum.GetUnderlyingType(dotnetCastType);
var enuSize = Marshal.SizeOf(underlying);
if (enuSize > 4)
throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported");
Type myParameterizedSomeClass = typeof(NumberRegister<>).MakeGenericType(dotnetCastType);
ConstructorInfo constr = myParameterizedSomeClass.GetConstructor(new Type[] { typeof(uint), typeof(string) });
var parameters = new object[] { memoryAddress, name };
var instance = (BaseRegister)constr.Invoke(parameters);
if (collectionType != null)
instance.WithCollectionType(collectionType);
return instance;
}
//parse all others where the type is known
RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault();
Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType();
bool isBytesRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister);
bool isStringRegister = !registerClassType.IsGenericType && registerClassType == typeof(StringRegister);
@@ -30,7 +64,7 @@ namespace MewtocolNet.RegisterBuilding {
//-------------------------------------------
//as numeric register with boolean bit target
//create a new bregister instance
var instance = new BytesRegister(memoryAddress, memorySizeBytes, name);
var instance = new BytesRegister(memoryAddress, memorySizeBytes.Value, name);
if (collectionType != null)
instance.WithCollectionType(collectionType);
@@ -42,13 +76,11 @@ namespace MewtocolNet.RegisterBuilding {
//-------------------------------------------
//as numeric register
var areaAddr = memoryAddress;
//create a new bregister instance
var flags = BindingFlags.Public | BindingFlags.Instance;
//int _adress, Type _enumType = null, string _name = null
var parameters = new object[] { areaAddr, null, name };
var parameters = new object[] { memoryAddress, name };
var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null);
if(collectionType != null)
@@ -62,7 +94,14 @@ namespace MewtocolNet.RegisterBuilding {
//-------------------------------------------
//as byte range register
var instance = new BytesRegister(memoryAddress, memorySizeBytes, name);
BytesRegister instance;
if(memorySizeBits != null) {
instance = new BytesRegister(memoryAddress, memorySizeBits.Value, name);
} else {
instance = new BytesRegister(memoryAddress, memorySizeBytes.Value, name);
}
if (collectionType != null)
instance.WithCollectionType(collectionType);
@@ -106,6 +145,12 @@ namespace MewtocolNet.RegisterBuilding {
}
private BaseRegister BuildFromMewAddress () {
return (BaseRegister)RegBuilder.Factory.FromPlcRegName(mewAddress, name).AsType(dotnetCastType).Build();
}
}
}

View File

@@ -1,114 +0,0 @@
using System;
namespace MewtocolNet.RegisterBuilding {
public class RegisterBuilderStep {
internal MewtocolInterface forInterface;
internal bool wasCasted = false;
internal string OriginalInput;
internal string Name;
internal RegisterType RegType;
internal int MemAddress;
internal int MemByteSize;
internal byte? SpecialAddress;
internal PlcVarType? plcVarType;
internal Type dotnetVarType;
public RegisterBuilderStep() => throw new NotSupportedException("Cant make a new instance of RegisterBuilderStep, use the builder pattern");
internal RegisterBuilderStep(RegisterType regType, int memAddr) {
RegType = regType;
MemAddress = memAddr;
}
internal RegisterBuilderStep(RegisterType regType, int memAddr, byte specialAddr) {
RegType = regType;
MemAddress = memAddr;
SpecialAddress = specialAddr;
}
public RegisterBuilderStep AsPlcType(PlcVarType varType) {
dotnetVarType = null;
plcVarType = varType;
wasCasted = true;
return this;
}
public RegisterBuilderStep AsType<T>() {
if (!typeof(T).IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting");
}
dotnetVarType = typeof(T);
plcVarType = null;
wasCasted = true;
return this;
}
public RegisterBuilderStep AsBytes (int byteLength) {
if (RegType != RegisterType.DT) {
throw new NotSupportedException($"Cant use the AsByte converter on a non DT register");
}
MemByteSize = byteLength;
dotnetVarType = typeof(byte[]);
plcVarType = null;
wasCasted = true;
return this;
}
internal RegisterBuilderStep AutoType() {
switch (RegType) {
case RegisterType.X:
case RegisterType.Y:
case RegisterType.R:
dotnetVarType = typeof(bool);
break;
case RegisterType.DT:
dotnetVarType = typeof(short);
break;
case RegisterType.DDT:
dotnetVarType = typeof(int);
break;
case RegisterType.DT_BYTE_RANGE:
dotnetVarType = typeof(string);
break;
}
plcVarType = null;
wasCasted = true;
return this;
}
}
}

View File

@@ -13,10 +13,10 @@ namespace MewtocolNet.Registers {
public event Action<object> ValueChanged;
internal MewtocolInterface attachedInterface;
internal object lastValue;
internal object lastValue = null;
internal Type collectionType;
internal string name;
internal int memoryAddress;
internal uint memoryAddress;
/// <inheritdoc/>
public MewtocolInterface AttachedInterface => attachedInterface;
@@ -34,18 +34,16 @@ namespace MewtocolNet.Registers {
public string Name => name;
/// <inheritdoc/>
public string PLCAddressName => GetRegisterPLCName();
public string PLCAddressName => GetMewName();
/// <inheritdoc/>
public int MemoryAddress => memoryAddress;
public uint MemoryAddress => memoryAddress;
#region Trigger update notify
public event PropertyChangedEventHandler PropertyChanged;
internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed);
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
#endregion
@@ -53,9 +51,14 @@ namespace MewtocolNet.Registers {
public virtual void SetValueFromPLC(object val) {
lastValue = val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
if(lastValue?.ToString() != val?.ToString()) {
lastValue = val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
}
@@ -90,7 +93,11 @@ namespace MewtocolNet.Registers {
public virtual string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
public virtual string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}";
public virtual string GetMewName() => $"{GetRegisterString()}{MemoryAddress}";
public virtual uint GetRegisterAddressLen() => throw new NotImplementedException();
public string GetRegisterWordRangeString() => $"{GetMewName()} - {MemoryAddress + GetRegisterAddressLen() - 1}";
#endregion
@@ -102,14 +109,24 @@ namespace MewtocolNet.Registers {
#endregion
public override string ToString() => $"{GetRegisterPLCName()}{(Name != null ? $" ({Name})" : "")} - Value: {GetValueString()}";
protected virtual void CheckAddressOverflow (uint addressStart, uint addressLen) {
if (addressStart < 0)
throw new NotSupportedException("The area address can't be negative");
if (addressStart + addressLen > 99999)
throw new NotSupportedException($"Memory adresses cant be greater than 99999 (DT{addressStart}-{addressStart + addressLen})");
}
public override string ToString() => $"{GetMewName()}{(Name != null ? $" ({Name})" : "")} - Value: {GetValueString()}";
public virtual string ToString(bool additional) {
if (!additional) return this.ToString();
StringBuilder sb = new StringBuilder();
sb.AppendLine($"PLC Naming: {GetRegisterPLCName()}");
sb.AppendLine($"PLC Naming: {GetMewName()}");
sb.AppendLine($"Name: {Name ?? "Not named"}");
sb.AppendLine($"Value: {GetValueString()}");
sb.AppendLine($"Register Type: {RegisterType}");

View File

@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Net;
using System.Text;
using System.Threading.Tasks;
@@ -25,21 +26,9 @@ namespace MewtocolNet.Registers {
/// <param name="_name">The custom name</param>
/// <exception cref="NotSupportedException"></exception>
/// <exception cref="Exception"></exception>
public BoolRegister(IOType _io, byte _spAddress = 0x0, int _areaAdress = 0, string _name = null) {
public BoolRegister(IOType _io, byte _spAddress = 0x0, uint _areaAdress = 0, string _name = null) {
if (_areaAdress < 0)
throw new NotSupportedException("The area address cant be negative");
if (_io == IOType.R && _areaAdress >= 512)
throw new NotSupportedException("R area addresses cant be greater than 511");
if ((_io == IOType.X || _io == IOType.Y) && _areaAdress >= 110)
throw new NotSupportedException("XY area addresses cant be greater than 110");
if (_spAddress > 0xF)
throw new NotSupportedException("Special address cant be greater 15 or 0xF");
lastValue = false;
lastValue = null;
memoryAddress = _areaAdress;
specialAddress = _spAddress;
@@ -47,18 +36,27 @@ namespace MewtocolNet.Registers {
RegisterType = (RegisterType)(int)_io;
CheckAddressOverflow(memoryAddress, 0);
}
protected override void CheckAddressOverflow(uint addressStart, uint addressLen) {
if ((int)RegisterType == (int)IOType.R && addressStart >= 512)
throw new NotSupportedException("R area addresses cant be greater than 511");
if (((int)RegisterType == (int)IOType.X || (int)RegisterType == (int)IOType.Y) && addressStart >= 110)
throw new NotSupportedException("XY area addresses cant be greater than 110");
if (specialAddress > 0xF)
throw new NotSupportedException("Special address cant be greater 15 or 0xF");
base.CheckAddressOverflow(addressStart, addressLen);
}
#region Read / Write
public override void SetValueFromPLC(object val) {
lastValue = (bool)val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
}
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
@@ -111,7 +109,7 @@ namespace MewtocolNet.Registers {
public override void ClearValue() => SetValueFromPLC(false);
/// <inheritdoc/>
public override string GetRegisterPLCName() {
public override string GetMewName() {
var spAdressEnd = SpecialAddress.ToString("X1");
@@ -131,13 +129,16 @@ namespace MewtocolNet.Registers {
}
/// <inheritdoc/>
public override uint GetRegisterAddressLen () => 1;
/// <inheritdoc/>
public override string ToString(bool additional) {
if (!additional) return this.ToString();
StringBuilder sb = new StringBuilder();
sb.AppendLine($"PLC Naming: {GetRegisterPLCName()}");
sb.AppendLine($"PLC Naming: {GetMewName()}");
sb.AppendLine($"Name: {Name ?? "Not named"}");
sb.AppendLine($"Value: {GetValueString()}");
sb.AppendLine($"Register Type: {RegisterType}");

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
@@ -12,35 +13,75 @@ namespace MewtocolNet.Registers {
/// </summary>
public class BytesRegister : BaseRegister {
internal int addressLength;
internal uint addressLength;
/// <summary>
/// The rgisters memory length
/// </summary>
public int AddressLength => addressLength;
public uint AddressLength => addressLength;
internal short ReservedSize { get; set; }
internal uint ReservedBytesSize { get; private set; }
internal ushort? ReservedBitSize { get; private set; }
/// <summary>
/// Defines a register containing a string
/// Defines a register containing bytes
/// </summary>
public BytesRegister(int _address, int _reservedByteSize, string _name = null) {
public BytesRegister(uint _address, uint _reservedByteSize, string _name = null) {
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
name = _name;
memoryAddress = _address;
ReservedSize = (short)_reservedByteSize;
ReservedBytesSize = _reservedByteSize;
//calc mem length
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
var byteSize = _reservedByteSize;
if (_reservedByteSize % 2 != 0) byteSize++;
var byteSize = ReservedBytesSize;
if (ReservedBytesSize % 2 != 0) byteSize++;
RegisterType = RegisterType.DT_BYTE_RANGE;
addressLength = (byteSize / 2) - 1;
addressLength = Math.Max((byteSize / 2), 1);
CheckAddressOverflow(memoryAddress, addressLength);
lastValue = null;
}
public override string GetValueString() => Value == null ? "null" : ((byte[])Value).ToHexString("-");
public BytesRegister(uint _address, ushort _reservedBitSize, string _name = null) {
name = _name;
memoryAddress = _address;
ReservedBytesSize = (uint)Math.Max(1, _reservedBitSize / 8);
ReservedBitSize = _reservedBitSize;
//calc mem length
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
var byteSize = ReservedBytesSize;
if (ReservedBytesSize % 2 != 0) byteSize++;
RegisterType = RegisterType.DT_BYTE_RANGE;
addressLength = Math.Max((byteSize / 2), 1);
CheckAddressOverflow(memoryAddress, addressLength);
lastValue = null;
}
public override string GetValueString() {
if (Value == null) return "null";
if(Value != null && Value is BitArray bitArr) {
return bitArr.ToBitString();
} else {
return ((byte[])Value).ToHexString("-");
}
}
/// <inheritdoc/>
public override string BuildMewtocolQuery() {
@@ -48,7 +89,7 @@ namespace MewtocolNet.Registers {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + AddressLength).ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + AddressLength - 1).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
@@ -56,10 +97,20 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override void SetValueFromPLC (object val) {
lastValue = (byte[])val;
bool changeTriggerBitArr = val is BitArray bitArr &&
lastValue is BitArray bitArr2 &&
(bitArr.ToBitString() != bitArr2.ToBitString());
TriggerChangedEvnt(this);
TriggerNotifyChange();
bool changeTriggerGeneral = (lastValue?.ToString() != val?.ToString());
if (changeTriggerBitArr || changeTriggerGeneral) {
lastValue = val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
}
@@ -69,6 +120,9 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC(null);
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
@@ -77,7 +131,13 @@ namespace MewtocolNet.Registers {
var read = await attachedInterface.ReadRawRegisterAsync(this);
if (read == null) return null;
var parsed = PlcValueParser.Parse<byte[]>(this, read);
object parsed;
if(ReservedBitSize != null) {
parsed = PlcValueParser.Parse<BitArray>(this, read);
} else {
parsed = PlcValueParser.Parse<byte[]>(this, read);
}
SetValueFromPLC(parsed);
return parsed;
@@ -89,8 +149,17 @@ namespace MewtocolNet.Registers {
if (!attachedInterface.IsConnected) return false;
var res = await attachedInterface.WriteRawRegisterAsync(this, (byte[])data);
byte[] encoded;
if (ReservedBitSize != null) {
encoded = PlcValueParser.Encode(this, (BitArray)data);
} else {
encoded = PlcValueParser.Encode(this, (byte[])data);
}
var res = await attachedInterface.WriteRawRegisterAsync(this, encoded);
if (res) SetValueFromPLC(data);
return res;
}

View File

@@ -1,4 +1,5 @@
using System;
using MewtocolNet.Registers;
using System;
using System.Text;
using System.Threading.Tasks;
@@ -37,7 +38,7 @@ namespace MewtocolNet {
/// <summary>
/// The plc memory address of the register
/// </summary>
int MemoryAddress { get; }
uint MemoryAddress { get; }
/// <summary>
/// Gets the value of the register as the plc representation string
@@ -67,7 +68,6 @@ namespace MewtocolNet {
/// <returns>True if successfully set</returns>
Task<bool> WriteAsync(object data);
}
}

View File

@@ -17,7 +17,7 @@ namespace MewtocolNet {
object Value { get; }
int MemoryAddress { get; }
uint MemoryAddress { get; }
// setters
@@ -37,7 +37,7 @@ namespace MewtocolNet {
string GetContainerName();
string GetRegisterPLCName();
string GetMewName();
byte? GetSpecialAddress();
@@ -47,6 +47,9 @@ namespace MewtocolNet {
string BuildMewtocolQuery();
uint GetRegisterAddressLen();
string GetRegisterWordRangeString();
//others

View File

@@ -14,66 +14,63 @@ namespace MewtocolNet.Registers {
/// <typeparam name="T">The type of the numeric value</typeparam>
public class NumberRegister<T> : BaseRegister {
internal Type enumType { get; set; }
/// <summary>
/// Defines a register containing a number
/// </summary>
/// <param name="_address">Memory start adress max 99999</param>
/// <param name="_name">Name of the register</param>
public NumberRegister (int _address, string _name = null) {
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
public NumberRegister (uint _address, string _name = null) {
memoryAddress = _address;
name = _name;
Type numType = typeof(T);
uint areaLen = 0;
var allowedTypes = PlcValueParser.GetAllowDotnetTypes();
if (!allowedTypes.Contains(numType))
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
if (typeof(T).IsEnum) {
var areaLen = (Marshal.SizeOf(numType) / 2) - 1;
RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
//for enums
lastValue = default(T);
var underlyingType = typeof(T).GetEnumUnderlyingType(); //the numeric type
areaLen = (uint)(Marshal.SizeOf(underlyingType) / 2) - 1;
}
if (areaLen == 0) RegisterType = RegisterType.DT;
if (areaLen == 1) RegisterType = RegisterType.DDT;
if (areaLen >= 2) RegisterType = RegisterType.DT_BYTE_RANGE;
/// <summary>
/// Defines a register containing a number
/// </summary>
/// <param name="_address">Memory start adress max 99999</param>
/// <param name="_enumType">Enum type to parse as</param>
/// <param name="_name">Name of the register</param>
public NumberRegister(int _address, Type _enumType, string _name = null) {
lastValue = null;
Console.WriteLine();
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
} else {
memoryAddress = _address;
name = _name;
//for all others known pre-defined numeric structs
Type numType = typeof(T);
var allowedTypes = PlcValueParser.GetAllowDotnetTypes();
if (!allowedTypes.Contains(numType))
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
var allowedTypes = PlcValueParser.GetAllowDotnetTypes();
if (!allowedTypes.Contains(numType))
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
areaLen = (uint)(Marshal.SizeOf(numType) / 2) - 1;
RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
var areaLen = (Marshal.SizeOf(numType) / 2) - 1;
RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
lastValue = null;
enumType = _enumType;
lastValue = default(T);
}
CheckAddressOverflow(memoryAddress, areaLen);
}
/// <inheritdoc/>
public override void SetValueFromPLC(object val) {
lastValue = (T)val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
if (lastValue?.ToString() != val?.ToString()) {
lastValue = (T)val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
}
@@ -93,71 +90,42 @@ namespace MewtocolNet.Registers {
}
/// <inheritdoc/>
public override string GetAsPLC() => ((TimeSpan)Value).AsPLCTime();
public override string GetAsPLC() {
/// <inheritdoc/>
public override string GetValueString() {
if(typeof(T) == typeof(TimeSpan)) {
return $"{Value} [{((TimeSpan)Value).AsPLCTime()}]";
}
//is number or bitwise
if (enumType == null) {
return $"{Value}";
}
//is enum
var dict = new Dictionary<int, string>();
foreach (var name in Enum.GetNames(enumType)) {
int enumKey = (int)Enum.Parse(enumType, name);
if (!dict.ContainsKey(enumKey)) {
dict.Add(enumKey, name);
}
}
if (enumType != null && Value is short shortVal) {
if (dict.ContainsKey(shortVal)) {
return $"{Value} ({dict[shortVal]})";
} else {
return $"{Value} (Missing Enum)";
}
}
if (enumType != null && Value is int intVal) {
if (dict.ContainsKey(intVal)) {
return $"{Value} ({dict[intVal]})";
} else {
return $"{Value} (Missing Enum)";
}
}
if (typeof(T) == typeof(TimeSpan)) return ((TimeSpan)Value).ToPlcTime();
return Value.ToString();
}
/// <inheritdoc/>
public override string GetValueString() {
if(Value != null && typeof(T) == typeof(TimeSpan)) {
return $"{Value} [{((TimeSpan)Value).ToPlcTime()}]";
}
if (Value != null && typeof(T).IsEnum) {
var underlying = Enum.GetUnderlyingType(typeof(T));
object val = Convert.ChangeType(Value, underlying);
return $"{Value} [{val}]";
}
return Value?.ToString() ?? "null";
}
/// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC(default(T));
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2);
/// <inheritdoc/>
public override async Task<object> ReadAsync() {

View File

@@ -1,6 +1,9 @@
using System;
using MewtocolNet.Exceptions;
using MewtocolNet.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
@@ -16,20 +19,23 @@ namespace MewtocolNet.Registers {
internal short UsedSize { get; set; }
internal int WordsSize { get; set; }
internal uint WordsSize { get; set; }
private bool isCalibrated = false;
private bool isCalibratedFromPlc = false;
/// <summary>
/// Defines a register containing a string
/// </summary>
public StringRegister (int _address, string _name = null) {
public StringRegister (uint _address, string _name = null) {
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
name = _name;
memoryAddress = _address;
RegisterType = RegisterType.DT_BYTE_RANGE;
CheckAddressOverflow(memoryAddress, 0);
lastValue = null;
}
/// <inheritdoc/>
@@ -38,7 +44,7 @@ namespace MewtocolNet.Registers {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + WordsSize - 1).ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + Math.Max(1, WordsSize) - 1).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
@@ -49,10 +55,14 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override void SetValueFromPLC (object val) {
lastValue = (string)val;
if (!val.Equals(lastValue)) {
TriggerChangedEvnt(this);
TriggerNotifyChange();
lastValue = (string)val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
}
@@ -62,6 +72,9 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC("");
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize);
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
@@ -69,25 +82,39 @@ namespace MewtocolNet.Registers {
//get the string params first
if(!isCalibrated) await Calibrate();
if(!isCalibratedFromPlc) await CalibrateFromPLC();
var read = await attachedInterface.ReadRawRegisterAsync(this);
if (read == null) return null;
return PlcValueParser.Parse<string>(this, read);
var parsed = PlcValueParser.Parse<string>(this, read);
SetValueFromPLC(parsed);
return parsed;
}
private async Task Calibrate () {
private async Task CalibrateFromPLC () {
Logger.Log($"Calibrating string ({PLCAddressName}) from PLC source", LogLevel.Verbose, attachedInterface);
//get the string describer bytes
var bytes = await attachedInterface.ReadByteRange(MemoryAddress, 4, false);
var bytes = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, 4, false);
if (bytes == null || bytes.Length == 0 || bytes.All(x => x == 0x0)) {
throw new MewtocolException($"The string register ({PLCAddressName}) doesn't exist in the PLC program");
}
ReservedSize = BitConverter.ToInt16(bytes, 0);
UsedSize = BitConverter.ToInt16(bytes, 2);
WordsSize = 2 + (ReservedSize + 1) / 2;
WordsSize = Math.Max(0, (uint)(2 + (ReservedSize + 1) / 2));
isCalibrated = true;
CheckAddressOverflow(memoryAddress, WordsSize);
isCalibratedFromPlc = true;
}
@@ -96,10 +123,18 @@ namespace MewtocolNet.Registers {
if (!attachedInterface.IsConnected) return false;
if (!isCalibratedFromPlc) {
//try to calibrate from plc
await CalibrateFromPLC();
}
var res = await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (string)data));
if (res) {
UsedSize = (short)((string)Value).Length;
SetValueFromPLC(data);
UsedSize = (short)((string)Value).Length;
}
return res;

View File

@@ -26,10 +26,11 @@ namespace MewtocolNet.TypeConversion {
};
/// <summary>
/// All conversions for reading dataf from and to the plc
/// All conversions for reading data from and to the plc, excluding Enum types
/// </summary>
internal static List<IPlcTypeConverter> items = new List<IPlcTypeConverter> {
//default bool R conversion
new PlcTypeConversion<bool>(RegisterType.R) {
HoldingRegisterType = typeof(BoolRegister),
PlcVarType = PlcVarType.BOOL,
@@ -43,6 +44,7 @@ namespace MewtocolNet.TypeConversion {
},
},
//default bool X conversion
new PlcTypeConversion<bool>(RegisterType.X) {
HoldingRegisterType = typeof(BoolRegister),
PlcVarType = PlcVarType.BOOL,
@@ -56,6 +58,7 @@ namespace MewtocolNet.TypeConversion {
},
},
//default bool Y conversion
new PlcTypeConversion<bool>(RegisterType.Y) {
HoldingRegisterType = typeof(BoolRegister),
PlcVarType = PlcVarType.BOOL,
@@ -69,6 +72,7 @@ namespace MewtocolNet.TypeConversion {
},
},
//default short DT conversion
new PlcTypeConversion<short>(RegisterType.DT) {
HoldingRegisterType = typeof(NumberRegister<short>),
PlcVarType = PlcVarType.INT,
@@ -82,6 +86,7 @@ namespace MewtocolNet.TypeConversion {
},
},
//default ushort DT conversion
new PlcTypeConversion<ushort>(RegisterType.DT) {
HoldingRegisterType = typeof(NumberRegister<ushort>),
PlcVarType = PlcVarType.UINT,
@@ -95,6 +100,7 @@ namespace MewtocolNet.TypeConversion {
},
},
//default int DDT conversion
new PlcTypeConversion<int>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<int>),
PlcVarType = PlcVarType.DINT,
@@ -108,6 +114,7 @@ namespace MewtocolNet.TypeConversion {
},
},
//default uint DDT conversion
new PlcTypeConversion<uint>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<uint>),
PlcVarType = PlcVarType.UDINT,
@@ -121,6 +128,7 @@ namespace MewtocolNet.TypeConversion {
},
},
//default float DDT conversion
new PlcTypeConversion<float>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<float>),
PlcVarType = PlcVarType.REAL,
@@ -139,6 +147,7 @@ namespace MewtocolNet.TypeConversion {
},
},
//default TimeSpan DDT conversion
new PlcTypeConversion<TimeSpan>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<TimeSpan>),
PlcVarType = PlcVarType.TIME,
@@ -157,11 +166,13 @@ namespace MewtocolNet.TypeConversion {
},
},
new PlcTypeConversion<byte[]>(RegisterType.DT) {
//default byte array DT Range conversion
new PlcTypeConversion<byte[]>(RegisterType.DT_BYTE_RANGE) {
HoldingRegisterType = typeof(BytesRegister),
FromRaw = (reg, bytes) => bytes,
ToRaw = (reg, value) => value,
},
//default string DT Range conversion
new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) {
HoldingRegisterType = typeof(StringRegister),
PlcVarType = PlcVarType.STRING,
@@ -195,12 +206,16 @@ namespace MewtocolNet.TypeConversion {
},
},
new PlcTypeConversion<BitArray>(RegisterType.DT) {
//default bitn array DT conversion
new PlcTypeConversion<BitArray>(RegisterType.DT_BYTE_RANGE) {
HoldingRegisterType = typeof(BytesRegister),
PlcVarType = PlcVarType.WORD,
FromRaw = (reg, bytes) => {
var byteReg = (BytesRegister)reg;
BitArray bitAr = new BitArray(bytes);
bitAr.Length = (int)byteReg.ReservedBitSize;
return bitAr;
},
@@ -208,6 +223,15 @@ namespace MewtocolNet.TypeConversion {
byte[] ret = new byte[(value.Length - 1) / 8 + 1];
value.CopyTo(ret, 0);
if(ret.Length % 2 != 0) {
var lst = ret.ToList();
lst.Add(0);
ret = lst.ToArray();
}
return ret;
},

View File

@@ -12,9 +12,22 @@ namespace MewtocolNet {
private static List<IPlcTypeConverter> conversions => Conversions.items;
public static T Parse<T>(IRegister register, byte[] bytes) {
internal static T Parse<T> (IRegister register, byte[] bytes) {
var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
IPlcTypeConverter converter;
//special case for enums
if(typeof(T).IsEnum) {
var underlyingNumberType = typeof(T).GetEnumUnderlyingType();
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingNumberType);
} else {
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
}
if (converter == null)
throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist");
@@ -23,9 +36,22 @@ namespace MewtocolNet {
}
public static byte[] Encode <T>(IRegister register, T value) {
internal static byte[] Encode<T> (IRegister register, T value) {
var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
IPlcTypeConverter converter;
//special case for enums
if (typeof(T).IsEnum) {
var underlyingNumberType = typeof(T).GetEnumUnderlyingType();
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingNumberType);
} else {
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
}
if (converter == null)
throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist");

View File

@@ -22,18 +22,24 @@ namespace MewtocolNet {
internal static bool IsAllowedPlcCastingType<T>() {
if (typeof(T).IsEnum) return true;
return allowedCastingTypes.Contains(typeof(T));
}
internal static bool IsAllowedPlcCastingType(this Type type) {
if (type.IsEnum) return true;
return allowedCastingTypes.Contains(type);
}
internal static RegisterType ToRegisterTypeDefault(this Type type) {
if (type.IsEnum) return RegisterType.DT;
var found = PlcValueParser.GetDefaultRegisterType(type);
if (found != null) {

View File

@@ -1,13 +1,12 @@
using MewtocolNet;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using System.Collections;
using MewtocolTests.EncapsulatedTests;
using Xunit;
using Xunit.Abstractions;
namespace MewtocolTests {
public class AutomatedPropertyRegisters {
public partial class AutomatedPropertyRegisters {
private readonly ITestOutputHelper output;
@@ -15,90 +14,14 @@ namespace MewtocolTests {
this.output = output;
}
public class TestRegisterCollection : RegisterCollection {
//corresponds to a R100 boolean register in the PLC
//can also be written as R1000 because the last one is a special address
[Register(IOType.R, memoryArea: 85, spAdress: 0)]
public bool TestBool1 { get; private set; }
//corresponds to a XD input of the PLC
[Register(IOType.X, (byte)0xD)]
public bool TestBoolInputXD { get; private set; }
//corresponds to a DT1101 - DT1104 string register in the PLC with (STRING[4])
//[Register(1101, 4)]
//public string TestString1 { get; private set; }
//corresponds to a DT7000 16 bit int register in the PLC
[Register(899)]
public short TestInt16 { get; private set; }
[Register(342)]
public ushort TestUInt16 { get; private set; }
//corresponds to a DTD7001 - DTD7002 32 bit int register in the PLC
[Register(7001)]
public int TestInt32 { get; private set; }
[Register(765)]
public uint TestUInt32 { get; private set; }
//corresponds to a DTD7001 - DTD7002 32 bit float register in the PLC (REAL)
[Register(7003)]
public float TestFloat32 { get; private set; }
//corresponds to a DT7005 - DT7009 string register in the PLC with (STRING[5])
[Register(7005, 5)]
public string TestString2 { get; private set; }
//corresponds to a DT7010 as a 16bit word/int and parses the word as single bits
[Register(7010)]
public BitArray TestBitRegister { get; private set; }
[Register(8010, BitCount.B32)]
public BitArray TestBitRegister32 { get; private set; }
//corresponds to a DT1204 as a 16bit word/int takes the bit at index 9 and writes it back as a boolean
[Register(1204, BitCount.B16, 9)]
public bool BitValue { get; private set; }
[Register(1204, BitCount.B32, 5)]
public bool FillTest { get; private set; }
//corresponds to a DT7012 - DT7013 as a 32bit time value that gets parsed as a timespan (TIME)
//the smallest value to communicate to the PLC is 10ms
[Register(7012)]
public TimeSpan TestTime { get; private set; }
public enum CurrentState {
Undefined = 0,
State1 = 1,
State2 = 2,
//State3 = 3,
State4 = 4,
State5 = 5,
StateBetween = 100,
State6 = 6,
State7 = 7,
}
[Register(50)]
public CurrentState TestEnum16 { get; private set; }
[Register(51, BitCount.B32)]
public CurrentState TestEnum32 { get; private set; }
}
private void TestBasicGeneration(IRegisterInternal reg, string propName, object expectValue, int expectAddr, string expectPlcName) {
private void Test(IRegisterInternal reg, string propName, uint expectAddr, string expectPlcName) {
Assert.NotNull(reg);
Assert.Equal(propName, reg.Name);
Assert.Equal(expectValue, reg.Value);
Assert.Null(reg.Value);
Assert.Equal(expectAddr, reg.MemoryAddress);
Assert.Equal(expectPlcName, reg.GetRegisterPLCName());
Assert.Equal(expectPlcName, reg.GetMewName());
output.WriteLine(reg.ToString());
@@ -106,107 +29,86 @@ namespace MewtocolTests {
//actual tests
[Fact(DisplayName = "Boolean R generation")]
[Fact(DisplayName = "Boolean generation")]
public void BooleanGen() {
var interf = Mewtocol.Ethernet("192.168.0.1");
interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller();
interf.AddRegisterCollection(new TestBoolRegisters());
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1));
var register1 = interf.GetRegister(nameof(TestBoolRegisters.RType));
var register2 = interf.GetRegister(nameof(TestBoolRegisters.XType));
//test generic properties
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestBool1), false, 85, "R85");
var register3 = interf.GetRegister(nameof(TestBoolRegisters.RType_MewString));
Test((IRegisterInternal)register1, nameof(TestBoolRegisters.RType), 85, "R85A");
Test((IRegisterInternal)register2, nameof(TestBoolRegisters.XType), 0, "XD");
Test((IRegisterInternal)register3, nameof(TestBoolRegisters.RType_MewString), 85, "R85B");
}
[Fact(DisplayName = "Boolean input XD generation")]
public void BooleanInputGen() {
[Fact(DisplayName = "Number 16 bit generation")]
public void N16BitGen () {
var interf = Mewtocol.Ethernet("192.168.0.1");
interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller();
interf.AddRegisterCollection(new Nums16Bit());
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBoolInputXD));
var register1 = interf.GetRegister(nameof(Nums16Bit.Int16Type));
var register2 = interf.GetRegister(nameof(Nums16Bit.UInt16Type));
var register3 = interf.GetRegister(nameof(Nums16Bit.Enum16Type));
var register4 = interf.GetRegister(nameof(Nums16Bit.Int16Type_MewString));
var register5 = interf.GetRegister(nameof(Nums16Bit.Enum16Type_MewString));
//test generic properties
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestBoolInputXD), false, 0, "XD");
Test((IRegisterInternal)register1, nameof(Nums16Bit.Int16Type), 899, "DT899");
Test((IRegisterInternal)register2, nameof(Nums16Bit.UInt16Type), 342, "DT342");
Test((IRegisterInternal)register3, nameof(Nums16Bit.Enum16Type), 50, "DT50");
Test((IRegisterInternal)register4, nameof(Nums16Bit.Int16Type_MewString), 900, "DT900");
Test((IRegisterInternal)register5, nameof(Nums16Bit.Enum16Type_MewString), 51, "DT51");
}
[Fact(DisplayName = "Int16 generation")]
public void Int16Gen() {
[Fact(DisplayName = "Number 32 bit generation")]
public void N32BitGen () {
var interf = Mewtocol.Ethernet("192.168.0.1");
interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller();
interf.AddRegisterCollection(new Nums32Bit());
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt16));
var register1 = interf.GetRegister(nameof(Nums32Bit.Int32Type));
var register2 = interf.GetRegister(nameof(Nums32Bit.UInt32Type));
var register3 = interf.GetRegister(nameof(Nums32Bit.Enum32Type));
var register4 = interf.GetRegister(nameof(Nums32Bit.FloatType));
var register5 = interf.GetRegister(nameof(Nums32Bit.TimeSpanType));
var register6 = interf.GetRegister(nameof(Nums32Bit.Enum32Type_MewString));
var register7 = interf.GetRegister(nameof(Nums32Bit.TimeSpanType_MewString));
//test generic properties
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestInt16), (short)0, 899, "DT899");
Test((IRegisterInternal)register1, nameof(Nums32Bit.Int32Type), 7001, "DDT7001");
Test((IRegisterInternal)register2, nameof(Nums32Bit.UInt32Type), 765, "DDT765");
Test((IRegisterInternal)register3, nameof(Nums32Bit.Enum32Type), 51, "DDT51");
Test((IRegisterInternal)register4, nameof(Nums32Bit.FloatType), 7003, "DDT7003");
Test((IRegisterInternal)register5, nameof(Nums32Bit.TimeSpanType), 7012, "DDT7012");
Test((IRegisterInternal)register6, nameof(Nums32Bit.Enum32Type_MewString), 53, "DDT53");
Test((IRegisterInternal)register7, nameof(Nums32Bit.TimeSpanType_MewString), 7014, "DDT7014");
}
[Fact(DisplayName = "UInt16 generation")]
public void UInt16Gen() {
[Fact(DisplayName = "String generation")]
public void StringGen() {
var interf = Mewtocol.Ethernet("192.168.0.1");
interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller();
interf.AddRegisterCollection(new TestStringRegisters());
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt16));
var register1 = interf.GetRegister(nameof(TestStringRegisters.StringType));
var register2 = interf.GetRegister(nameof(TestStringRegisters.StringType_MewString));
//test generic properties
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestUInt16), (ushort)0, 342, "DT342");
}
[Fact(DisplayName = "Int32 generation")]
public void Int32Gen() {
var interf = Mewtocol.Ethernet("192.168.0.1");
interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt32));
//test generic properties
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestInt32), (int)0, 7001, "DDT7001");
}
[Fact(DisplayName = "UInt32 generation")]
public void UInt32Gen() {
var interf = Mewtocol.Ethernet("192.168.0.1");
interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt32));
//test generic properties
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestUInt32), (uint)0, 765, "DDT765");
}
[Fact(DisplayName = "Float32 generation")]
public void Float32Gen() {
var interf = Mewtocol.Ethernet("192.168.0.1");
interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestFloat32));
//test generic properties
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestFloat32), (float)0, 7003, "DDT7003");
}
[Fact(DisplayName = "TimeSpan generation")]
public void TimespanGen() {
var interf = Mewtocol.Ethernet("192.168.0.1");
interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime));
//test generic properties
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestTime), TimeSpan.Zero, 7012, "DDT7012");
Test((IRegisterInternal)register1, nameof(TestStringRegisters.StringType), 7005, "DT7005");
Test((IRegisterInternal)register2, nameof(TestStringRegisters.StringType_MewString), 7050, "DT7050");
}

View File

@@ -13,7 +13,9 @@ internal class RegisterReadWriteTest {
public object IntialValue { get; set; }
public object AfterWriteValue { get; set; }
public object IntermediateValue { get; set; }
public object AfterWriteValue { get; set; }
public string RegisterPlcAddressName { get; set; }

View File

@@ -0,0 +1,115 @@
using MewtocolNet;
using MewtocolNet.RegisterAttributes;
using System.Collections;
namespace MewtocolTests.EncapsulatedTests {
public enum CurrentState : short {
Undefined = 0,
State1 = 1,
State2 = 2,
//State3 = 3, <= leave empty for test purposes
State4 = 4,
State5 = 5,
StateBetween = 100,
State6 = 6,
State7 = 7,
}
public enum CurrentState32 : int {
Undefined = 0,
State1 = 1,
State2 = 2,
//State3 = 3, <= leave empty for test purposes
State4 = 4,
State5 = 5,
StateBetween = 100,
State6 = 6,
State7 = 7,
}
public class TestBoolRegisters : RegisterCollection {
[Register(IOType.R, memoryArea: 85, spAdress: 0xA)]
public bool RType { get; set; }
[Register(IOType.X, (byte)0xD)]
public bool XType { get; set; }
[Register("R85B")]
public bool RType_MewString { get; set; }
}
public class Nums16Bit : RegisterCollection {
[Register(899)]
public short Int16Type { get; set; }
[Register(342)]
public ushort UInt16Type { get; set; }
[Register(50)]
public CurrentState Enum16Type { get; set; }
[Register("DT900")]
public short Int16Type_MewString { get; set; }
[Register("DT51")]
public CurrentState Enum16Type_MewString { get; set; }
}
public class Nums32Bit : RegisterCollection {
[Register(7001)]
public int Int32Type { get; set; }
[Register(765)]
public uint UInt32Type { get; set; }
[Register(51)]
public CurrentState32 Enum32Type { get; set; }
[Register(7003)]
public float FloatType { get; set; }
[Register(7012)]
public TimeSpan TimeSpanType { get; set; }
[Register("DDT53")]
public CurrentState32 Enum32Type_MewString { get; set; }
[Register("DDT7014")]
public TimeSpan TimeSpanType_MewString { get; set; }
}
public class TestStringRegisters : RegisterCollection {
[Register(7005, 5)]
public string? StringType { get; set; }
[Register("DT7050")]
public string? StringType_MewString { get; set; }
}
public class TestBitwiseRegisters : RegisterCollection {
[Register(7010)]
public BitArray TestBitRegister { get; set; }
[Register(8010, BitCount.B32)]
public BitArray TestBitRegister32 { get; set; }
[Register(1204, BitCount.B16, 9)]
public bool BitValue { get; set; }
[Register(1204, BitCount.B32, 5)]
public bool FillTest { get; set; }
}
}

View File

@@ -13,7 +13,7 @@ namespace MewtocolTests {
this.output = output;
}
[Fact(DisplayName = nameof(MewtocolHelpers.ToBitString))]
[Fact(DisplayName = nameof(PlcFormat.ToBitString))]
public void ToBitStringGeneration() {
var bitarr = new BitArray(16);
@@ -91,6 +91,25 @@ namespace MewtocolTests {
}
[Fact(DisplayName = nameof(PlcFormat.ParsePlcTime))]
public void ParsePlcTime () {
Assert.Equal(new TimeSpan(5, 30, 30, 15, 10), PlcFormat.ParsePlcTime("T#5d30h30m15s10ms"));
Assert.Equal(new TimeSpan(0, 30, 30, 15, 10), PlcFormat.ParsePlcTime("T#30h30m15s10ms"));
Assert.Equal(new TimeSpan(0, 1, 30, 15, 10), PlcFormat.ParsePlcTime("T#1h30m15s10ms"));
Assert.Equal(new TimeSpan(0, 0, 5, 30, 10), PlcFormat.ParsePlcTime("T#5m30s10ms"));
Assert.Throws<NotSupportedException>(() => PlcFormat.ParsePlcTime("T#5m30s5ms"));
}
[Fact(DisplayName = nameof(PlcFormat.ToPlcTime))]
public void ToPlcTime() {
Assert.Equal("T#1d6h5m30s10ms", PlcFormat.ToPlcTime(new TimeSpan(0, 30, 5, 30, 10)));
Assert.Equal("T#6d5h30m10s", PlcFormat.ToPlcTime(new TimeSpan(6, 5, 30, 10)));
}
}
}

View File

@@ -3,6 +3,7 @@ using MewtocolNet.Logging;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.Registers;
using MewtocolTests.EncapsulatedTests;
using System.Collections;
using Xunit;
using Xunit.Abstractions;
@@ -41,15 +42,51 @@ namespace MewtocolTests
new RegisterReadWriteTest {
TargetRegister = new BoolRegister(IOType.R, 0xA, 10),
RegisterPlcAddressName = "R10A",
IntialValue = false,
IntermediateValue = false,
AfterWriteValue = true,
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<short>(3000),
RegisterPlcAddressName = "DT3000",
IntialValue = (short)0,
IntermediateValue = (short)0,
AfterWriteValue = (short)-513,
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<CurrentState>(3001),
RegisterPlcAddressName = "DT3001",
IntermediateValue = CurrentState.Undefined,
AfterWriteValue = CurrentState.State4,
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<CurrentState32>(3002),
RegisterPlcAddressName = "DDT3002",
IntermediateValue = CurrentState32.Undefined,
AfterWriteValue = CurrentState32.StateBetween,
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<TimeSpan>(3004),
RegisterPlcAddressName = "DDT3004",
IntermediateValue = TimeSpan.Zero,
AfterWriteValue = TimeSpan.FromSeconds(11),
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<TimeSpan>(3006),
RegisterPlcAddressName = "DDT3006",
IntermediateValue = TimeSpan.Zero,
AfterWriteValue = PlcFormat.ParsePlcTime("T#50m"),
},
new RegisterReadWriteTest {
TargetRegister = new StringRegister(40),
RegisterPlcAddressName = "DT40",
IntermediateValue = "Hello",
AfterWriteValue = "TestV",
},
new RegisterReadWriteTest {
TargetRegister = RegBuilder.Factory.FromPlcRegName("DT3008").AsBits(5).Build(),
RegisterPlcAddressName = "DT3008",
IntermediateValue = new BitArray(new bool[] { false, false, false, false, false }),
AfterWriteValue = new BitArray(new bool[] { false, true, false, false, false }),
},
};
@@ -57,16 +94,9 @@ namespace MewtocolTests
this.output = output;
Logger.LogLevel = LogLevel.Critical;
Logger.OnNewLogMessage((d, l, m) => {
output.WriteLine($"Mewtocol Logger: {d} {m}");
});
}
[Fact(DisplayName = "Connection cycle client to PLC")]
[Fact(DisplayName = "Connection cycle client to PLC (Ethernet)")]
public async void TestClientConnection() {
foreach (var plc in testPlcInformationData) {
@@ -87,7 +117,7 @@ namespace MewtocolTests
}
[Fact(DisplayName = "Reading basic information from PLC")]
[Fact(DisplayName = "Reading basic status from PLC (Ethernet)")]
public async void TestClientReadPLCStatus() {
foreach (var plc in testPlcInformationData) {
@@ -111,42 +141,56 @@ namespace MewtocolTests
}
[Fact(DisplayName = "Reading basic information from PLC")]
[Fact(DisplayName = "Reading / Writing registers from PLC (Ethernet)")]
public async void TestRegisterReadWriteAsync() {
foreach (var plc in testPlcInformationData) {
Logger.LogLevel = LogLevel.Verbose;
Logger.OnNewLogMessage((d, l, m) => {
output.WriteLine($"Testing: {plc.PLCName}\n");
output.WriteLine($"{d:HH:mm:ss:fff} {m}");
var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort);
});
foreach (var testRW in testRegisterRW) {
var plc = testPlcInformationData[0];
client.AddRegister(testRW.TargetRegister);
output.WriteLine($"\n\n --- Testing: {plc.PLCName} ---\n");
}
var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort);
await client.ConnectAsync();
Assert.True(client.IsConnected);
foreach (var testRW in testRegisterRW) {
foreach (var testRW in testRegisterRW) {
var testRegister = client.Registers.First(x => x.PLCAddressName == testRW.RegisterPlcAddressName);
//test inital val
Assert.Equal(testRW.IntialValue, testRegister.Value);
await testRegister.WriteAsync(testRW.AfterWriteValue);
//test after write val
Assert.Equal(testRW.AfterWriteValue, testRegister.Value);
}
client.Disconnect();
client.AddRegister(testRW.TargetRegister);
}
await client.ConnectAsync();
Assert.True(client.IsConnected);
//cycle run mode to reset registers to inital
await client.SetOperationModeAsync(false);
await client.SetOperationModeAsync(true);
foreach (var testRW in testRegisterRW) {
var testRegister = client.Registers.First(x => x.PLCAddressName == testRW.RegisterPlcAddressName);
//test inital val
Assert.Null(testRegister.Value);
await testRegister.ReadAsync();
Assert.Equal(testRW.IntermediateValue, testRegister.Value);
await testRegister.WriteAsync(testRW.AfterWriteValue);
await testRegister.ReadAsync();
//test after write val
Assert.Equal(testRW.AfterWriteValue, testRegister.Value);
}
client.Disconnect();
}
}

View File

@@ -1,6 +1,7 @@
using MewtocolNet;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.Registers;
using MewtocolTests.EncapsulatedTests;
using System.Collections;
using Xunit;
using Xunit.Abstractions;
@@ -162,12 +163,14 @@ public class TestRegisterBuilder {
}
[Fact(DisplayName = "Parsing as Number Register (Casted)")]
public void TestRegisterBuildingNumericCasted() {
public void TestRegisterBuildingNumericCasted () {
var expect = new NumberRegister<short>(303, null);
var expect2 = new NumberRegister<int>(10002, null);
var expect3 = new NumberRegister<TimeSpan>(400, null);
//var expect4 = new NRegister<TimeSpan>(103, null, true);
var expect = new NumberRegister<short>(303);
var expect2 = new NumberRegister<int>(10002);
var expect3 = new NumberRegister<float>(404);
var expect4 = new NumberRegister<TimeSpan>(400);
var expect5 = new NumberRegister<CurrentState>(203);
var expect6 = new NumberRegister<CurrentState32>(204);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsType<short>().Build());
@@ -175,32 +178,82 @@ public class TestRegisterBuilder {
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsPlcType(PlcVarType.DINT).Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsType<int>().Build());
Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT400").AsPlcType(PlcVarType.TIME).Build());
Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT400").AsType<TimeSpan>().Build());
Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT404").AsPlcType(PlcVarType.REAL).Build());
Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT404").AsType<float>().Build());
//Assert.Equivalent(expect4, RegBuilder.FromPlcRegName("DT103").AsType<BitArray>().Build());
Assert.Equivalent(expect4, RegBuilder.Factory.FromPlcRegName("DDT400").AsPlcType(PlcVarType.TIME).Build());
Assert.Equivalent(expect4, RegBuilder.Factory.FromPlcRegName("DDT400").AsType<TimeSpan>().Build());
Assert.Equivalent(expect5, RegBuilder.Factory.FromPlcRegName("DT203").AsType<CurrentState>().Build());
Assert.Equivalent(expect6, RegBuilder.Factory.FromPlcRegName("DT204").AsType<CurrentState32>().Build());
}
[Fact(DisplayName = "Parsing as Number Register (Auto)")]
public void TestRegisterBuildingNumericAuto() {
public void TestRegisterBuildingNumericAuto () {
var expect = new NumberRegister<short>(303, null);
var expect2 = new NumberRegister<int>(10002, null);
var expect = new NumberRegister<short>(201);
var expect2 = new NumberRegister<int>(10002);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT201").Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").Build());
}
[Fact(DisplayName = "Parsing as Bytes Register (Casted)")]
public void TestRegisterBuildingByteRangeCasted() {
public void TestRegisterBuildingByteRangeCasted () {
var expect = new BytesRegister(303, 5);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsBytes(5).Build());
var expect = new BytesRegister(305, (uint)35);
Assert.Equal((uint)18, expect.AddressLength);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT305").AsBytes(35).Build());
}
[Fact(DisplayName = "Parsing as Bytes Register (Auto)")]
public void TestRegisterBuildingByteRangeAuto () {
var expect = new BytesRegister(300, (uint)20 * 2);
var actual = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT300-DT319").Build();
Assert.Equal((uint)20, expect.AddressLength);
Assert.Equivalent(expect, actual);
}
[Fact(DisplayName = "Parsing as Bit Array")]
public void TestRegisterBuildingBitArray () {
var expect1 = new BytesRegister(311, (ushort)5);
var expect2 = new BytesRegister(312, (ushort)16);
var expect3 = new BytesRegister(313, (ushort)32);
var actual1 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT311").AsBits(5).Build();
var actual2 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT312").AsBits(16).Build();
var actual3 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT313").AsBits(32).Build();
Assert.Equivalent(expect1, actual1);
Assert.Equivalent(expect2, actual2);
Assert.Equivalent(expect3, actual3);
Assert.Equal((uint)1, actual1.AddressLength);
Assert.Equal((uint)1, actual2.AddressLength);
Assert.Equal((uint)2, actual3.AddressLength);
}
[Fact(DisplayName = "Parsing as String Register")]
public void TestRegisterBuildingString () {
var expect1 = new StringRegister(314);
var actual1 = (StringRegister)RegBuilder.Factory.FromPlcRegName("DT314").AsType<string>().Build();
Assert.Equivalent(expect1, actual1);
Assert.Equal((uint)0, actual1.WordsSize);
}
}

View File

@@ -23,8 +23,8 @@ namespace MewtocolTests {
new NumberRegister<uint>(50),
new NumberRegister<float>(50),
new NumberRegister<TimeSpan>(50),
new BytesRegister(50, 30),
new BytesRegister(50, 31),
new BytesRegister(50, (uint)30),
new BytesRegister(50, (uint)31),
};
List<string> expectedIdents = new List<string> {
@@ -103,7 +103,7 @@ namespace MewtocolTests {
IRegisterInternal? reg = registers[i];
string expect = expcectedIdents[i];
Assert.Equal(expect, reg.GetRegisterPLCName());
Assert.Equal(expect, reg.GetMewName());
}