mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
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:
@@ -19,13 +19,20 @@ namespace MewtocolNet.Exceptions {
|
|||||||
|
|
||||||
internal static MewtocolException DupeRegister (IRegisterInternal register) {
|
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) {
|
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()}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,50 +14,6 @@ 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>
|
|
||||||
/// 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
|
#region Byte and string operation helpers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
89
MewtocolNet/Helpers/PlcFormat.cs
Normal file
89
MewtocolNet/Helpers/PlcFormat.cs
Normal 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"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -77,7 +77,14 @@ namespace MewtocolNet {
|
|||||||
/// <param name="withTerminator">Append the checksum and bcc automatically</param>
|
/// <param name="withTerminator">Append the checksum and bcc automatically</param>
|
||||||
/// <param name="timeoutMs">Timout to wait for a response</param>
|
/// <param name="timeoutMs">Timout to wait for a response</param>
|
||||||
/// <returns>Returns the result</returns>
|
/// <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>
|
/// <summary>
|
||||||
/// Use this to await the first poll iteration after connecting,
|
/// Use this to await the first poll iteration after connecting,
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ namespace MewtocolNet {
|
|||||||
private protected MewtocolInterface () {
|
private protected MewtocolInterface () {
|
||||||
|
|
||||||
Connected += MewtocolInterface_Connected;
|
Connected += MewtocolInterface_Connected;
|
||||||
|
RegisterChanged += OnRegisterChanged;
|
||||||
|
|
||||||
void MewtocolInterface_Connected(PLCInfo obj) {
|
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} " +
|
Logger.Log($"{asInternal.GetMewName()} " +
|
||||||
$"{(o.Name != null ? $"({o.Name}) " : "")}" +
|
$"{(o.Name != null ? $"({o.Name}) " : "")}" +
|
||||||
$"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this);
|
$"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this);
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,15 +195,15 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual string GetConnectionInfo() => throw new NotImplementedException();
|
public virtual string GetConnectionInfo () => throw new NotImplementedException();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <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
|
//send request
|
||||||
queuedMessages++;
|
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) {
|
if (await Task.WhenAny(tempResponse, Task.Delay(timeoutMs)) != tempResponse) {
|
||||||
// timeout logic
|
// 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 {
|
try {
|
||||||
|
|
||||||
@@ -236,13 +236,27 @@ namespace MewtocolNet {
|
|||||||
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
||||||
stream.Write(writeBuffer, 0, writeBuffer.Length);
|
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
|
//calc upstream speed
|
||||||
CalcUpstreamSpeed(writeBuffer.Length);
|
CalcUpstreamSpeed(writeBuffer.Length);
|
||||||
|
|
||||||
Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this);
|
Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this);
|
||||||
Logger.Log($"--> OUT MSG: {frame.Replace("\r", "(CR)")}", 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
|
//did not receive bytes but no errors, the com port was not configured right
|
||||||
if (readResult.Item1.Length == 0) {
|
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
|
//read total
|
||||||
List<byte> totalResponse = new List<byte>();
|
List<byte> totalResponse = new List<byte>();
|
||||||
@@ -303,6 +317,7 @@ namespace MewtocolNet {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
bool needsRead = false;
|
bool needsRead = false;
|
||||||
|
int readFrames = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
@@ -327,6 +342,15 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
if (commandRes == CommandState.RequestedNextFrame) {
|
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
|
//request next frame
|
||||||
var writeBuffer = Encoding.UTF8.GetBytes("%01**&\r");
|
var writeBuffer = Encoding.UTF8.GetBytes("%01**&\r");
|
||||||
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
||||||
@@ -334,6 +358,8 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readFrames++;
|
||||||
|
|
||||||
} while (needsRead);
|
} while (needsRead);
|
||||||
|
|
||||||
} catch (OperationCanceledException) { }
|
} 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 CR = '\r';
|
||||||
const char DELIMITER = '&';
|
const char DELIMITER = '&';
|
||||||
@@ -405,8 +431,8 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
private protected virtual void OnConnected (PLCInfo plcinf) {
|
private protected virtual void OnConnected (PLCInfo plcinf) {
|
||||||
|
|
||||||
Logger.Log("Connected", LogLevel.Info, this);
|
Logger.Log("Connected to PLC", LogLevel.Info, this);
|
||||||
Logger.Log($"\n\n{plcinf.ToString()}\n\n", LogLevel.Verbose, this);
|
Logger.Log($"{plcinf.ToString()}", LogLevel.Verbose, this);
|
||||||
|
|
||||||
IsConnected = true;
|
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 () {
|
private void SetUpstreamStopWatchStart () {
|
||||||
|
|
||||||
if (speedStopwatchUpstr == null) {
|
if (speedStopwatchUpstr == null) {
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
if((bool)register.Value != resultBitArray[k]) {
|
if((bool)register.Value != resultBitArray[k]) {
|
||||||
register.SetValueFromPLC(resultBitArray[k]);
|
register.SetValueFromPLC(resultBitArray[k]);
|
||||||
InvokeRegisterChanged(register);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -192,7 +191,6 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
if (lastVal != readout) {
|
if (lastVal != readout) {
|
||||||
rwReg.SetValueFromPLC(readout);
|
rwReg.SetValueFromPLC(readout);
|
||||||
InvokeRegisterChanged(reg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -218,16 +216,21 @@ namespace MewtocolNet {
|
|||||||
string propName = prop.Name;
|
string propName = prop.Name;
|
||||||
foreach (var attr in attributes) {
|
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;
|
var dotnetType = prop.PropertyType;
|
||||||
|
|
||||||
AddRegister(new RegisterBuildInfo {
|
AddRegister(new RegisterBuildInfo {
|
||||||
|
mewAddress = cAttribute.MewAddress,
|
||||||
memoryAddress = cAttribute.MemoryArea,
|
memoryAddress = cAttribute.MemoryArea,
|
||||||
specialAddress = cAttribute.SpecialAddress,
|
specialAddress = cAttribute.SpecialAddress,
|
||||||
memorySizeBytes = cAttribute.ByteLength,
|
memorySizeBytes = cAttribute.ByteLength,
|
||||||
registerType = cAttribute.RegisterType,
|
registerType = cAttribute.RegisterType,
|
||||||
dotnetCastType = dotnetType,
|
dotnetCastType = dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType,
|
||||||
collectionType = collection.GetType(),
|
collectionType = collection.GetType(),
|
||||||
name = prop.Name,
|
name = prop.Name,
|
||||||
});
|
});
|
||||||
@@ -345,6 +348,9 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#region Register Adding
|
#region Register Adding
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void AddRegister(IRegister register) => AddRegister(register as BaseRegister);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void AddRegister(BaseRegister register) {
|
public void AddRegister(BaseRegister register) {
|
||||||
|
|
||||||
@@ -354,14 +360,14 @@ namespace MewtocolNet {
|
|||||||
if (CheckDuplicateNameRegister(register))
|
if (CheckDuplicateNameRegister(register))
|
||||||
throw MewtocolException.DupeNameRegister(register);
|
throw MewtocolException.DupeNameRegister(register);
|
||||||
|
|
||||||
|
if (CheckOverlappingRegister(register, out var regB))
|
||||||
|
throw MewtocolException.OverlappingRegister(register, regB);
|
||||||
|
|
||||||
register.attachedInterface = this;
|
register.attachedInterface = this;
|
||||||
RegistersUnderlying.Add(register);
|
RegistersUnderlying.Add(register);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void AddRegister(IRegister register) => AddRegister(register as BaseRegister);
|
|
||||||
|
|
||||||
internal void AddRegister (RegisterBuildInfo buildInfo) {
|
internal void AddRegister (RegisterBuildInfo buildInfo) {
|
||||||
|
|
||||||
var builtRegister = buildInfo.Build();
|
var builtRegister = buildInfo.Build();
|
||||||
@@ -379,6 +385,9 @@ namespace MewtocolNet {
|
|||||||
if(CheckDuplicateNameRegister(builtRegister))
|
if(CheckDuplicateNameRegister(builtRegister))
|
||||||
throw MewtocolException.DupeNameRegister(builtRegister);
|
throw MewtocolException.DupeNameRegister(builtRegister);
|
||||||
|
|
||||||
|
if (CheckOverlappingRegister(builtRegister, out var regB))
|
||||||
|
throw MewtocolException.OverlappingRegister(builtRegister, regB);
|
||||||
|
|
||||||
builtRegister.attachedInterface = this;
|
builtRegister.attachedInterface = this;
|
||||||
RegistersUnderlying.Add(builtRegister);
|
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
|
#endregion
|
||||||
|
|
||||||
#region Register accessing
|
#region Register accessing
|
||||||
@@ -428,6 +469,18 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#region Event Invoking
|
#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) {
|
internal void PropertyRegisterWasSet(string propName, object value) {
|
||||||
|
|
||||||
_ = SetRegisterAsync(GetRegister(propName), value);
|
_ = SetRegisterAsync(GetRegister(propName), value);
|
||||||
|
|||||||
@@ -64,12 +64,8 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#region Operation mode changing
|
#region Operation mode changing
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Changes the PLCs operation mode to the given one
|
public async Task<bool> SetOperationModeAsync (bool setRun) {
|
||||||
/// </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) {
|
|
||||||
|
|
||||||
string modeChar = setRun ? "R" : "P";
|
string modeChar = setRun ? "R" : "P";
|
||||||
|
|
||||||
@@ -77,7 +73,7 @@ namespace MewtocolNet {
|
|||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
|
||||||
if (result.Success) {
|
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 {
|
} else {
|
||||||
Logger.Log("Operation mode change failed", LogLevel.Error, this);
|
Logger.Log("Operation mode change failed", LogLevel.Error, this);
|
||||||
}
|
}
|
||||||
@@ -116,14 +112,15 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </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="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, 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>();
|
var byteList = new List<byte>();
|
||||||
|
|
||||||
@@ -131,12 +128,13 @@ namespace MewtocolNet {
|
|||||||
if (count % 2 != 0)
|
if (count % 2 != 0)
|
||||||
wordLength++;
|
wordLength++;
|
||||||
|
|
||||||
|
int blockSize = 8;
|
||||||
|
|
||||||
//read blocks of max 4 words per msg
|
//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 curWordStart = start + i;
|
||||||
int curWordEnd = curWordStart + 7;
|
int curWordEnd = curWordStart + blockSize - 1;
|
||||||
|
|
||||||
string startStr = curWordStart.ToString().PadLeft(5, '0');
|
string startStr = curWordStart.ToString().PadLeft(5, '0');
|
||||||
string endStr = (curWordEnd).ToString().PadLeft(5, '0');
|
string endStr = (curWordEnd).ToString().PadLeft(5, '0');
|
||||||
@@ -146,7 +144,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
if (result.Success && !string.IsNullOrEmpty(result.Response)) {
|
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;
|
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
|
//returns a byte array 1 long and with the byte beeing 0 or 1
|
||||||
if (toWriteType == typeof(BoolRegister)) {
|
if (toWriteType == typeof(BoolRegister)) {
|
||||||
|
|
||||||
string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}";
|
string reqStr = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}";
|
||||||
var result = await SendCommandAsync(requeststring);
|
var res = await SendCommandAsync(reqStr);
|
||||||
return result.Success;
|
return res.Success;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//writes a byte array 2 bytes or 4 bytes long depending on the data size
|
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}";
|
||||||
if (toWriteType.IsGenericType && toWriteType.GetGenericTypeDefinition() == typeof(NumberRegister<>)) {
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
return result.Success;
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using MewtocolNet.RegisterBuilding;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace MewtocolNet.RegisterAttributes {
|
namespace MewtocolNet.RegisterAttributes {
|
||||||
|
|
||||||
@@ -10,31 +11,39 @@ namespace MewtocolNet.RegisterAttributes {
|
|||||||
|
|
||||||
internal RegisterType? RegisterType;
|
internal RegisterType? RegisterType;
|
||||||
|
|
||||||
internal int MemoryArea = 0;
|
internal uint MemoryArea = 0;
|
||||||
internal int ByteLength = 2;
|
internal uint ByteLength = 2;
|
||||||
internal byte SpecialAddress = 0x0;
|
internal byte SpecialAddress = 0x0;
|
||||||
|
|
||||||
internal BitCount BitCount;
|
internal BitCount BitCount;
|
||||||
internal int AssignedBitIndex = -1;
|
internal int AssignedBitIndex = -1;
|
||||||
|
|
||||||
|
internal string MewAddress = null;
|
||||||
|
|
||||||
|
public RegisterAttribute(string mewAddress) {
|
||||||
|
|
||||||
|
MewAddress = mewAddress;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attribute for string type or numeric registers
|
/// Attribute for string type or numeric registers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="memoryArea">The area in the plcs memory</param>
|
/// <param name="memoryArea">The area in the plcs memory</param>
|
||||||
public RegisterAttribute(int memoryArea) {
|
public RegisterAttribute(uint memoryArea) {
|
||||||
|
|
||||||
MemoryArea = memoryArea;
|
MemoryArea = memoryArea;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RegisterAttribute(int memoryArea, int byteLength) {
|
public RegisterAttribute(uint memoryArea, uint byteLength) {
|
||||||
|
|
||||||
MemoryArea = memoryArea;
|
MemoryArea = memoryArea;
|
||||||
ByteLength = byteLength;
|
ByteLength = byteLength;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RegisterAttribute(int memoryArea, BitCount bitCount) {
|
public RegisterAttribute(uint memoryArea, BitCount bitCount) {
|
||||||
|
|
||||||
MemoryArea = memoryArea;
|
MemoryArea = memoryArea;
|
||||||
BitCount = bitCount;
|
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;
|
MemoryArea = memoryArea;
|
||||||
BitCount = bitCount;
|
BitCount = bitCount;
|
||||||
@@ -68,7 +77,7 @@ namespace MewtocolNet.RegisterAttributes {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attribute for boolean registers
|
/// Attribute for boolean registers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RegisterAttribute(IOType type, int memoryArea, byte spAdress = 0x0) {
|
public RegisterAttribute(IOType type, uint memoryArea, byte spAdress = 0x0) {
|
||||||
|
|
||||||
MemoryArea = memoryArea;
|
MemoryArea = memoryArea;
|
||||||
RegisterType = (RegisterType)(int)type;
|
RegisterType = (RegisterType)(int)type;
|
||||||
|
|||||||
267
MewtocolNet/RegisterBuilding/BuilderStep.cs
Normal file
267
MewtocolNet/RegisterBuilding/BuilderStep.cs
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
public string hardFailReason;
|
public string hardFailReason;
|
||||||
|
|
||||||
public RegisterBuilderStep stepData;
|
public BuilderStep stepData;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
|
|
||||||
(x) => TryBuildBoolean(x),
|
(x) => TryBuildBoolean(x),
|
||||||
(x) => TryBuildNumericBased(x),
|
(x) => TryBuildNumericBased(x),
|
||||||
|
(x) => TryBuildByteRangeBased(x),
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
public static RegBuilder Factory { get; private set; } = new RegBuilder();
|
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) {
|
foreach (var method in parseMethods) {
|
||||||
|
|
||||||
@@ -45,6 +47,7 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
|
|
||||||
res.stepData.OriginalInput = plcAddrName;
|
res.stepData.OriginalInput = plcAddrName;
|
||||||
res.stepData.forInterface = forInterface;
|
res.stepData.forInterface = forInterface;
|
||||||
|
|
||||||
return res.stepData;
|
return res.stepData;
|
||||||
|
|
||||||
} else if(res.state == ParseResultState.FailedHard) {
|
} else if(res.state == ParseResultState.FailedHard) {
|
||||||
@@ -77,7 +80,7 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
string special = match.Groups["special"].Value;
|
string special = match.Groups["special"].Value;
|
||||||
|
|
||||||
IOType regType;
|
IOType regType;
|
||||||
int areaAdd = 0;
|
uint areaAdd = 0;
|
||||||
byte specialAdd = 0x0;
|
byte specialAdd = 0x0;
|
||||||
|
|
||||||
//try cast the prefix
|
//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 {
|
return new ParseResult {
|
||||||
state = ParseResultState.FailedHard,
|
state = ParseResultState.FailedHard,
|
||||||
@@ -102,7 +105,7 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
//special address not given
|
//special address not given
|
||||||
if(string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) {
|
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) {
|
if (isAreaInt && areaInt >= 0 && areaInt <= 9) {
|
||||||
|
|
||||||
@@ -142,7 +145,11 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
|
|
||||||
return new ParseResult {
|
return new ParseResult {
|
||||||
state = ParseResultState.Success,
|
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
|
// one to two word registers
|
||||||
private static ParseResult TryBuildNumericBased (string plcAddrName) {
|
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);
|
var match = patternByte.Match(plcAddrName);
|
||||||
|
|
||||||
@@ -159,12 +166,11 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
state = ParseResultState.FailedSoft
|
state = ParseResultState.FailedSoft
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
string prefix = match.Groups["prefix"].Value;
|
string prefix = match.Groups["prefix"].Value;
|
||||||
string area = match.Groups["area"].Value;
|
string area = match.Groups["area"].Value;
|
||||||
|
|
||||||
RegisterType regType;
|
RegisterType regType;
|
||||||
int areaAdd = 0;
|
uint areaAdd = 0;
|
||||||
|
|
||||||
//try cast the prefix
|
//try cast the prefix
|
||||||
if (!Enum.TryParse(prefix, out regType)) {
|
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 {
|
return new ParseResult {
|
||||||
state = ParseResultState.FailedHard,
|
state = ParseResultState.FailedHard,
|
||||||
@@ -187,7 +193,76 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
|
|
||||||
return new ParseResult {
|
return new ParseResult {
|
||||||
state = ParseResultState.Success,
|
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,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
using MewtocolNet.Registers;
|
using MewtocolNet.Exceptions;
|
||||||
|
using MewtocolNet.Registers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MewtocolNet.RegisterBuilding {
|
namespace MewtocolNet.RegisterBuilding {
|
||||||
|
|
||||||
internal struct RegisterBuildInfo {
|
internal struct RegisterBuildInfo {
|
||||||
|
|
||||||
|
internal string mewAddress;
|
||||||
|
|
||||||
internal string name;
|
internal string name;
|
||||||
internal int memoryAddress;
|
internal uint memoryAddress;
|
||||||
internal int memorySizeBytes;
|
|
||||||
|
internal uint? memorySizeBytes;
|
||||||
|
internal ushort? memorySizeBits;
|
||||||
internal byte? specialAddress;
|
internal byte? specialAddress;
|
||||||
|
|
||||||
internal RegisterType? registerType;
|
internal RegisterType? registerType;
|
||||||
@@ -18,10 +25,37 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
|
|
||||||
internal BaseRegister Build () {
|
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();
|
RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault();
|
||||||
|
|
||||||
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);
|
bool isStringRegister = !registerClassType.IsGenericType && registerClassType == typeof(StringRegister);
|
||||||
|
|
||||||
@@ -30,7 +64,7 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
//-------------------------------------------
|
//-------------------------------------------
|
||||||
//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 instance = new BytesRegister(memoryAddress, memorySizeBytes, name);
|
var instance = new BytesRegister(memoryAddress, memorySizeBytes.Value, name);
|
||||||
|
|
||||||
if (collectionType != null)
|
if (collectionType != null)
|
||||||
instance.WithCollectionType(collectionType);
|
instance.WithCollectionType(collectionType);
|
||||||
@@ -42,13 +76,11 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
//-------------------------------------------
|
//-------------------------------------------
|
||||||
//as numeric register
|
//as numeric register
|
||||||
|
|
||||||
var areaAddr = memoryAddress;
|
|
||||||
|
|
||||||
//create a new bregister instance
|
//create a new bregister instance
|
||||||
var flags = BindingFlags.Public | BindingFlags.Instance;
|
var flags = BindingFlags.Public | BindingFlags.Instance;
|
||||||
|
|
||||||
//int _adress, Type _enumType = null, string _name = null
|
//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);
|
var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null);
|
||||||
|
|
||||||
if(collectionType != null)
|
if(collectionType != null)
|
||||||
@@ -62,7 +94,14 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
|
|
||||||
//-------------------------------------------
|
//-------------------------------------------
|
||||||
//as byte range register
|
//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)
|
if (collectionType != null)
|
||||||
instance.WithCollectionType(collectionType);
|
instance.WithCollectionType(collectionType);
|
||||||
@@ -106,6 +145,12 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BaseRegister BuildFromMewAddress () {
|
||||||
|
|
||||||
|
return (BaseRegister)RegBuilder.Factory.FromPlcRegName(mewAddress, name).AsType(dotnetCastType).Build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -13,10 +13,10 @@ namespace MewtocolNet.Registers {
|
|||||||
public event Action<object> ValueChanged;
|
public event Action<object> ValueChanged;
|
||||||
|
|
||||||
internal MewtocolInterface attachedInterface;
|
internal MewtocolInterface attachedInterface;
|
||||||
internal object lastValue;
|
internal object lastValue = null;
|
||||||
internal Type collectionType;
|
internal Type collectionType;
|
||||||
internal string name;
|
internal string name;
|
||||||
internal int memoryAddress;
|
internal uint memoryAddress;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public MewtocolInterface AttachedInterface => attachedInterface;
|
public MewtocolInterface AttachedInterface => attachedInterface;
|
||||||
@@ -34,18 +34,16 @@ namespace MewtocolNet.Registers {
|
|||||||
public string Name => name;
|
public string Name => name;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string PLCAddressName => GetRegisterPLCName();
|
public string PLCAddressName => GetMewName();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int MemoryAddress => memoryAddress;
|
public uint MemoryAddress => memoryAddress;
|
||||||
|
|
||||||
#region Trigger update notify
|
#region Trigger update notify
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed);
|
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
|
||||||
|
|
||||||
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -53,9 +51,14 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
public virtual void SetValueFromPLC(object val) {
|
public virtual void SetValueFromPLC(object val) {
|
||||||
|
|
||||||
lastValue = val;
|
if(lastValue?.ToString() != val?.ToString()) {
|
||||||
TriggerChangedEvnt(this);
|
|
||||||
TriggerNotifyChange();
|
lastValue = val;
|
||||||
|
|
||||||
|
TriggerNotifyChange();
|
||||||
|
attachedInterface.InvokeRegisterChanged(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +93,11 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
public virtual string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
|
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
|
#endregion
|
||||||
|
|
||||||
@@ -102,14 +109,24 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
#endregion
|
#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) {
|
public virtual string ToString(bool additional) {
|
||||||
|
|
||||||
if (!additional) return this.ToString();
|
if (!additional) return this.ToString();
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.AppendLine($"PLC Naming: {GetRegisterPLCName()}");
|
sb.AppendLine($"PLC Naming: {GetMewName()}");
|
||||||
sb.AppendLine($"Name: {Name ?? "Not named"}");
|
sb.AppendLine($"Name: {Name ?? "Not named"}");
|
||||||
sb.AppendLine($"Value: {GetValueString()}");
|
sb.AppendLine($"Value: {GetValueString()}");
|
||||||
sb.AppendLine($"Register Type: {RegisterType}");
|
sb.AppendLine($"Register Type: {RegisterType}");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -25,21 +26,9 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <param name="_name">The custom name</param>
|
/// <param name="_name">The custom name</param>
|
||||||
/// <exception cref="NotSupportedException"></exception>
|
/// <exception cref="NotSupportedException"></exception>
|
||||||
/// <exception cref="Exception"></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)
|
lastValue = null;
|
||||||
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;
|
|
||||||
|
|
||||||
memoryAddress = _areaAdress;
|
memoryAddress = _areaAdress;
|
||||||
specialAddress = _spAddress;
|
specialAddress = _spAddress;
|
||||||
@@ -47,18 +36,27 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
RegisterType = (RegisterType)(int)_io;
|
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
|
#region Read / Write
|
||||||
|
|
||||||
public override void SetValueFromPLC(object val) {
|
|
||||||
|
|
||||||
lastValue = (bool)val;
|
|
||||||
TriggerChangedEvnt(this);
|
|
||||||
TriggerNotifyChange();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<object> ReadAsync() {
|
public override async Task<object> ReadAsync() {
|
||||||
|
|
||||||
@@ -111,7 +109,7 @@ namespace MewtocolNet.Registers {
|
|||||||
public override void ClearValue() => SetValueFromPLC(false);
|
public override void ClearValue() => SetValueFromPLC(false);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string GetRegisterPLCName() {
|
public override string GetMewName() {
|
||||||
|
|
||||||
var spAdressEnd = SpecialAddress.ToString("X1");
|
var spAdressEnd = SpecialAddress.ToString("X1");
|
||||||
|
|
||||||
@@ -131,13 +129,16 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override uint GetRegisterAddressLen () => 1;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string ToString(bool additional) {
|
public override string ToString(bool additional) {
|
||||||
|
|
||||||
if (!additional) return this.ToString();
|
if (!additional) return this.ToString();
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.AppendLine($"PLC Naming: {GetRegisterPLCName()}");
|
sb.AppendLine($"PLC Naming: {GetMewName()}");
|
||||||
sb.AppendLine($"Name: {Name ?? "Not named"}");
|
sb.AppendLine($"Name: {Name ?? "Not named"}");
|
||||||
sb.AppendLine($"Value: {GetValueString()}");
|
sb.AppendLine($"Value: {GetValueString()}");
|
||||||
sb.AppendLine($"Register Type: {RegisterType}");
|
sb.AppendLine($"Register Type: {RegisterType}");
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -12,35 +13,75 @@ namespace MewtocolNet.Registers {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class BytesRegister : BaseRegister {
|
public class BytesRegister : BaseRegister {
|
||||||
|
|
||||||
internal int addressLength;
|
internal uint addressLength;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The rgisters memory length
|
/// The rgisters memory length
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Defines a register containing a string
|
/// Defines a register containing bytes
|
||||||
/// </summary>
|
/// </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;
|
name = _name;
|
||||||
memoryAddress = _address;
|
memoryAddress = _address;
|
||||||
ReservedSize = (short)_reservedByteSize;
|
ReservedBytesSize = _reservedByteSize;
|
||||||
|
|
||||||
//calc mem length
|
//calc mem length
|
||||||
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
|
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
|
||||||
var byteSize = _reservedByteSize;
|
var byteSize = ReservedBytesSize;
|
||||||
if (_reservedByteSize % 2 != 0) byteSize++;
|
if (ReservedBytesSize % 2 != 0) byteSize++;
|
||||||
|
|
||||||
RegisterType = RegisterType.DT_BYTE_RANGE;
|
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/>
|
/// <inheritdoc/>
|
||||||
public override string BuildMewtocolQuery() {
|
public override string BuildMewtocolQuery() {
|
||||||
@@ -48,7 +89,7 @@ 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 + AddressLength - 1).ToString().PadLeft(5, '0'));
|
||||||
|
|
||||||
return asciistring.ToString();
|
return asciistring.ToString();
|
||||||
}
|
}
|
||||||
@@ -56,10 +97,20 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void SetValueFromPLC (object val) {
|
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);
|
bool changeTriggerGeneral = (lastValue?.ToString() != val?.ToString());
|
||||||
TriggerNotifyChange();
|
|
||||||
|
if (changeTriggerBitArr || changeTriggerGeneral) {
|
||||||
|
|
||||||
|
lastValue = val;
|
||||||
|
|
||||||
|
TriggerNotifyChange();
|
||||||
|
attachedInterface.InvokeRegisterChanged(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +120,9 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void ClearValue() => SetValueFromPLC(null);
|
public override void ClearValue() => SetValueFromPLC(null);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override uint GetRegisterAddressLen() => AddressLength;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<object> ReadAsync() {
|
public override async Task<object> ReadAsync() {
|
||||||
|
|
||||||
@@ -77,7 +131,13 @@ namespace MewtocolNet.Registers {
|
|||||||
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
||||||
if (read == null) return null;
|
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);
|
SetValueFromPLC(parsed);
|
||||||
return parsed;
|
return parsed;
|
||||||
@@ -89,8 +149,17 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
if (!attachedInterface.IsConnected) return false;
|
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);
|
if (res) SetValueFromPLC(data);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using MewtocolNet.Registers;
|
||||||
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ namespace MewtocolNet {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The plc memory address of the register
|
/// The plc memory address of the register
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int MemoryAddress { get; }
|
uint MemoryAddress { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value of the register as the plc representation string
|
/// Gets the value of the register as the plc representation string
|
||||||
@@ -67,7 +68,6 @@ namespace MewtocolNet {
|
|||||||
/// <returns>True if successfully set</returns>
|
/// <returns>True if successfully set</returns>
|
||||||
Task<bool> WriteAsync(object data);
|
Task<bool> WriteAsync(object data);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
object Value { get; }
|
object Value { get; }
|
||||||
|
|
||||||
int MemoryAddress { get; }
|
uint MemoryAddress { get; }
|
||||||
|
|
||||||
// setters
|
// setters
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
string GetContainerName();
|
string GetContainerName();
|
||||||
|
|
||||||
string GetRegisterPLCName();
|
string GetMewName();
|
||||||
|
|
||||||
byte? GetSpecialAddress();
|
byte? GetSpecialAddress();
|
||||||
|
|
||||||
@@ -47,6 +47,9 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
string BuildMewtocolQuery();
|
string BuildMewtocolQuery();
|
||||||
|
|
||||||
|
uint GetRegisterAddressLen();
|
||||||
|
|
||||||
|
string GetRegisterWordRangeString();
|
||||||
|
|
||||||
//others
|
//others
|
||||||
|
|
||||||
|
|||||||
@@ -14,66 +14,63 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <typeparam name="T">The type of the numeric value</typeparam>
|
/// <typeparam name="T">The type of the numeric value</typeparam>
|
||||||
public class NumberRegister<T> : BaseRegister {
|
public class NumberRegister<T> : BaseRegister {
|
||||||
|
|
||||||
internal Type enumType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a register containing a number
|
/// Defines a register containing a number
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="_address">Memory start adress max 99999</param>
|
/// <param name="_address">Memory start adress max 99999</param>
|
||||||
/// <param name="_name">Name of the register</param>
|
/// <param name="_name">Name of the register</param>
|
||||||
public NumberRegister (int _address, string _name = null) {
|
public NumberRegister (uint _address, string _name = null) {
|
||||||
|
|
||||||
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
|
||||||
|
|
||||||
memoryAddress = _address;
|
memoryAddress = _address;
|
||||||
name = _name;
|
name = _name;
|
||||||
|
|
||||||
Type numType = typeof(T);
|
Type numType = typeof(T);
|
||||||
|
uint areaLen = 0;
|
||||||
|
|
||||||
var allowedTypes = PlcValueParser.GetAllowDotnetTypes();
|
if (typeof(T).IsEnum) {
|
||||||
if (!allowedTypes.Contains(numType))
|
|
||||||
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
|
|
||||||
|
|
||||||
var areaLen = (Marshal.SizeOf(numType) / 2) - 1;
|
//for enums
|
||||||
RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
|
|
||||||
|
|
||||||
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>
|
lastValue = null;
|
||||||
/// Defines a register containing a number
|
Console.WriteLine();
|
||||||
/// </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) {
|
|
||||||
|
|
||||||
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
} else {
|
||||||
|
|
||||||
memoryAddress = _address;
|
//for all others known pre-defined numeric structs
|
||||||
name = _name;
|
|
||||||
|
|
||||||
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();
|
areaLen = (uint)(Marshal.SizeOf(numType) / 2) - 1;
|
||||||
if (!allowedTypes.Contains(numType))
|
RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
|
||||||
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
|
|
||||||
|
|
||||||
var areaLen = (Marshal.SizeOf(numType) / 2) - 1;
|
lastValue = null;
|
||||||
RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
|
|
||||||
|
|
||||||
enumType = _enumType;
|
}
|
||||||
lastValue = default(T);
|
|
||||||
|
CheckAddressOverflow(memoryAddress, areaLen);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void SetValueFromPLC(object val) {
|
public override void SetValueFromPLC(object val) {
|
||||||
|
|
||||||
lastValue = (T)val;
|
if (lastValue?.ToString() != val?.ToString()) {
|
||||||
TriggerChangedEvnt(this);
|
|
||||||
TriggerNotifyChange();
|
lastValue = (T)val;
|
||||||
|
|
||||||
|
TriggerNotifyChange();
|
||||||
|
attachedInterface.InvokeRegisterChanged(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,71 +90,42 @@ namespace MewtocolNet.Registers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string GetAsPLC() => ((TimeSpan)Value).AsPLCTime();
|
public override string GetAsPLC() {
|
||||||
|
|
||||||
/// <inheritdoc/>
|
if (typeof(T) == typeof(TimeSpan)) return ((TimeSpan)Value).ToPlcTime();
|
||||||
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)";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value.ToString();
|
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/>
|
/// <inheritdoc/>
|
||||||
public override void ClearValue() => SetValueFromPLC(default(T));
|
public override void ClearValue() => SetValueFromPLC(default(T));
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<object> ReadAsync() {
|
public override async Task<object> ReadAsync() {
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
using System;
|
using MewtocolNet.Exceptions;
|
||||||
|
using MewtocolNet.Logging;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -16,20 +19,23 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
internal short UsedSize { get; set; }
|
internal short UsedSize { get; set; }
|
||||||
|
|
||||||
internal int WordsSize { get; set; }
|
internal uint WordsSize { get; set; }
|
||||||
|
|
||||||
private bool isCalibrated = false;
|
private bool isCalibratedFromPlc = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a register containing a string
|
/// Defines a register containing a string
|
||||||
/// </summary>
|
/// </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;
|
name = _name;
|
||||||
memoryAddress = _address;
|
memoryAddress = _address;
|
||||||
RegisterType = RegisterType.DT_BYTE_RANGE;
|
RegisterType = RegisterType.DT_BYTE_RANGE;
|
||||||
|
|
||||||
|
CheckAddressOverflow(memoryAddress, 0);
|
||||||
|
|
||||||
|
lastValue = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -38,7 +44,7 @@ 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 + WordsSize - 1).ToString().PadLeft(5, '0'));
|
asciistring.Append((MemoryAddress + Math.Max(1, WordsSize) - 1).ToString().PadLeft(5, '0'));
|
||||||
|
|
||||||
return asciistring.ToString();
|
return asciistring.ToString();
|
||||||
}
|
}
|
||||||
@@ -49,10 +55,14 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void SetValueFromPLC (object val) {
|
public override void SetValueFromPLC (object val) {
|
||||||
|
|
||||||
lastValue = (string)val;
|
if (!val.Equals(lastValue)) {
|
||||||
|
|
||||||
TriggerChangedEvnt(this);
|
lastValue = (string)val;
|
||||||
TriggerNotifyChange();
|
|
||||||
|
TriggerNotifyChange();
|
||||||
|
attachedInterface.InvokeRegisterChanged(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +72,9 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void ClearValue() => SetValueFromPLC("");
|
public override void ClearValue() => SetValueFromPLC("");
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<object> ReadAsync() {
|
public override async Task<object> ReadAsync() {
|
||||||
|
|
||||||
@@ -69,25 +82,39 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
//get the string params first
|
//get the string params first
|
||||||
|
|
||||||
if(!isCalibrated) await Calibrate();
|
if(!isCalibratedFromPlc) await CalibrateFromPLC();
|
||||||
|
|
||||||
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
||||||
if (read == null) return null;
|
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
|
//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);
|
ReservedSize = BitConverter.ToInt16(bytes, 0);
|
||||||
UsedSize = BitConverter.ToInt16(bytes, 2);
|
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 (!attachedInterface.IsConnected) return false;
|
||||||
|
|
||||||
|
if (!isCalibratedFromPlc) {
|
||||||
|
|
||||||
|
//try to calibrate from plc
|
||||||
|
await CalibrateFromPLC();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var res = await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (string)data));
|
var res = await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (string)data));
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
UsedSize = (short)((string)Value).Length;
|
|
||||||
SetValueFromPLC(data);
|
SetValueFromPLC(data);
|
||||||
|
UsedSize = (short)((string)Value).Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
internal static List<IPlcTypeConverter> items = new List<IPlcTypeConverter> {
|
internal static List<IPlcTypeConverter> items = new List<IPlcTypeConverter> {
|
||||||
|
|
||||||
|
//default bool R conversion
|
||||||
new PlcTypeConversion<bool>(RegisterType.R) {
|
new PlcTypeConversion<bool>(RegisterType.R) {
|
||||||
HoldingRegisterType = typeof(BoolRegister),
|
HoldingRegisterType = typeof(BoolRegister),
|
||||||
PlcVarType = PlcVarType.BOOL,
|
PlcVarType = PlcVarType.BOOL,
|
||||||
@@ -43,6 +44,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
//default bool X conversion
|
||||||
new PlcTypeConversion<bool>(RegisterType.X) {
|
new PlcTypeConversion<bool>(RegisterType.X) {
|
||||||
HoldingRegisterType = typeof(BoolRegister),
|
HoldingRegisterType = typeof(BoolRegister),
|
||||||
PlcVarType = PlcVarType.BOOL,
|
PlcVarType = PlcVarType.BOOL,
|
||||||
@@ -56,6 +58,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
//default bool Y conversion
|
||||||
new PlcTypeConversion<bool>(RegisterType.Y) {
|
new PlcTypeConversion<bool>(RegisterType.Y) {
|
||||||
HoldingRegisterType = typeof(BoolRegister),
|
HoldingRegisterType = typeof(BoolRegister),
|
||||||
PlcVarType = PlcVarType.BOOL,
|
PlcVarType = PlcVarType.BOOL,
|
||||||
@@ -69,6 +72,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
//default short DT conversion
|
||||||
new PlcTypeConversion<short>(RegisterType.DT) {
|
new PlcTypeConversion<short>(RegisterType.DT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<short>),
|
HoldingRegisterType = typeof(NumberRegister<short>),
|
||||||
PlcVarType = PlcVarType.INT,
|
PlcVarType = PlcVarType.INT,
|
||||||
@@ -82,6 +86,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
//default ushort DT conversion
|
||||||
new PlcTypeConversion<ushort>(RegisterType.DT) {
|
new PlcTypeConversion<ushort>(RegisterType.DT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<ushort>),
|
HoldingRegisterType = typeof(NumberRegister<ushort>),
|
||||||
PlcVarType = PlcVarType.UINT,
|
PlcVarType = PlcVarType.UINT,
|
||||||
@@ -95,6 +100,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
//default int DDT conversion
|
||||||
new PlcTypeConversion<int>(RegisterType.DDT) {
|
new PlcTypeConversion<int>(RegisterType.DDT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<int>),
|
HoldingRegisterType = typeof(NumberRegister<int>),
|
||||||
PlcVarType = PlcVarType.DINT,
|
PlcVarType = PlcVarType.DINT,
|
||||||
@@ -108,6 +114,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
//default uint DDT conversion
|
||||||
new PlcTypeConversion<uint>(RegisterType.DDT) {
|
new PlcTypeConversion<uint>(RegisterType.DDT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<uint>),
|
HoldingRegisterType = typeof(NumberRegister<uint>),
|
||||||
PlcVarType = PlcVarType.UDINT,
|
PlcVarType = PlcVarType.UDINT,
|
||||||
@@ -121,6 +128,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
//default float DDT conversion
|
||||||
new PlcTypeConversion<float>(RegisterType.DDT) {
|
new PlcTypeConversion<float>(RegisterType.DDT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<float>),
|
HoldingRegisterType = typeof(NumberRegister<float>),
|
||||||
PlcVarType = PlcVarType.REAL,
|
PlcVarType = PlcVarType.REAL,
|
||||||
@@ -139,6 +147,7 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
//default TimeSpan DDT conversion
|
||||||
new PlcTypeConversion<TimeSpan>(RegisterType.DDT) {
|
new PlcTypeConversion<TimeSpan>(RegisterType.DDT) {
|
||||||
HoldingRegisterType = typeof(NumberRegister<TimeSpan>),
|
HoldingRegisterType = typeof(NumberRegister<TimeSpan>),
|
||||||
PlcVarType = PlcVarType.TIME,
|
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),
|
HoldingRegisterType = typeof(BytesRegister),
|
||||||
FromRaw = (reg, bytes) => bytes,
|
FromRaw = (reg, bytes) => bytes,
|
||||||
ToRaw = (reg, value) => value,
|
ToRaw = (reg, value) => value,
|
||||||
},
|
},
|
||||||
|
//default string DT Range conversion
|
||||||
new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) {
|
new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) {
|
||||||
HoldingRegisterType = typeof(StringRegister),
|
HoldingRegisterType = typeof(StringRegister),
|
||||||
PlcVarType = PlcVarType.STRING,
|
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),
|
HoldingRegisterType = typeof(BytesRegister),
|
||||||
PlcVarType = PlcVarType.WORD,
|
|
||||||
FromRaw = (reg, bytes) => {
|
FromRaw = (reg, bytes) => {
|
||||||
|
|
||||||
|
var byteReg = (BytesRegister)reg;
|
||||||
|
|
||||||
BitArray bitAr = new BitArray(bytes);
|
BitArray bitAr = new BitArray(bytes);
|
||||||
|
bitAr.Length = (int)byteReg.ReservedBitSize;
|
||||||
|
|
||||||
return bitAr;
|
return bitAr;
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -208,6 +223,15 @@ namespace MewtocolNet.TypeConversion {
|
|||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
if(ret.Length % 2 != 0) {
|
||||||
|
|
||||||
|
var lst = ret.ToList();
|
||||||
|
lst.Add(0);
|
||||||
|
ret = lst.ToArray();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,9 +12,22 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
private static List<IPlcTypeConverter> conversions => Conversions.items;
|
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)
|
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");
|
||||||
@@ -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)
|
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");
|
||||||
|
|||||||
@@ -22,18 +22,24 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
internal static bool IsAllowedPlcCastingType<T>() {
|
internal static bool IsAllowedPlcCastingType<T>() {
|
||||||
|
|
||||||
|
if (typeof(T).IsEnum) return true;
|
||||||
|
|
||||||
return allowedCastingTypes.Contains(typeof(T));
|
return allowedCastingTypes.Contains(typeof(T));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsAllowedPlcCastingType(this Type type) {
|
internal static bool IsAllowedPlcCastingType(this Type type) {
|
||||||
|
|
||||||
|
if (type.IsEnum) return true;
|
||||||
|
|
||||||
return allowedCastingTypes.Contains(type);
|
return allowedCastingTypes.Contains(type);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static RegisterType ToRegisterTypeDefault(this Type type) {
|
internal static RegisterType ToRegisterTypeDefault(this Type type) {
|
||||||
|
|
||||||
|
if (type.IsEnum) return RegisterType.DT;
|
||||||
|
|
||||||
var found = PlcValueParser.GetDefaultRegisterType(type);
|
var found = PlcValueParser.GetDefaultRegisterType(type);
|
||||||
|
|
||||||
if (found != null) {
|
if (found != null) {
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using MewtocolNet;
|
using MewtocolNet;
|
||||||
using MewtocolNet.RegisterAttributes;
|
|
||||||
using MewtocolNet.Registers;
|
using MewtocolNet.Registers;
|
||||||
using System.Collections;
|
using MewtocolTests.EncapsulatedTests;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace MewtocolTests {
|
namespace MewtocolTests {
|
||||||
|
|
||||||
public class AutomatedPropertyRegisters {
|
public partial class AutomatedPropertyRegisters {
|
||||||
|
|
||||||
private readonly ITestOutputHelper output;
|
private readonly ITestOutputHelper output;
|
||||||
|
|
||||||
@@ -15,90 +14,14 @@ namespace MewtocolTests {
|
|||||||
this.output = output;
|
this.output = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestRegisterCollection : RegisterCollection {
|
private void Test(IRegisterInternal reg, string propName, uint expectAddr, string expectPlcName) {
|
||||||
|
|
||||||
//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) {
|
|
||||||
|
|
||||||
Assert.NotNull(reg);
|
Assert.NotNull(reg);
|
||||||
Assert.Equal(propName, reg.Name);
|
Assert.Equal(propName, reg.Name);
|
||||||
Assert.Equal(expectValue, reg.Value);
|
Assert.Null(reg.Value);
|
||||||
|
|
||||||
Assert.Equal(expectAddr, reg.MemoryAddress);
|
Assert.Equal(expectAddr, reg.MemoryAddress);
|
||||||
Assert.Equal(expectPlcName, reg.GetRegisterPLCName());
|
Assert.Equal(expectPlcName, reg.GetMewName());
|
||||||
|
|
||||||
output.WriteLine(reg.ToString());
|
output.WriteLine(reg.ToString());
|
||||||
|
|
||||||
@@ -106,107 +29,86 @@ namespace MewtocolTests {
|
|||||||
|
|
||||||
//actual tests
|
//actual tests
|
||||||
|
|
||||||
[Fact(DisplayName = "Boolean R generation")]
|
[Fact(DisplayName = "Boolean generation")]
|
||||||
public void BooleanGen() {
|
public void BooleanGen() {
|
||||||
|
|
||||||
var interf = Mewtocol.Ethernet("192.168.0.1");
|
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
|
var register3 = interf.GetRegister(nameof(TestBoolRegisters.RType_MewString));
|
||||||
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestBool1), false, 85, "R85");
|
|
||||||
|
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")]
|
[Fact(DisplayName = "Number 16 bit generation")]
|
||||||
public void BooleanInputGen() {
|
public void N16BitGen () {
|
||||||
|
|
||||||
var interf = Mewtocol.Ethernet("192.168.0.1");
|
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
|
//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")]
|
[Fact(DisplayName = "Number 32 bit generation")]
|
||||||
public void Int16Gen() {
|
public void N32BitGen () {
|
||||||
|
|
||||||
var interf = Mewtocol.Ethernet("192.168.0.1");
|
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
|
//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")]
|
[Fact(DisplayName = "String generation")]
|
||||||
public void UInt16Gen() {
|
public void StringGen() {
|
||||||
|
|
||||||
var interf = Mewtocol.Ethernet("192.168.0.1");
|
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
|
//test generic properties
|
||||||
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestUInt16), (ushort)0, 342, "DT342");
|
Test((IRegisterInternal)register1, nameof(TestStringRegisters.StringType), 7005, "DT7005");
|
||||||
|
Test((IRegisterInternal)register2, nameof(TestStringRegisters.StringType_MewString), 7050, "DT7050");
|
||||||
}
|
|
||||||
|
|
||||||
[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");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ internal class RegisterReadWriteTest {
|
|||||||
|
|
||||||
public object IntialValue { get; set; }
|
public object IntialValue { get; set; }
|
||||||
|
|
||||||
|
public object IntermediateValue { get; set; }
|
||||||
|
|
||||||
public object AfterWriteValue { get; set; }
|
public object AfterWriteValue { get; set; }
|
||||||
|
|
||||||
public string RegisterPlcAddressName { get; set; }
|
public string RegisterPlcAddressName { get; set; }
|
||||||
|
|||||||
115
MewtocolTests/EncapsulatedTests/TestRegisterCollection.cs
Normal file
115
MewtocolTests/EncapsulatedTests/TestRegisterCollection.cs
Normal 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; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ namespace MewtocolTests {
|
|||||||
this.output = output;
|
this.output = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(DisplayName = nameof(MewtocolHelpers.ToBitString))]
|
[Fact(DisplayName = nameof(PlcFormat.ToBitString))]
|
||||||
public void ToBitStringGeneration() {
|
public void ToBitStringGeneration() {
|
||||||
|
|
||||||
var bitarr = new BitArray(16);
|
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)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using MewtocolNet.Logging;
|
|||||||
using MewtocolNet.RegisterBuilding;
|
using MewtocolNet.RegisterBuilding;
|
||||||
using MewtocolNet.Registers;
|
using MewtocolNet.Registers;
|
||||||
using MewtocolTests.EncapsulatedTests;
|
using MewtocolTests.EncapsulatedTests;
|
||||||
|
using System.Collections;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
@@ -41,15 +42,51 @@ namespace MewtocolTests
|
|||||||
new RegisterReadWriteTest {
|
new RegisterReadWriteTest {
|
||||||
TargetRegister = new BoolRegister(IOType.R, 0xA, 10),
|
TargetRegister = new BoolRegister(IOType.R, 0xA, 10),
|
||||||
RegisterPlcAddressName = "R10A",
|
RegisterPlcAddressName = "R10A",
|
||||||
IntialValue = false,
|
IntermediateValue = false,
|
||||||
AfterWriteValue = true,
|
AfterWriteValue = true,
|
||||||
},
|
},
|
||||||
new RegisterReadWriteTest {
|
new RegisterReadWriteTest {
|
||||||
TargetRegister = new NumberRegister<short>(3000),
|
TargetRegister = new NumberRegister<short>(3000),
|
||||||
RegisterPlcAddressName = "DT3000",
|
RegisterPlcAddressName = "DT3000",
|
||||||
IntialValue = (short)0,
|
IntermediateValue = (short)0,
|
||||||
AfterWriteValue = (short)-513,
|
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;
|
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() {
|
public async void TestClientConnection() {
|
||||||
|
|
||||||
foreach (var plc in testPlcInformationData) {
|
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() {
|
public async void TestClientReadPLCStatus() {
|
||||||
|
|
||||||
foreach (var plc in testPlcInformationData) {
|
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() {
|
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();
|
foreach (var testRW in testRegisterRW) {
|
||||||
Assert.True(client.IsConnected);
|
|
||||||
|
|
||||||
foreach (var testRW in testRegisterRW) {
|
client.AddRegister(testRW.TargetRegister);
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using MewtocolNet;
|
using MewtocolNet;
|
||||||
using MewtocolNet.RegisterBuilding;
|
using MewtocolNet.RegisterBuilding;
|
||||||
using MewtocolNet.Registers;
|
using MewtocolNet.Registers;
|
||||||
|
using MewtocolTests.EncapsulatedTests;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
@@ -162,12 +163,14 @@ public class TestRegisterBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact(DisplayName = "Parsing as Number Register (Casted)")]
|
[Fact(DisplayName = "Parsing as Number Register (Casted)")]
|
||||||
public void TestRegisterBuildingNumericCasted() {
|
public void TestRegisterBuildingNumericCasted () {
|
||||||
|
|
||||||
var expect = new NumberRegister<short>(303, null);
|
var expect = new NumberRegister<short>(303);
|
||||||
var expect2 = new NumberRegister<int>(10002, null);
|
var expect2 = new NumberRegister<int>(10002);
|
||||||
var expect3 = new NumberRegister<TimeSpan>(400, null);
|
var expect3 = new NumberRegister<float>(404);
|
||||||
//var expect4 = new NRegister<TimeSpan>(103, null, true);
|
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").AsPlcType(PlcVarType.INT).Build());
|
||||||
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsType<short>().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").AsPlcType(PlcVarType.DINT).Build());
|
||||||
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsType<int>().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("DDT404").AsPlcType(PlcVarType.REAL).Build());
|
||||||
Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT400").AsType<TimeSpan>().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)")]
|
[Fact(DisplayName = "Parsing as Number Register (Auto)")]
|
||||||
public void TestRegisterBuildingNumericAuto() {
|
public void TestRegisterBuildingNumericAuto () {
|
||||||
|
|
||||||
var expect = new NumberRegister<short>(303, null);
|
var expect = new NumberRegister<short>(201);
|
||||||
var expect2 = new NumberRegister<int>(10002, null);
|
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());
|
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").Build());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(DisplayName = "Parsing as Bytes Register (Casted)")]
|
[Fact(DisplayName = "Parsing as Bytes Register (Casted)")]
|
||||||
public void TestRegisterBuildingByteRangeCasted() {
|
public void TestRegisterBuildingByteRangeCasted () {
|
||||||
|
|
||||||
var expect = new BytesRegister(303, 5);
|
var expect = new BytesRegister(305, (uint)35);
|
||||||
|
|
||||||
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsBytes(5).Build());
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ namespace MewtocolTests {
|
|||||||
new NumberRegister<uint>(50),
|
new NumberRegister<uint>(50),
|
||||||
new NumberRegister<float>(50),
|
new NumberRegister<float>(50),
|
||||||
new NumberRegister<TimeSpan>(50),
|
new NumberRegister<TimeSpan>(50),
|
||||||
new BytesRegister(50, 30),
|
new BytesRegister(50, (uint)30),
|
||||||
new BytesRegister(50, 31),
|
new BytesRegister(50, (uint)31),
|
||||||
};
|
};
|
||||||
|
|
||||||
List<string> expectedIdents = new List<string> {
|
List<string> expectedIdents = new List<string> {
|
||||||
@@ -103,7 +103,7 @@ namespace MewtocolTests {
|
|||||||
IRegisterInternal? reg = registers[i];
|
IRegisterInternal? reg = registers[i];
|
||||||
string expect = expcectedIdents[i];
|
string expect = expcectedIdents[i];
|
||||||
|
|
||||||
Assert.Equal(expect, reg.GetRegisterPLCName());
|
Assert.Equal(expect, reg.GetMewName());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user