mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Simplify registers
- add array conversion - fixed dt area merging
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.ComCassette {
|
||||
namespace MewtocolNet.ComCassette {
|
||||
|
||||
/// <summary>
|
||||
/// Needs a list of all status codes.. hard to reverse engineer
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
/// <summary>
|
||||
/// A DWord is a 16 bit value of 2 bytes
|
||||
/// </summary>
|
||||
public struct DWord : MewtocolExtensionTypeDDT {
|
||||
public struct DWord : MewtocolExtTypeInit2Word {
|
||||
|
||||
private int bitLength;
|
||||
|
||||
@@ -112,6 +107,8 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
public int GetIntialPlcByteSize() => 4;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace MewtocolNet {
|
||||
|
||||
internal interface MewtocolExtensionTypeDT { }
|
||||
internal interface MewtocolExtTypeInit1Word { }
|
||||
|
||||
internal interface MewtocolExtensionTypeDDT { }
|
||||
internal interface MewtocolExtTypeInit2Word { }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
/// <summary>
|
||||
/// A word is a 16 bit value of 2 bytes
|
||||
/// </summary>
|
||||
public struct Word : MewtocolExtensionTypeDT {
|
||||
public struct Word : MewtocolExtTypeInit1Word {
|
||||
|
||||
private int bitLength;
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.Documentation {
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.Documentation {
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.Documentation {
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
|
||||
namespace MewtocolNet.Exceptions {
|
||||
|
||||
@@ -23,19 +22,19 @@ namespace MewtocolNet.Exceptions {
|
||||
|
||||
}
|
||||
|
||||
internal static MewtocolException DupeRegister (IRegisterInternal register) {
|
||||
internal static MewtocolException DupeRegister(Register register) {
|
||||
|
||||
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}");
|
||||
|
||||
}
|
||||
|
||||
internal static MewtocolException DupeNameRegister (IRegisterInternal register) {
|
||||
internal static MewtocolException DupeNameRegister(Register register) {
|
||||
|
||||
return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.GetMewName()}");
|
||||
|
||||
}
|
||||
|
||||
internal static MewtocolException OverlappingRegister (IRegisterInternal registerA, IRegisterInternal registerB) {
|
||||
internal static MewtocolException OverlappingRegister(Register registerA, Register registerB) {
|
||||
|
||||
return new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " +
|
||||
$"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}");
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace MewtocolNet.Helpers {
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.Helpers {
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using static MewtocolNet.RegisterBuilding.RBuild;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -19,6 +19,23 @@ namespace MewtocolNet {
|
||||
|
||||
#region Byte and string operation helpers
|
||||
|
||||
public static int DetermineTypeByteSize(this Type type) {
|
||||
|
||||
//enums can only be of numeric types
|
||||
if (type.IsEnum) return Marshal.SizeOf(Enum.GetUnderlyingType(type));
|
||||
|
||||
//strings get always set with 4 bytes because the first 4 bytes contain the length
|
||||
if (type == typeof(string)) return 4;
|
||||
|
||||
if (type.Namespace.StartsWith("System")) return Marshal.SizeOf(type);
|
||||
|
||||
if (typeof(MewtocolExtTypeInit1Word).IsAssignableFrom(type)) return 2;
|
||||
if (typeof(MewtocolExtTypeInit2Word).IsAssignableFrom(type)) return 4;
|
||||
|
||||
throw new Exception("Type not supported");
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches a byte array for a pattern
|
||||
/// </summary>
|
||||
@@ -53,23 +70,6 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the byte string from a incoming RD message
|
||||
/// </summary>
|
||||
internal static string ParseDTByteString(this string _onString, int _blockSize = 4) {
|
||||
|
||||
if (_onString == null)
|
||||
return null;
|
||||
|
||||
var res = new Regex(@"\%([0-9a-fA-F]{2})\$RD(.{" + _blockSize + "})").Match(_onString);
|
||||
if (res.Success) {
|
||||
string val = res.Groups[2].Value;
|
||||
return val;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a return message as RCS single bit
|
||||
/// </summary>
|
||||
@@ -243,7 +243,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
internal static bool CompareIsDuplicate (this IRegisterInternal reg1, IRegisterInternal compare) {
|
||||
internal static bool CompareIsDuplicate(this Register reg1, Register compare) {
|
||||
|
||||
bool valCompare = reg1.RegisterType == compare.RegisterType &&
|
||||
reg1.MemoryAddress == compare.MemoryAddress &&
|
||||
@@ -254,7 +254,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
internal static bool CompareIsDuplicateNonCast (this BaseRegister toInsert, BaseRegister compare, List<Type> allowOverlappingTypes) {
|
||||
internal static bool CompareIsDuplicateNonCast(this Register toInsert, Register compare, List<Type> allowOverlappingTypes) {
|
||||
|
||||
foreach (var type in allowOverlappingTypes) {
|
||||
|
||||
@@ -271,7 +271,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
internal static bool CompareIsNameDuplicate(this IRegisterInternal reg1, IRegisterInternal compare) {
|
||||
internal static bool CompareIsNameDuplicate(this Register reg1, Register compare) {
|
||||
|
||||
return (reg1.Name != null || compare.Name != null) && reg1.Name == compare.Name;
|
||||
|
||||
@@ -354,6 +354,50 @@ namespace MewtocolNet {
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mapping
|
||||
|
||||
/// <summary>
|
||||
/// Maps the source object to target object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of target object.</typeparam>
|
||||
/// <typeparam name="TU">Type of source object.</typeparam>
|
||||
/// <param name="target">Target object.</param>
|
||||
/// <param name="source">Source object.</param>
|
||||
/// <returns>Updated target object.</returns>
|
||||
internal static T Map<T, TU>(this T target, TU source) {
|
||||
|
||||
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
var tprops = target.GetType().GetProperties();
|
||||
|
||||
tprops.Where(x => x.CanWrite == true).ToList().ForEach(prop => {
|
||||
// check whether source object has the the property
|
||||
var sp = source.GetType().GetProperty(prop.Name);
|
||||
if (sp != null) {
|
||||
// if yes, copy the value to the matching property
|
||||
var value = sp.GetValue(source, null);
|
||||
target.GetType().GetProperty(prop.Name).SetValue(target, value, null);
|
||||
}
|
||||
});
|
||||
|
||||
var tfields = target.GetType().GetFields(flags);
|
||||
tfields.ToList().ForEach(field => {
|
||||
|
||||
var sp = source.GetType().GetField(field.Name, flags);
|
||||
|
||||
if (sp != null) {
|
||||
// if yes, copy the value to the matching property
|
||||
var value = sp.GetValue(source);
|
||||
target.GetType().GetField(field.Name, flags).SetValue(target, value);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// Provides an anonymous interface for register reading and writing without memory management
|
||||
/// </summary>
|
||||
RBuild Register { get; }
|
||||
RBuildAnon Register { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to establish a connection with the device asynchronously
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
17
MewtocolNet/InternalEnums/DynamicSizeState.cs
Normal file
17
MewtocolNet/InternalEnums/DynamicSizeState.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
[Flags]
|
||||
internal enum DynamicSizeState {
|
||||
|
||||
None = 0,
|
||||
DynamicallySized = 1,
|
||||
NeedsSizeUpdate = 2,
|
||||
WasSizeUpdated = 4,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,11 +3,9 @@ using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.SetupClasses;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -106,39 +104,6 @@ namespace MewtocolNet {
|
||||
|
||||
#region Build Order 2
|
||||
|
||||
public class MemoryManagerSettings {
|
||||
|
||||
/// <summary>
|
||||
/// <code>
|
||||
/// This feature can improve read write times by a big margin but also
|
||||
/// block outgoing messages inbetween polling cycles more frequently
|
||||
/// </code>
|
||||
/// The max distance of the gap between registers (if there is a gap between
|
||||
/// adjacent registers) to merge them into one request <br/>
|
||||
/// Example: <br/>
|
||||
/// <example>
|
||||
/// We have a register at DT100 (1 word long) and a
|
||||
/// register at DT101 (1 word long) <br/>
|
||||
/// - If the max distance is 0 it will not merge them into one request<br/>
|
||||
/// - If the max distance is 1 it will merge them into one request<br/>
|
||||
/// - If the max distance is 2 and the next register is at DT102 it will also merge them and ignore the spacer byte in the response<br/>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
|
||||
public int MaxOptimizationDistance { get; set; } = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The max number of registers per request group
|
||||
/// </summary>
|
||||
public int MaxRegistersPerGroup { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Wether or not to throw an exception when a byte array overlap or duplicate is detected
|
||||
/// </summary>
|
||||
public bool AllowByteRegisterDupes { get; set; } = false;
|
||||
|
||||
}
|
||||
|
||||
public class PollLevelConfigurator {
|
||||
|
||||
internal Dictionary<int, PollLevelConfig> levelConfigs = new Dictionary<int, PollLevelConfig>();
|
||||
@@ -230,20 +195,25 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// General setting for the memory manager
|
||||
/// </summary>
|
||||
public PostInit<T> WithMemoryManagerSettings (Action<MemoryManagerSettings> settings) {
|
||||
public PostInit<T> WithInterfaceSettings(Action<InterfaceSettings> settings) {
|
||||
|
||||
var res = new MemoryManagerSettings();
|
||||
var res = new InterfaceSettings();
|
||||
settings.Invoke(res);
|
||||
|
||||
if (res.MaxOptimizationDistance < 0)
|
||||
throw new NotSupportedException($"A value lower than 0 is not allowed for " +
|
||||
$"{nameof(MemoryManagerSettings.MaxOptimizationDistance)}");
|
||||
$"{nameof(InterfaceSettings.MaxOptimizationDistance)}");
|
||||
|
||||
if (res.MaxDataBlocksPerWrite < 1)
|
||||
throw new NotSupportedException($"A value lower than 1 is not allowed for " +
|
||||
$"{nameof(InterfaceSettings.MaxDataBlocksPerWrite)}");
|
||||
|
||||
if (intf is MewtocolInterface imew) {
|
||||
|
||||
imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance;
|
||||
imew.memoryManager.maxRegistersPerGroup = res.MaxRegistersPerGroup;
|
||||
imew.memoryManager.allowByteRegDupes = res.AllowByteRegisterDupes;
|
||||
imew.memoryManager.pollLevelOrMode = res.PollLevelOverwriteMode;
|
||||
|
||||
imew.maxDataBlocksPerWrite = res.MaxDataBlocksPerWrite;
|
||||
|
||||
}
|
||||
|
||||
@@ -274,6 +244,8 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
public EndInit<T> WithRegisterCollections(Action<RegCollector> collector) {
|
||||
|
||||
try {
|
||||
|
||||
var res = new RegCollector();
|
||||
collector.Invoke(res);
|
||||
|
||||
@@ -285,6 +257,12 @@ namespace MewtocolNet {
|
||||
postInit = this
|
||||
};
|
||||
|
||||
} catch {
|
||||
|
||||
throw;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MewtocolNet.Helpers;
|
||||
using MewtocolNet.Helpers;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.Helpers;
|
||||
using MewtocolNet.Helpers;
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -11,7 +12,6 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace MewtocolNet {
|
||||
|
||||
private void OnRegisterChanged(IRegister o) {
|
||||
|
||||
var asInternal = (BaseRegister)o;
|
||||
var asInternal = (Register)o;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(asInternal.GetMewName());
|
||||
@@ -168,12 +168,16 @@ namespace MewtocolNet {
|
||||
|
||||
Logger.Log(sb.ToString(), LogLevel.Change, this);
|
||||
|
||||
OnRegisterChangedUpdateProps((IRegisterInternal)o);
|
||||
OnRegisterChangedUpdateProps((Register)o);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual Task ConnectAsync() => throw new NotImplementedException();
|
||||
public virtual async Task ConnectAsync() {
|
||||
|
||||
await memoryManager.OnPlcConnected();
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task AwaitFirstDataCycleAsync() => await firstPollTask;
|
||||
@@ -301,7 +305,13 @@ namespace MewtocolNet {
|
||||
for (int j = 0; j < split.Length; j++) {
|
||||
|
||||
split[j] = split[j].Replace("\r", "");
|
||||
|
||||
if (j < split.Length - 1) {
|
||||
//on last frame include csum
|
||||
split[j] = split[j].Substring(0, split[j].Length - 2);
|
||||
|
||||
}
|
||||
|
||||
if (j > 0) split[j] = split[j].Replace($"%{GetStationNumber()}", "");
|
||||
|
||||
}
|
||||
@@ -334,9 +344,12 @@ namespace MewtocolNet {
|
||||
|
||||
bool needsRead = false;
|
||||
int readFrames = 0;
|
||||
int readBytesPayload = 0;
|
||||
|
||||
do {
|
||||
|
||||
if (onReceiveProgress != null && wordsCountRequested != null) onReceiveProgress(0);
|
||||
|
||||
SetDownstreamStopWatchStart();
|
||||
|
||||
byte[] buffer = new byte[RecBufferSize];
|
||||
@@ -358,15 +371,6 @@ namespace MewtocolNet {
|
||||
|
||||
if (commandRes == CommandState.RequestedNextFrame) {
|
||||
|
||||
//calc frame progress
|
||||
if(onReceiveProgress != null && wordsCountRequested != null) {
|
||||
|
||||
var frameBytesCount = tempMsg.Length - 6;
|
||||
double prog = (double)frameBytesCount / wordsCountRequested.Value;
|
||||
onReceiveProgress(prog);
|
||||
|
||||
}
|
||||
|
||||
//request next frame
|
||||
var writeBuffer = Encoding.UTF8.GetBytes($"%{GetStationNumber()}**&\r");
|
||||
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
||||
@@ -375,6 +379,21 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
//calc frame progress
|
||||
if (onReceiveProgress != null && wordsCountRequested != null) {
|
||||
|
||||
if (readFrames == 0) {
|
||||
readBytesPayload += received.Length - 9;
|
||||
} else {
|
||||
readBytesPayload += received.Length - 7;
|
||||
}
|
||||
|
||||
var frameBytesPlayloadCount = readBytesPayload / 2;
|
||||
double prog = (double)frameBytesPlayloadCount / (wordsCountRequested.Value * 2);
|
||||
onReceiveProgress(prog);
|
||||
|
||||
}
|
||||
|
||||
readFrames++;
|
||||
|
||||
} while (needsRead);
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.SetupClasses;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
@@ -27,7 +19,7 @@ namespace MewtocolNet {
|
||||
|
||||
private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
|
||||
|
||||
internal IEnumerable<BaseRegister> RegistersInternal => GetAllRegistersInternal();
|
||||
internal IEnumerable<Register> RegistersInternal => GetAllRegistersInternal();
|
||||
|
||||
public IEnumerable<IRegister> Registers => GetAllRegisters();
|
||||
|
||||
@@ -46,7 +38,7 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RBuild Register => new RBuild(this);
|
||||
public RBuildAnon Register => new RBuildAnon(this);
|
||||
|
||||
#region Register Polling
|
||||
|
||||
@@ -200,7 +192,7 @@ namespace MewtocolNet {
|
||||
if (registerCollections.Count != 0)
|
||||
throw new NotSupportedException("Register collections can only be build once");
|
||||
|
||||
var regBuild = RBuild.Factory;
|
||||
var regBuild = new RBuildMult(this);
|
||||
|
||||
foreach (var collection in collections) {
|
||||
|
||||
@@ -259,7 +251,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// Writes back the values changes of the underlying registers to the corrosponding property
|
||||
/// </summary>
|
||||
private void OnRegisterChangedUpdateProps(IRegisterInternal reg) {
|
||||
private void OnRegisterChangedUpdateProps(Register reg) {
|
||||
|
||||
var collection = reg.ContainedCollection;
|
||||
if (collection == null) return;
|
||||
@@ -278,19 +270,19 @@ namespace MewtocolNet {
|
||||
|
||||
#region Register Adding
|
||||
|
||||
internal void AddRegisters (params BaseRegister[] registers) {
|
||||
internal void AddRegisters(params Register[] registers) {
|
||||
|
||||
InsertRegistersToMemoryStack(registers.ToList());
|
||||
|
||||
}
|
||||
|
||||
internal void InsertRegistersToMemoryStack (List<BaseRegister> registers) {
|
||||
internal void InsertRegistersToMemoryStack(List<Register> registers) {
|
||||
|
||||
memoryManager.LinkAndMergeRegisters(registers);
|
||||
|
||||
}
|
||||
|
||||
private bool CheckDuplicateRegister (IRegisterInternal instance, out IRegisterInternal foundDupe) {
|
||||
private bool CheckDuplicateRegister(Register instance, out Register foundDupe) {
|
||||
|
||||
foundDupe = RegistersInternal.FirstOrDefault(x => x.CompareIsDuplicate(instance));
|
||||
|
||||
@@ -298,7 +290,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
private bool CheckDuplicateRegister(IRegisterInternal instance) {
|
||||
private bool CheckDuplicateRegister(Register instance) {
|
||||
|
||||
var foundDupe = RegistersInternal.FirstOrDefault(x => x.CompareIsDuplicate(instance));
|
||||
|
||||
@@ -306,13 +298,13 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
private bool CheckDuplicateNameRegister(IRegisterInternal instance) {
|
||||
private bool CheckDuplicateNameRegister(Register instance) {
|
||||
|
||||
return RegistersInternal.Any(x => x.CompareIsNameDuplicate(instance));
|
||||
|
||||
}
|
||||
|
||||
private bool CheckOverlappingRegister (IRegisterInternal instance, out IRegisterInternal regB) {
|
||||
private bool CheckOverlappingRegister(Register instance, out Register regB) {
|
||||
|
||||
//ignore bool registers, they have their own address spectrum
|
||||
regB = null;
|
||||
@@ -362,7 +354,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
internal IEnumerable<BaseRegister> GetAllRegistersInternal () {
|
||||
internal IEnumerable<Register> GetAllRegistersInternal() {
|
||||
|
||||
return memoryManager.GetAllRegisters();
|
||||
|
||||
@@ -378,7 +370,7 @@ namespace MewtocolNet {
|
||||
|
||||
for (int i = 0; i < internals.Count; i++) {
|
||||
|
||||
var reg = (IRegisterInternal)internals[i];
|
||||
var reg = (Register)internals[i];
|
||||
reg.ClearValue();
|
||||
|
||||
}
|
||||
@@ -391,7 +383,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
internal void InvokeRegisterChanged(BaseRegister reg) {
|
||||
internal void InvokeRegisterChanged(Register reg) {
|
||||
|
||||
RegisterChanged?.Invoke(reg);
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
public abstract partial class MewtocolInterface {
|
||||
|
||||
internal int maxDataBlocksPerWrite = 8;
|
||||
|
||||
#region PLC info getters
|
||||
|
||||
/// <summary>
|
||||
@@ -84,7 +81,7 @@ namespace MewtocolNet {
|
||||
|
||||
#endregion
|
||||
|
||||
#region Byte range writingv / reading to registers
|
||||
#region Byte range writing / reading to registers
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte array to a span over multiple registers at once,
|
||||
@@ -94,19 +91,15 @@ namespace MewtocolNet {
|
||||
/// /// <param name="start">start address of the array</param>
|
||||
/// <param name="byteArr"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> WriteByteRange (int start, byte[] byteArr, bool flipBytes = false) {
|
||||
public async Task<bool> WriteByteRange(int start, byte[] byteArr) {
|
||||
|
||||
string byteString;
|
||||
if (!IsConnected)
|
||||
throw MewtocolException.NotConnectedSend();
|
||||
|
||||
if(flipBytes) {
|
||||
byteString = byteArr.BigToMixedEndian().ToHexString();
|
||||
} else {
|
||||
byteString = byteArr.ToHexString();
|
||||
}
|
||||
string byteString = byteArr.ToHexString();
|
||||
|
||||
var wordLength = byteArr.Length / 2;
|
||||
if (byteArr.Length % 2 != 0)
|
||||
wordLength++;
|
||||
if (byteArr.Length % 2 != 0) wordLength++;
|
||||
|
||||
string startStr = start.ToString().PadLeft(5, '0');
|
||||
string endStr = (start + wordLength - 1).ToString().PadLeft(5, '0');
|
||||
@@ -123,52 +116,85 @@ namespace MewtocolNet {
|
||||
/// doesn't block the receive thread
|
||||
/// </summary>
|
||||
/// <param name="start">Start adress</param>
|
||||
/// <param name="count">Number of bytes to get</param>
|
||||
/// <param name="flipBytes">Flips bytes from big to mixed endian</param>
|
||||
/// <param name="onProgress">Gets invoked when the progress changes, contains the progress as a double</param>
|
||||
/// <returns>A byte array or null of there was an error</returns>
|
||||
public async Task<byte[]> ReadByteRangeNonBlocking (int start, int count, bool flipBytes = false, Action<double> onProgress = null) {
|
||||
/// <param name="byteCount">Number of bytes to get</param>
|
||||
/// <param name="onProgress">Gets invoked when the progress changes, contains the progress as a double from 0 - 1.0</param>
|
||||
/// <returns>A byte array of the requested DT area</returns>
|
||||
public async Task<byte[]> ReadByteRangeNonBlocking(int start, int byteCount, Action<double> onProgress = null) {
|
||||
|
||||
var byteList = new List<byte>();
|
||||
if (!IsConnected)
|
||||
throw MewtocolException.NotConnectedSend();
|
||||
|
||||
var wordLength = count / 2;
|
||||
if (count % 2 != 0)
|
||||
wordLength++;
|
||||
onProgress += (p) => Console.WriteLine($"{p * 100:N2}%");
|
||||
|
||||
int blockSize = 8;
|
||||
//on odd bytes add one word
|
||||
var wordLength = byteCount / 2;
|
||||
if (byteCount % 2 != 0) wordLength++;
|
||||
|
||||
//read blocks of max 4 words per msg
|
||||
for (int i = 0; i < wordLength; i += blockSize) {
|
||||
int maxReadBlockSize = maxDataBlocksPerWrite;
|
||||
|
||||
int curWordStart = start + i;
|
||||
int curWordEnd = curWordStart + blockSize - 1;
|
||||
if (byteCount < (maxReadBlockSize * 2)) maxReadBlockSize = wordLength;
|
||||
|
||||
string startStr = curWordStart.ToString().PadLeft(5, '0');
|
||||
string endStr = (curWordEnd).ToString().PadLeft(5, '0');
|
||||
int blocksToReadNoOverflow = wordLength / maxReadBlockSize;
|
||||
int blocksOverflow = wordLength % maxReadBlockSize;
|
||||
int totalBlocksToRead = blocksOverflow != 0 ? blocksToReadNoOverflow + 1 : blocksToReadNoOverflow;
|
||||
|
||||
List<byte> readBytes = new List<byte>();
|
||||
|
||||
async Task ReadBlock (int wordStart, int wordEnd, Action<double> readProg) {
|
||||
|
||||
int blockSize = wordEnd - wordStart + 1;
|
||||
string startStr = wordStart.ToString().PadLeft(5, '0');
|
||||
string endStr = wordEnd.ToString().PadLeft(5, '0');
|
||||
|
||||
string requeststring = $"%{GetStationNumber()}#RDD{startStr}{endStr}";
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
|
||||
var result = await SendCommandAsync(requeststring, onReceiveProgress: readProg);
|
||||
|
||||
if (result.Success && !string.IsNullOrEmpty(result.Response)) {
|
||||
|
||||
var bytes = result.Response.ParseDTByteString(blockSize * 4).HexStringToByteArray();
|
||||
var bytes = result.Response.ParseDTRawStringAsBytes();
|
||||
readBytes.AddRange(bytes);
|
||||
|
||||
if (bytes == null) return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//get all full blocks
|
||||
for (int i = 0; i < blocksToReadNoOverflow; i++) {
|
||||
|
||||
int curWordStart, curWordEnd;
|
||||
|
||||
curWordStart = start + (i * maxReadBlockSize);
|
||||
curWordEnd = curWordStart + maxReadBlockSize - 1;
|
||||
|
||||
await ReadBlock(curWordStart, curWordEnd, (p) => {
|
||||
|
||||
if (onProgress != null && p != 0) {
|
||||
var toplevelProg = (double)(i + 1) / totalBlocksToRead;
|
||||
onProgress(toplevelProg * p);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//read remaining block
|
||||
if (i == blocksToReadNoOverflow - 1 && blocksOverflow != 0) {
|
||||
|
||||
if (onProgress != null)
|
||||
onProgress((double)readBytes.Count / byteCount);
|
||||
|
||||
curWordStart = start + ((i + 1) * maxReadBlockSize);
|
||||
curWordEnd = curWordStart + blocksOverflow - 1;
|
||||
|
||||
await ReadBlock(curWordStart, curWordEnd, (p) => {});
|
||||
|
||||
if (flipBytes) {
|
||||
byteList.AddRange(bytes.BigToMixedEndian().Take(count).ToArray());
|
||||
} else {
|
||||
byteList.AddRange(bytes.Take(count).ToArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (onProgress != null)
|
||||
onProgress((double)i / wordLength);
|
||||
onProgress((double)1);
|
||||
|
||||
}
|
||||
|
||||
return byteList.ToArray();
|
||||
return readBytes.ToArray();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
using MewtocolNet.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -134,6 +129,7 @@ namespace MewtocolNet {
|
||||
|
||||
if (gotInfo != null) {
|
||||
|
||||
await base.ConnectAsync();
|
||||
OnConnected(gotInfo.Value);
|
||||
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
@@ -110,6 +109,8 @@ namespace MewtocolNet {
|
||||
|
||||
if (plcinf != null) {
|
||||
|
||||
await base.ConnectAsync();
|
||||
|
||||
OnConnected(plcinf.Value);
|
||||
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet {
|
||||
namespace MewtocolNet {
|
||||
|
||||
public enum BaudRate {
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet {
|
||||
namespace MewtocolNet {
|
||||
|
||||
public enum MewtocolVersion {
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet {
|
||||
namespace MewtocolNet {
|
||||
|
||||
public enum PlcVarType {
|
||||
|
||||
|
||||
16
MewtocolNet/PublicEnums/PollLevelOverwriteMode.cs
Normal file
16
MewtocolNet/PublicEnums/PollLevelOverwriteMode.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace MewtocolNet {
|
||||
|
||||
public enum PollLevelOverwriteMode {
|
||||
|
||||
/// <summary>
|
||||
/// The lowest average poll level for overlapping registers gets used
|
||||
/// </summary>
|
||||
Lowest,
|
||||
/// <summary>
|
||||
/// The highest average poll level for overlapping registers gets used
|
||||
/// </summary>
|
||||
Highest,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.PublicEnums {
|
||||
namespace MewtocolNet.PublicEnums {
|
||||
public enum RegisterBuildSource {
|
||||
Anonymous,
|
||||
Manual,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace MewtocolNet {
|
||||
namespace MewtocolNet {
|
||||
|
||||
/// <summary>
|
||||
/// The register prefixed type
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace MewtocolNet.RegisterAttributes {
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using MewtocolNet.Registers;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MewtocolNet.RegisterAttributes {
|
||||
|
||||
19
MewtocolNet/RegisterBuilding/ParseResultState.cs
Normal file
19
MewtocolNet/RegisterBuilding/ParseResultState.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
internal enum ParseResultState {
|
||||
|
||||
/// <summary>
|
||||
/// The parse try failed at the intial regex match
|
||||
/// </summary>
|
||||
FailedSoft,
|
||||
/// <summary>
|
||||
/// The parse try failed at the afer- regex match
|
||||
/// </summary>
|
||||
FailedHard,
|
||||
/// <summary>
|
||||
/// The parse try did work
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
99
MewtocolNet/RegisterBuilding/RBuildAnon.cs
Normal file
99
MewtocolNet/RegisterBuilding/RBuildAnon.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
/// <summary>
|
||||
/// Anonymous register builder
|
||||
/// </summary>
|
||||
public class RBuildAnon : RBuildBase {
|
||||
|
||||
public RBuildAnon(MewtocolInterface plc) : base(plc) { }
|
||||
|
||||
/// <inheritdoc cref="RBuildMult.Address(string, string)"/>
|
||||
public SAddress Address(string plcAddrName) {
|
||||
|
||||
return new SAddress {
|
||||
attachedPlc = this.attachedPLC,
|
||||
addrString = plcAddrName
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public new class SAddress {
|
||||
|
||||
protected internal MewtocolInterface attachedPlc;
|
||||
protected internal string addrString;
|
||||
protected internal string name;
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the register and bypasses the memory manager <br/>
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <returns>True if success</returns>
|
||||
public async Task<bool> WriteToAsync<T>(T value) {
|
||||
|
||||
try {
|
||||
|
||||
var tempRegister = AssembleTemporaryRegister<T>();
|
||||
return await tempRegister.WriteAsync(value);
|
||||
|
||||
} catch {
|
||||
|
||||
throw;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the register and bypasses the memory manager <br/>
|
||||
/// </summary>
|
||||
/// <returns>The value read or null if failed</returns>
|
||||
public async Task<T> ReadFromAsync<T>() {
|
||||
|
||||
try {
|
||||
|
||||
var tempRegister = AssembleTemporaryRegister<T>();
|
||||
return (T)await tempRegister.ReadAsync();
|
||||
|
||||
} catch {
|
||||
|
||||
throw;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Register AssembleTemporaryRegister<T>() {
|
||||
|
||||
var temp = new RBuildMult(attachedPlc).Address(addrString).AsType<T>();
|
||||
|
||||
var assembler = new RegisterAssembler(attachedPlc);
|
||||
return assembler.Assemble(temp.Data);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class RBuildSingle : RBuildBase {
|
||||
|
||||
public RBuildSingle(MewtocolInterface plc) : base(plc) { }
|
||||
|
||||
/// <inheritdoc cref="RBuildMult.Address(string, string)"/>
|
||||
public SAddress Address(string plcAddrName, string name = null) {
|
||||
|
||||
var data = ParseAddress(plcAddrName, name);
|
||||
|
||||
return new SAddress {
|
||||
Data = data,
|
||||
builder = this,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +1,27 @@
|
||||
using MewtocolNet.PublicEnums;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using static MewtocolNet.RegisterBuilding.RBuild;
|
||||
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
internal enum ParseResultState {
|
||||
public class RBuildBase {
|
||||
|
||||
/// <summary>
|
||||
/// The parse try failed at the intial regex match
|
||||
/// </summary>
|
||||
FailedSoft,
|
||||
/// <summary>
|
||||
/// The parse try failed at the afer- regex match
|
||||
/// </summary>
|
||||
FailedHard,
|
||||
/// <summary>
|
||||
/// The parse try did work
|
||||
/// </summary>
|
||||
Success,
|
||||
protected internal MewtocolInterface attachedPLC;
|
||||
|
||||
}
|
||||
public RBuildBase() { }
|
||||
|
||||
/// <summary>
|
||||
/// Contains useful tools for register creation
|
||||
/// </summary>
|
||||
public class RBuild {
|
||||
internal RBuildBase(MewtocolInterface plc) => attachedPLC = plc;
|
||||
|
||||
private MewtocolInterface attachedPLC;
|
||||
internal List<StepData> unfinishedList = new List<StepData>();
|
||||
|
||||
public RBuild () { }
|
||||
|
||||
internal RBuild (MewtocolInterface plc) {
|
||||
|
||||
attachedPLC = plc;
|
||||
|
||||
}
|
||||
|
||||
public static RBuild Factory => new RBuild();
|
||||
|
||||
internal List<SData> unfinishedList = new List<SData>();
|
||||
|
||||
#region String parse stage
|
||||
#region Parser stage
|
||||
|
||||
//methods to test the input string on
|
||||
private static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
|
||||
protected static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
|
||||
|
||||
(x) => TryBuildBoolean(x),
|
||||
(x) => TryBuildNumericBased(x),
|
||||
@@ -65,55 +29,28 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
};
|
||||
|
||||
internal class SData {
|
||||
|
||||
internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous;
|
||||
|
||||
internal bool wasAddressStringRangeBased;
|
||||
internal string originalParseStr;
|
||||
internal string name;
|
||||
internal RegisterType regType;
|
||||
internal uint memAddress;
|
||||
internal byte specialAddress;
|
||||
internal Type dotnetVarType;
|
||||
|
||||
//optional
|
||||
internal uint? byteSize;
|
||||
internal uint? bitSize;
|
||||
internal int? stringSize;
|
||||
|
||||
internal int pollLevel = 1;
|
||||
|
||||
//only for building from attributes
|
||||
internal RegisterCollection regCollection;
|
||||
internal PropertyInfo boundProperty;
|
||||
|
||||
internal string typeDef;
|
||||
|
||||
}
|
||||
|
||||
public class SBase {
|
||||
|
||||
public SBase() { }
|
||||
|
||||
internal SBase(SData data, RBuild bldr) {
|
||||
internal SBase(StepData data, RBuildBase bldr) {
|
||||
Data = data;
|
||||
builder = bldr;
|
||||
}
|
||||
|
||||
internal SData Data { get; set; }
|
||||
internal StepData Data;
|
||||
|
||||
internal RBuild builder;
|
||||
internal RBuildBase builder;
|
||||
|
||||
}
|
||||
|
||||
internal struct ParseResult {
|
||||
internal protected struct ParseResult {
|
||||
|
||||
public ParseResultState state;
|
||||
internal ParseResultState state;
|
||||
|
||||
public string hardFailReason;
|
||||
internal string hardFailReason;
|
||||
|
||||
public SData stepData;
|
||||
internal StepData stepData;
|
||||
|
||||
}
|
||||
|
||||
@@ -200,7 +137,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.Success,
|
||||
stepData = new SData {
|
||||
stepData = new StepData {
|
||||
regType = (RegisterType)(int)regType,
|
||||
memAddress = areaAdd,
|
||||
specialAddress = specialAdd,
|
||||
@@ -248,7 +185,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.Success,
|
||||
stepData = new SData {
|
||||
stepData = new StepData {
|
||||
regType = regType,
|
||||
memAddress = areaAdd,
|
||||
}
|
||||
@@ -288,7 +225,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
uint areaAdd = 0;
|
||||
|
||||
//try cast the prefix
|
||||
if (!Enum.TryParse(prefix, out regType) || regType != RegisterType.DT) {
|
||||
if (!Enum.TryParse(prefix, out regType)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
@@ -312,25 +249,22 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.Success,
|
||||
stepData = new SData {
|
||||
stepData = new StepData {
|
||||
regType = RegisterType.DT_BYTE_RANGE,
|
||||
wasAddressStringRangeBased = true,
|
||||
dotnetVarType = typeof(byte[]),
|
||||
memAddress = addresses[0],
|
||||
byteSize = (addresses[1] - addresses[0] + 1) * 2
|
||||
byteSizeHint = (addresses[1] - addresses[0] + 1) * 2
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the register builder for a new mewtocol address <br/>
|
||||
/// Examples:
|
||||
/// <code>Address("DT100") | Address("R10A") | Address("DDT50", "MyRegisterName")</code>
|
||||
/// </summary>
|
||||
/// <param name="plcAddrName">Address name formatted as FP-Address like in FP-Winpro</param>
|
||||
/// <param name="name">Custom name for the register to referr to it later</param>
|
||||
public SAddress Address (string plcAddrName, string name = null) {
|
||||
#endregion
|
||||
|
||||
#region Addressing stage
|
||||
|
||||
internal StepData ParseAddress(string plcAddrName, string name = null) {
|
||||
|
||||
foreach (var method in parseMethods) {
|
||||
|
||||
@@ -345,10 +279,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
unfinishedList.Add(res.stepData);
|
||||
|
||||
return new SAddress {
|
||||
Data = res.stepData,
|
||||
builder = this,
|
||||
};
|
||||
return res.stepData;
|
||||
|
||||
} else if (res.state == ParseResultState.FailedHard) {
|
||||
|
||||
@@ -362,19 +293,9 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
}
|
||||
|
||||
//internal use only, adds a type definition (for use when building from attibute)
|
||||
internal SAddress AddressFromAttribute (string plcAddrName, string typeDef) {
|
||||
|
||||
var built = Address(plcAddrName);
|
||||
built.Data.typeDef = typeDef;
|
||||
built.Data.buildSource = RegisterBuildSource.Attribute;
|
||||
return built;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type determination stage
|
||||
#region Typing stage
|
||||
|
||||
public class SAddress : SBase {
|
||||
|
||||
@@ -384,7 +305,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
/// <typeparam name="T">
|
||||
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
|
||||
/// </typeparam>
|
||||
public TempRegister<T> AsType<T> () {
|
||||
public TempRegister<T> AsType<T>(int? sizeHint = null) {
|
||||
|
||||
if (!typeof(T).IsAllowedPlcCastingType()) {
|
||||
|
||||
@@ -392,6 +313,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
}
|
||||
|
||||
Data.byteSizeHint = (uint?)sizeHint;
|
||||
Data.dotnetVarType = typeof(T);
|
||||
|
||||
return new TempRegister<T>(Data, builder);
|
||||
@@ -416,32 +338,22 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
var elementType = type.GetElementType();
|
||||
|
||||
if (!elementType.IsAllowedPlcCastingType()) {
|
||||
if (type != typeof(byte[]) && !elementType.IsAllowedPlcCastingType()) {
|
||||
|
||||
throw new NotSupportedException($"The dotnet type {elementType}, is not supported for PLC type casting");
|
||||
|
||||
}
|
||||
|
||||
bool isExtensionTypeDT = typeof(MewtocolExtensionTypeDT).IsAssignableFrom(elementType);
|
||||
bool isExtensionTypeDDT = typeof(MewtocolExtensionTypeDDT).IsAssignableFrom(elementType);
|
||||
|
||||
int byteSizePerItem = 0;
|
||||
if(elementType.Namespace.StartsWith("System")) {
|
||||
byteSizePerItem = Marshal.SizeOf(elementType);
|
||||
} else if (isExtensionTypeDT) {
|
||||
byteSizePerItem = 2;
|
||||
} else if (isExtensionTypeDDT) {
|
||||
byteSizePerItem = 4;
|
||||
}
|
||||
int byteSizePerItem = elementType.DetermineTypeByteSize();
|
||||
|
||||
//check if it fits without remainder
|
||||
if(Data.byteSize % byteSizePerItem != 0) {
|
||||
if (Data.byteSizeHint % byteSizePerItem != 0) {
|
||||
throw new NotSupportedException($"The array element type {elementType} doesn't fit into the adress range");
|
||||
}
|
||||
|
||||
return (TempRegister)generic.Invoke(this, new object[] {
|
||||
//element count
|
||||
new int[] { (int)((Data.byteSize / byteSizePerItem) / 2) }
|
||||
new int[] { (int)((Data.byteSizeHint / byteSizePerItem)) }
|
||||
});
|
||||
|
||||
} else if (Data.wasAddressStringRangeBased) {
|
||||
@@ -521,7 +433,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
} else if (stringMatch.Success) {
|
||||
|
||||
Data.dotnetVarType = typeof(string);
|
||||
Data.stringSize = int.Parse(stringMatch.Groups["len"].Value);
|
||||
Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value);
|
||||
|
||||
} else if (arrayMatch.Success) {
|
||||
|
||||
@@ -553,10 +465,15 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray));
|
||||
MethodInfo generic = method.MakeGenericMethod(arrType);
|
||||
|
||||
return (TempRegister)generic.Invoke(this, new object[] {
|
||||
var tmp = (TempRegister)generic.Invoke(this, new object[] {
|
||||
indices.ToArray()
|
||||
});
|
||||
|
||||
tmp.builder = builder;
|
||||
tmp.Data = Data;
|
||||
|
||||
return tmp;
|
||||
|
||||
} else {
|
||||
|
||||
throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized");
|
||||
@@ -601,7 +518,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
if (arrRank > 3)
|
||||
throw new NotSupportedException($"4+ dimensional arrays are not supported");
|
||||
|
||||
if (!elBaseType.IsAllowedPlcCastingType())
|
||||
if (typeof(T) != typeof(byte[]) && !elBaseType.IsAllowedPlcCastingType())
|
||||
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC array type casting");
|
||||
|
||||
if (arrRank != indicies.Length)
|
||||
@@ -609,30 +526,14 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
Data.dotnetVarType = typeof(T);
|
||||
|
||||
return new TempRegister(Data, builder);
|
||||
int byteSizePerItem = elBaseType.DetermineTypeByteSize();
|
||||
int calcedTotalByteSize = indicies.Aggregate((a, x) => a * x) * byteSizePerItem;
|
||||
|
||||
}
|
||||
Data.byteSizeHint = (uint)calcedTotalByteSize;
|
||||
Data.arrayIndicies = indicies;
|
||||
|
||||
/// <summary>
|
||||
/// Automatically finds the best type for the register
|
||||
/// </summary>
|
||||
public TempRegister AutoType() {
|
||||
|
||||
switch (Data.regType) {
|
||||
case RegisterType.X:
|
||||
case RegisterType.Y:
|
||||
case RegisterType.R:
|
||||
Data.dotnetVarType = typeof(bool);
|
||||
break;
|
||||
case RegisterType.DT:
|
||||
Data.dotnetVarType = typeof(short);
|
||||
break;
|
||||
case RegisterType.DDT:
|
||||
Data.dotnetVarType = typeof(int);
|
||||
break;
|
||||
case RegisterType.DT_BYTE_RANGE:
|
||||
Data.dotnetVarType = typeof(string);
|
||||
break;
|
||||
if (Data.byteSizeHint % byteSizePerItem != 0) {
|
||||
throw new NotSupportedException($"The array element type {elBaseType} doesn't fit into the adress range");
|
||||
}
|
||||
|
||||
return new TempRegister(Data, builder);
|
||||
@@ -647,7 +548,9 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
public class TempRegister<T> : SBase {
|
||||
|
||||
internal TempRegister(SData data, RBuild bldr) : base(data, bldr) {}
|
||||
internal TempRegister() { }
|
||||
|
||||
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the poll level of the register
|
||||
@@ -655,29 +558,17 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
public TempRegister<T> PollLevel(int level) {
|
||||
|
||||
Data.pollLevel = level;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the register and bypasses the memory manager <br/>
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <returns>True if success</returns>
|
||||
public async Task<bool> WriteToAsync (T value) => await builder.WriteAnonymousAsync(this, value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the register and bypasses the memory manager <br/>
|
||||
/// </summary>
|
||||
/// <returns>The value read or null if failed</returns>
|
||||
public async Task<T> ReadFromAsync () => await builder.ReadAnonymousAsync(this);
|
||||
|
||||
}
|
||||
|
||||
public class TempRegister : SBase {
|
||||
|
||||
internal TempRegister(SData data, RBuild bldr) : base(data, bldr) { }
|
||||
internal TempRegister() { }
|
||||
|
||||
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the poll level of the register
|
||||
@@ -685,36 +576,6 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
public TempRegister PollLevel(int level) {
|
||||
|
||||
Data.pollLevel = level;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the register and bypasses the memory manager <br/>
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <returns>True if success</returns>
|
||||
public async Task<bool> WriteToAsync(object value) => await builder.WriteAnonymousAsync(this, value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the register and bypasses the memory manager <br/>
|
||||
/// </summary>
|
||||
/// <returns>The value read or null if failed</returns>
|
||||
public async Task<object> ReadFromAsync () => await builder.ReadAnonymousAsync(this);
|
||||
|
||||
internal TempRegister RegCollection(RegisterCollection col) {
|
||||
|
||||
Data.regCollection = col;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
internal TempRegister BoundProp(PropertyInfo prop) {
|
||||
|
||||
Data.boundProperty = prop;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
@@ -723,42 +584,6 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
#endregion
|
||||
|
||||
#region Anonymous read/write bindings
|
||||
|
||||
private async Task<bool> WriteAnonymousAsync (TempRegister reg, object value) {
|
||||
|
||||
var assembler = new RegisterAssembler(attachedPLC);
|
||||
var tempRegister = assembler.Assemble(reg.Data);
|
||||
return await tempRegister.WriteAsync(value);
|
||||
|
||||
}
|
||||
|
||||
private async Task<bool> WriteAnonymousAsync<T>(TempRegister<T> reg, object value) {
|
||||
|
||||
var assembler = new RegisterAssembler(attachedPLC);
|
||||
var tempRegister = assembler.Assemble(reg.Data);
|
||||
return await tempRegister.WriteAsync(value);
|
||||
|
||||
}
|
||||
|
||||
private async Task<object> ReadAnonymousAsync (TempRegister reg) {
|
||||
|
||||
var assembler = new RegisterAssembler(attachedPLC);
|
||||
var tempRegister = assembler.Assemble(reg.Data);
|
||||
return await tempRegister.ReadAsync();
|
||||
|
||||
}
|
||||
|
||||
private async Task<T> ReadAnonymousAsync<T>(TempRegister<T> reg) {
|
||||
|
||||
var assembler = new RegisterAssembler(attachedPLC);
|
||||
var tempRegister = assembler.Assemble(reg.Data);
|
||||
return (T)await tempRegister.ReadAsync();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
129
MewtocolNet/RegisterBuilding/RBuildMult.cs
Normal file
129
MewtocolNet/RegisterBuilding/RBuildMult.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using MewtocolNet.PublicEnums;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
/// <summary>
|
||||
/// Contains useful tools for bunch register creation
|
||||
/// </summary>
|
||||
public class RBuildMult : RBuildBase {
|
||||
|
||||
public RBuildMult(MewtocolInterface plc) : base(plc) { }
|
||||
|
||||
#region String parse stage
|
||||
|
||||
/// <summary>
|
||||
/// Starts the register builder for a new mewtocol address <br/>
|
||||
/// Examples:
|
||||
/// <code>Address("DT100") | Address("R10A") | Address("DDT50", "MyRegisterName")</code>
|
||||
/// </summary>
|
||||
/// <param name="plcAddrName">Address name formatted as FP-Address like in FP-Winpro</param>
|
||||
/// <param name="name">Custom name for the register to referr to it later</param>
|
||||
public SAddress Address(string plcAddrName, string name = null) {
|
||||
|
||||
var data = ParseAddress(plcAddrName, name);
|
||||
|
||||
return new SAddress {
|
||||
Data = data,
|
||||
builder = this,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//internal use only, adds a type definition (for use when building from attibute)
|
||||
internal SAddress AddressFromAttribute(string plcAddrName, string typeDef) {
|
||||
|
||||
var built = Address(plcAddrName);
|
||||
built.Data.typeDef = typeDef;
|
||||
built.Data.buildSource = RegisterBuildSource.Attribute;
|
||||
return built;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Typing stage
|
||||
|
||||
public new class SAddress : RBuildBase.SAddress {
|
||||
|
||||
public new TempRegister<T> AsType<T>(int? sizeHint = null) => new TempRegister<T>().Map(base.AsType<T>(sizeHint));
|
||||
|
||||
public new TempRegister AsType(Type type) => new TempRegister().Map(base.AsType(type));
|
||||
|
||||
public new TempRegister AsType(PlcVarType type) => new TempRegister().Map(base.AsType(type));
|
||||
|
||||
public new TempRegister AsType(string type) => new TempRegister().Map(base.AsType(type));
|
||||
|
||||
public new TempRegister AsTypeArray<T>(params int[] indicies) => new TempRegister().Map(base.AsTypeArray<T>(indicies));
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Options stage
|
||||
|
||||
public new class TempRegister<T> : RBuildBase.TempRegister<T> {
|
||||
|
||||
internal TempRegister() { }
|
||||
|
||||
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
|
||||
|
||||
///<inheritdoc cref="RBuildBase.TempRegister.PollLevel(int)"/>
|
||||
public new TempRegister<T> PollLevel(int level) => new TempRegister<T>().Map(base.PollLevel(level));
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the generated <see cref="IRegister"/>
|
||||
/// </summary>
|
||||
public TempRegister<T> Out(Action<IRegister> registerOut) {
|
||||
|
||||
Data.registerOut = registerOut;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public new class TempRegister : RBuildBase.TempRegister {
|
||||
|
||||
internal TempRegister() { }
|
||||
|
||||
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
|
||||
|
||||
///<inheritdoc cref="RBuildBase.TempRegister.PollLevel(int)"/>
|
||||
public new TempRegister PollLevel(int level) => new TempRegister().Map(base.PollLevel(level));
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the generated <see cref="IRegister"/>
|
||||
/// </summary>
|
||||
public TempRegister Out(Action<IRegister> registerOut) {
|
||||
|
||||
Data.registerOut = registerOut;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
//internal use only
|
||||
internal TempRegister RegCollection(RegisterCollection col) {
|
||||
|
||||
Data.regCollection = col;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
internal TempRegister BoundProp(PropertyInfo prop) {
|
||||
|
||||
Data.boundProperty = prop;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +1,112 @@
|
||||
using System;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
public static class RegBuilderExtensions {
|
||||
|
||||
public static IPlc AddTrackedRegisters(this IPlc plc, Action<RBuild> builder) {
|
||||
|
||||
if (plc.IsConnected)
|
||||
throw new Exception("Can't add registers if the PLC is connected");
|
||||
|
||||
var regBuilder = new RBuild();
|
||||
builder.Invoke(regBuilder);
|
||||
/// <summary>
|
||||
/// Adds a single register to the plc stack and returns the generated <see cref="IRegister"/><br/>
|
||||
/// This waits for the memory manager to size all dynamic registers correctly
|
||||
/// </summary>
|
||||
/// <returns>The generated <see cref="IRegister"/></returns>
|
||||
public static IRegister AddRegister(this IPlc plc, Action<RBuildSingle> builder) {
|
||||
|
||||
var assembler = new RegisterAssembler((MewtocolInterface)plc);
|
||||
var regBuilder = new RBuildSingle((MewtocolInterface)plc);
|
||||
|
||||
builder.Invoke(regBuilder);
|
||||
|
||||
var registers = assembler.AssembleAll(regBuilder);
|
||||
|
||||
var interf = (MewtocolInterface)plc;
|
||||
|
||||
interf.AddRegisters(registers.ToArray());
|
||||
|
||||
Task.Run(interf.memoryManager.CheckAllDynamicallySizedAreas);
|
||||
|
||||
return registers.First();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single register to the plc stack and returns the generated <see cref="IRegister"/>
|
||||
/// Waits
|
||||
/// </summary>
|
||||
/// <returns>The generated <see cref="IRegister"/></returns>
|
||||
public static async Task<IRegister> AddRegisterAsync (this IPlc plc, Action<RBuildSingle> builder) {
|
||||
|
||||
var assembler = new RegisterAssembler((MewtocolInterface)plc);
|
||||
var regBuilder = new RBuildSingle((MewtocolInterface)plc);
|
||||
|
||||
builder.Invoke(regBuilder);
|
||||
|
||||
var registers = assembler.AssembleAll(regBuilder);
|
||||
|
||||
var interf = (MewtocolInterface)plc;
|
||||
|
||||
interf.AddRegisters(registers.ToArray());
|
||||
|
||||
await interf.memoryManager.CheckAllDynamicallySizedAreas();
|
||||
|
||||
return registers.First();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple registers to the plc stack at once <br/>
|
||||
/// Using this over adding each register individually will result in better generation time performance
|
||||
/// of the <see cref="UnderlyingRegisters.MemoryAreaManager"/> <br/><br/>
|
||||
/// <b>WARNING!</b> This will not wait for the memory manager to account for dynamically sized registers
|
||||
/// like ones with the <see cref="string"/> type.. <br/>
|
||||
/// use <see cref="AddRegistersAsync"/>
|
||||
/// for this case
|
||||
/// </summary>
|
||||
public static IPlc AddRegisters (this IPlc plc, Action<RBuildMult> builder) {
|
||||
|
||||
var assembler = new RegisterAssembler((MewtocolInterface)plc);
|
||||
var regBuilder = new RBuildMult((MewtocolInterface)plc);
|
||||
|
||||
builder.Invoke(regBuilder);
|
||||
|
||||
var registers = assembler.AssembleAll(regBuilder);
|
||||
|
||||
var interf = (MewtocolInterface)plc;
|
||||
|
||||
interf.AddRegisters(registers.ToArray());
|
||||
|
||||
Task.Run(interf.memoryManager.CheckAllDynamicallySizedAreas);
|
||||
|
||||
return plc;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple registers to the plc stack at once <br/>
|
||||
/// Using this over adding each register individually will result in better generation time performance
|
||||
/// of the <see cref="UnderlyingRegisters.MemoryAreaManager"/><br/><br/>
|
||||
/// This waits for the memory manager to size all dynamic registers correctly
|
||||
/// </summary>
|
||||
public static async Task<IPlc> AddRegistersAsync (this IPlc plc, Action<RBuildMult> builder) {
|
||||
|
||||
var assembler = new RegisterAssembler((MewtocolInterface)plc);
|
||||
var regBuilder = new RBuildMult((MewtocolInterface)plc);
|
||||
|
||||
builder.Invoke(regBuilder);
|
||||
|
||||
var registers = assembler.AssembleAll(regBuilder);
|
||||
|
||||
var interf = (MewtocolInterface)plc;
|
||||
|
||||
interf.AddRegisters(registers.ToArray());
|
||||
|
||||
await interf.memoryManager.CheckAllDynamicallySizedAreas();
|
||||
|
||||
return plc;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@ using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using static MewtocolNet.RegisterBuilding.RBuild;
|
||||
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
internal class RegisterAssembler {
|
||||
|
||||
internal RegisterCollection collectionTarget;
|
||||
@@ -21,14 +20,17 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
}
|
||||
|
||||
internal List<BaseRegister> AssembleAll (RBuild rBuildData, bool flagAutoGenerated = false) {
|
||||
internal List<Register> AssembleAll(RBuildBase rBuildData, bool flagAutoGenerated = false) {
|
||||
|
||||
List<BaseRegister> generatedInstances = new List<BaseRegister>();
|
||||
List<Register> generatedInstances = new List<Register>();
|
||||
|
||||
foreach (var data in rBuildData.unfinishedList) {
|
||||
|
||||
var generatedInstance = Assemble(data);
|
||||
|
||||
generatedInstance.autoGenerated = flagAutoGenerated;
|
||||
data.registerOut?.Invoke(generatedInstance);
|
||||
|
||||
generatedInstances.Add(generatedInstance);
|
||||
|
||||
}
|
||||
@@ -37,87 +39,114 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
}
|
||||
|
||||
internal BaseRegister Assemble (SData data) {
|
||||
internal Register Assemble(StepData data) {
|
||||
|
||||
//parse all others where the type is known
|
||||
Type registerClassType = data.dotnetVarType.GetDefaultRegisterHoldingType();
|
||||
|
||||
BaseRegister generatedInstance = null;
|
||||
Register generatedInstance = null;
|
||||
|
||||
if (data.dotnetVarType.IsArray) {
|
||||
|
||||
Console.WriteLine();
|
||||
return new ArrayRegister(0, 0);
|
||||
|
||||
}
|
||||
|
||||
if (data.dotnetVarType.IsEnum) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as numeric register with enum target
|
||||
//as array register
|
||||
|
||||
var underlying = Enum.GetUnderlyingType(data.dotnetVarType);
|
||||
int numericSize = Marshal.SizeOf(underlying);
|
||||
Type elementType = data.dotnetVarType.GetElementType();
|
||||
|
||||
if (numericSize > 4)
|
||||
throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported");
|
||||
uint numericSizePerElement = (uint)elementType.DetermineTypeByteSize();
|
||||
|
||||
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
Type paramedClass = typeof(NumberRegister<>).MakeGenericType(data.dotnetVarType);
|
||||
ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] { typeof(uint), typeof(string) }, null);
|
||||
|
||||
var parameters = new object[] { data.memAddress, data.name };
|
||||
var instance = (BaseRegister)constr.Invoke(parameters);
|
||||
|
||||
instance.RegisterType = numericSize > 2 ? RegisterType.DDT : RegisterType.DT;
|
||||
|
||||
generatedInstance = instance;
|
||||
|
||||
} else if (registerClassType.IsGenericType) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as numeric register
|
||||
|
||||
//create a new bregister instance
|
||||
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
//int _adress, Type _enumType = null, string _name = null
|
||||
var parameters = new object[] { data.memAddress, data.name };
|
||||
var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null);
|
||||
|
||||
int numericSize = 0;
|
||||
bool isExtensionTypeDT = typeof(MewtocolExtensionTypeDT).IsAssignableFrom(data.dotnetVarType);
|
||||
bool isExtensionTypeDDT = typeof(MewtocolExtensionTypeDDT).IsAssignableFrom(data.dotnetVarType);
|
||||
|
||||
if (data.dotnetVarType.Namespace == "System") {
|
||||
numericSize = Marshal.SizeOf(data.dotnetVarType);
|
||||
} else if(isExtensionTypeDT) {
|
||||
numericSize = 2;
|
||||
} else if(isExtensionTypeDDT) {
|
||||
numericSize = 4;
|
||||
if (elementType.IsEnum && numericSizePerElement > 4) {
|
||||
if (data.boundProperty != null) {
|
||||
throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported ({data.boundProperty})");
|
||||
} else {
|
||||
throw new NotSupportedException($"The type {data.dotnetVarType} is not supported for NumberRegisters");
|
||||
throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
instance.RegisterType = numericSize > 2 ? RegisterType.DDT : RegisterType.DT;
|
||||
var sizeStateFlags = DynamicSizeState.None;
|
||||
|
||||
generatedInstance = instance;
|
||||
//string with size hint
|
||||
if (elementType == typeof(string) && data.perElementByteSizeHint != null) {
|
||||
|
||||
} else if (registerClassType == typeof(ArrayRegister) && data.byteSize != null) {
|
||||
numericSizePerElement = (uint)data.byteSizeHint + 4;
|
||||
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
|
||||
|
||||
//-------------------------------------------
|
||||
//as byte range register
|
||||
} else if (elementType == typeof(string)) {
|
||||
|
||||
ArrayRegister instance = new ArrayRegister(data.memAddress, (uint)data.byteSize, data.name);
|
||||
generatedInstance = instance;
|
||||
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate;
|
||||
|
||||
} else if (registerClassType == typeof(StringRegister)) {
|
||||
}
|
||||
|
||||
//-------------------------------------------
|
||||
//as byte range register
|
||||
var instance = (BaseRegister)new StringRegister(data.memAddress, data.name) {
|
||||
ReservedSize = (short)(data.stringSize ?? 0),
|
||||
var parameters = new object[] {
|
||||
data.memAddress,
|
||||
data.byteSizeHint,
|
||||
data.arrayIndicies,
|
||||
sizeStateFlags,
|
||||
data.name
|
||||
};
|
||||
|
||||
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
Type paramedClass = typeof(ArrayRegister<>).MakeGenericType(data.dotnetVarType);
|
||||
ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] {
|
||||
typeof(uint),
|
||||
typeof(uint),
|
||||
typeof(int[]),
|
||||
typeof(DynamicSizeState),
|
||||
typeof(string)
|
||||
}, null);
|
||||
|
||||
var instance = (Register)constr.Invoke(parameters);
|
||||
|
||||
instance.RegisterType = RegisterType.DT_BYTE_RANGE;
|
||||
|
||||
if (data.boundProperty != null && data.boundProperty.PropertyType != data.dotnetVarType)
|
||||
throw new TypeAccessException($"The bound property {data.boundProperty} must by of type: {data.dotnetVarType}");
|
||||
|
||||
generatedInstance = instance;
|
||||
|
||||
} else if (!data.regType.IsBoolean() && data.dotnetVarType.IsAllowedPlcCastingType()) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as single register
|
||||
|
||||
uint numericSize = (uint)data.dotnetVarType.DetermineTypeByteSize();
|
||||
|
||||
if (data.dotnetVarType.IsEnum && numericSize > 4) {
|
||||
if (data.boundProperty != null) {
|
||||
throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported ({data.boundProperty})");
|
||||
} else {
|
||||
throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
var sizeStateFlags = DynamicSizeState.None;
|
||||
|
||||
//string with size hint
|
||||
if(data.dotnetVarType == typeof(string) && data.byteSizeHint != null) {
|
||||
|
||||
numericSize = (uint)data.byteSizeHint + 4;
|
||||
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
|
||||
|
||||
} else if (data.dotnetVarType == typeof(string)) {
|
||||
|
||||
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate;
|
||||
|
||||
}
|
||||
|
||||
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
Type paramedClass = typeof(SingleRegister<>).MakeGenericType(data.dotnetVarType);
|
||||
ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] {
|
||||
typeof(uint), typeof(uint), typeof(DynamicSizeState) ,typeof(string)
|
||||
}, null);
|
||||
|
||||
var parameters = new object[] {
|
||||
data.memAddress,
|
||||
numericSize,
|
||||
sizeStateFlags,
|
||||
data.name
|
||||
};
|
||||
|
||||
var instance = (Register)constr.Invoke(parameters);
|
||||
|
||||
generatedInstance = instance;
|
||||
|
||||
} else if (data.regType.IsBoolean()) {
|
||||
|
||||
39
MewtocolNet/RegisterBuilding/StepData.cs
Normal file
39
MewtocolNet/RegisterBuilding/StepData.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using MewtocolNet.PublicEnums;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
internal class StepData {
|
||||
|
||||
//for referencing the output at builder level
|
||||
internal Action<IRegister> registerOut;
|
||||
|
||||
internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous;
|
||||
|
||||
internal bool wasAddressStringRangeBased;
|
||||
internal string originalParseStr;
|
||||
internal string name;
|
||||
internal RegisterType regType;
|
||||
internal uint memAddress;
|
||||
internal byte specialAddress;
|
||||
internal Type dotnetVarType;
|
||||
|
||||
//optional
|
||||
internal uint? byteSizeHint;
|
||||
internal uint? perElementByteSizeHint;
|
||||
internal int[] arrayIndicies;
|
||||
|
||||
internal int pollLevel = 1;
|
||||
|
||||
//only for building from attributes
|
||||
internal RegisterCollection regCollection;
|
||||
internal PropertyInfo boundProperty;
|
||||
|
||||
internal string typeDef;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.Registers {
|
||||
@@ -12,7 +10,9 @@ namespace MewtocolNet.Registers {
|
||||
/// <summary>
|
||||
/// Defines a register containing a string
|
||||
/// </summary>
|
||||
public class ArrayRegister : BaseRegister {
|
||||
public class ArrayRegister<T> : Register {
|
||||
|
||||
internal int[] indicies;
|
||||
|
||||
internal uint addressLength;
|
||||
|
||||
@@ -21,24 +21,21 @@ namespace MewtocolNet.Registers {
|
||||
/// </summary>
|
||||
public uint AddressLength => addressLength;
|
||||
|
||||
internal uint ReservedBytesSize { get; set; }
|
||||
|
||||
internal ushort? ReservedBitSize { get; set; }
|
||||
|
||||
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
|
||||
public ArrayRegister() =>
|
||||
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
|
||||
|
||||
internal ArrayRegister(uint _address, uint _reservedByteSize, string _name = null) {
|
||||
internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indicies , DynamicSizeState dynamicSizeSt, string _name = null) {
|
||||
|
||||
name = _name;
|
||||
memoryAddress = _address;
|
||||
ReservedBytesSize = _reservedByteSize;
|
||||
dynamicSizeState = dynamicSizeSt;
|
||||
indicies = _indicies;
|
||||
|
||||
//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++;
|
||||
var byteSize = _reservedByteSize;
|
||||
if (byteSize % 2 != 0) byteSize++;
|
||||
|
||||
RegisterType = RegisterType.DT_BYTE_RANGE;
|
||||
addressLength = Math.Max((byteSize / 2), 1);
|
||||
@@ -53,35 +50,72 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
if (Value == null) return "null";
|
||||
|
||||
if(Value != null && Value is BitArray bitArr) {
|
||||
|
||||
return bitArr.ToBitString();
|
||||
|
||||
} else {
|
||||
|
||||
return ((byte[])Value).ToHexString("-");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string BuildMewtocolQuery() {
|
||||
|
||||
StringBuilder asciistring = new StringBuilder("D");
|
||||
|
||||
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
|
||||
asciistring.Append((MemoryAddress + AddressLength - 1).ToString().PadLeft(5, '0'));
|
||||
|
||||
return asciistring.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetRegisterString() => "DT";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override uint GetRegisterAddressLen() => AddressLength;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<bool> WriteAsync(object value) {
|
||||
|
||||
if (!attachedInterface.IsConnected)
|
||||
throw MewtocolException.NotConnectedSend();
|
||||
|
||||
var encoded = PlcValueParser.Encode(this, (T)value);
|
||||
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
|
||||
|
||||
if (res) {
|
||||
|
||||
//find the underlying memory
|
||||
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
|
||||
.FirstOrDefault(x => x.IsSameAddressAndType(this));
|
||||
|
||||
if (matchingReg != null)
|
||||
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded);
|
||||
|
||||
AddSuccessWrite();
|
||||
UpdateHoldingValue(value);
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<object> ReadAsync() {
|
||||
|
||||
if (!attachedInterface.IsConnected)
|
||||
throw MewtocolException.NotConnectedSend();
|
||||
|
||||
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
|
||||
if (res == null) return null;
|
||||
|
||||
//var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
|
||||
//.FirstOrDefault(x => x.IsSameAddressAndType(this));
|
||||
|
||||
//if (matchingReg != null)
|
||||
// matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
|
||||
|
||||
return SetValueFromBytes(res);
|
||||
|
||||
}
|
||||
|
||||
internal override object SetValueFromBytes(byte[] bytes) {
|
||||
|
||||
AddSuccessRead();
|
||||
|
||||
var parsed = PlcValueParser.ParseArray<T>(this, indicies, bytes);
|
||||
UpdateHoldingValue(parsed);
|
||||
return parsed;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override void UpdateHoldingValue(object val) {
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
namespace MewtocolNet.Registers {
|
||||
|
||||
/// <summary>
|
||||
/// An interface for all register types
|
||||
@@ -3,13 +3,13 @@ using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.Registers {
|
||||
|
||||
public abstract class BaseRegister : IRegister, IRegisterInternal, INotifyPropertyChanged {
|
||||
public abstract class Register : IRegister, INotifyPropertyChanged {
|
||||
|
||||
/// <summary>
|
||||
/// Gets called whenever the value was changed
|
||||
@@ -26,6 +26,8 @@ namespace MewtocolNet.Registers {
|
||||
internal IMemoryArea underlyingMemory;
|
||||
internal bool autoGenerated;
|
||||
|
||||
internal DynamicSizeState dynamicSizeState;
|
||||
|
||||
internal object lastValue = null;
|
||||
internal string name;
|
||||
internal uint memoryAddress;
|
||||
@@ -34,6 +36,8 @@ namespace MewtocolNet.Registers {
|
||||
internal uint successfulReads = 0;
|
||||
internal uint successfulWrites = 0;
|
||||
|
||||
internal bool wasOverlapFitted = false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RegisterCollection ContainedCollection => containedCollection;
|
||||
|
||||
@@ -97,30 +101,19 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
public virtual Task<bool> WriteAsync(object data) => throw new NotImplementedException();
|
||||
|
||||
internal virtual Task UpdateDynamicSize () => throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Default accessors
|
||||
|
||||
public RegisterType GetRegisterType() => RegisterType;
|
||||
|
||||
public virtual string BuildMewtocolQuery() {
|
||||
|
||||
StringBuilder asciistring = new StringBuilder("D");
|
||||
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
|
||||
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
|
||||
return asciistring.ToString();
|
||||
|
||||
}
|
||||
|
||||
public virtual string GetStartingMemoryArea() => MemoryAddress.ToString();
|
||||
|
||||
public virtual byte? GetSpecialAddress() => null;
|
||||
|
||||
public virtual string GetValueString() => Value?.ToString() ?? "null";
|
||||
|
||||
public virtual string GetAsPLC() => Value?.ToString() ?? "null";
|
||||
|
||||
public virtual string GetRegisterString() => RegisterType.ToString();
|
||||
public virtual string GetRegisterString() => RegisterType == RegisterType.DT_BYTE_RANGE ? "DT" : RegisterType.ToString();
|
||||
|
||||
public virtual string GetCombinedName() => $"{GetContainerName()}{(GetContainerName() != null ? "." : "")}{Name ?? "Unnamed"}";
|
||||
|
||||
@@ -156,7 +149,7 @@ namespace MewtocolNet.Registers {
|
||||
else successfulWrites++;
|
||||
}
|
||||
|
||||
internal virtual bool IsSameAddressAndType (BaseRegister toCompare) {
|
||||
internal virtual bool IsSameAddressAndType(Register toCompare) {
|
||||
|
||||
return this.MemoryAddress == toCompare.MemoryAddress &&
|
||||
this.RegisterType == toCompare.RegisterType &&
|
||||
@@ -166,11 +159,38 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
internal virtual bool IsSameAddress (BaseRegister toCompare) {
|
||||
internal int AveragePollLevel(List<Register> testAgainst, PollLevelOverwriteMode mode) {
|
||||
|
||||
return (this.MemoryAddress == toCompare.MemoryAddress) &&
|
||||
(this.GetRegisterAddressLen() == toCompare.GetRegisterAddressLen()) &&
|
||||
(this.GetSpecialAddress() == toCompare.GetSpecialAddress());
|
||||
var whereAddressFitsInto = this.CanFitAddressRange(testAgainst)
|
||||
.Where(x => !x.wasOverlapFitted).ToList();
|
||||
|
||||
this.wasOverlapFitted = true;
|
||||
if (whereAddressFitsInto.Count == 0) return this.pollLevel;
|
||||
|
||||
whereAddressFitsInto.Add(this);
|
||||
|
||||
int avgLvl = mode == PollLevelOverwriteMode.Highest ?
|
||||
whereAddressFitsInto.Max(x => x.pollLevel) : whereAddressFitsInto.Min(x => x.pollLevel);
|
||||
|
||||
whereAddressFitsInto.ForEach(x => x.pollLevel = avgLvl);
|
||||
|
||||
return avgLvl;
|
||||
|
||||
}
|
||||
|
||||
internal IEnumerable<Register> CanFitAddressRange(List<Register> testAgainst) {
|
||||
|
||||
foreach (var reg in testAgainst) {
|
||||
|
||||
if (reg == this) continue;
|
||||
|
||||
bool otherFitsInsideSelf = (reg.MemoryAddress >= this.MemoryAddress) &&
|
||||
(reg.GetRegisterAddressEnd() <= this.GetRegisterAddressEnd()) &&
|
||||
(reg.GetSpecialAddress() == this.GetSpecialAddress());
|
||||
|
||||
if (otherFitsInsideSelf) yield return reg;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -207,7 +227,7 @@ namespace MewtocolNet.Registers {
|
||||
sb.Append($"Address: {GetRegisterWordRangeString()}\n");
|
||||
|
||||
if (GetType().IsGenericType)
|
||||
sb.Append($"Type: {RegisterType}, NumberRegister<{GetType().GenericTypeArguments[0]}>\n");
|
||||
sb.Append($"Type: {RegisterType}, {GetType().Name}<{GetType().GenericTypeArguments[0]}>\n");
|
||||
else
|
||||
sb.AppendLine($"Type: {RegisterType}, {GetType().Name}\n");
|
||||
|
||||
@@ -217,10 +237,6 @@ namespace MewtocolNet.Registers {
|
||||
sb.Append($"Value: {GetValueString()}\n");
|
||||
|
||||
sb.Append($"Reads: {successfulReads}, Writes: {successfulWrites}\n");
|
||||
sb.Append($"Underlying System Type: {underlyingSystemType}\n");
|
||||
|
||||
if (this is StringRegister sr)
|
||||
sb.Append($"Reserved: {sr.ReservedSize}, Used: {sr.UsedSize}\n");
|
||||
|
||||
if (GetSpecialAddress() != null)
|
||||
sb.Append($"SPAddress: {GetSpecialAddress():X1}\n");
|
||||
@@ -1,19 +1,12 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.Registers {
|
||||
|
||||
/// <summary>
|
||||
/// Defines a register containing a boolean
|
||||
/// </summary>
|
||||
public class BoolRegister : BaseRegister {
|
||||
public class BoolRegister : Register {
|
||||
|
||||
internal byte specialAddress;
|
||||
/// <summary>
|
||||
@@ -57,24 +50,6 @@ namespace MewtocolNet.Registers {
|
||||
/// <inheritdoc/>
|
||||
public override byte? GetSpecialAddress() => SpecialAddress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string BuildMewtocolQuery() {
|
||||
|
||||
//(R|X|Y)(area add [3] + special add [1])
|
||||
StringBuilder asciistring = new StringBuilder();
|
||||
|
||||
string prefix = RegisterType.ToString();
|
||||
string mem = MemoryAddress.ToString();
|
||||
string sp = SpecialAddress.ToString("X1");
|
||||
|
||||
asciistring.Append(prefix);
|
||||
asciistring.Append(mem.PadLeft(3, '0'));
|
||||
asciistring.Append(sp);
|
||||
|
||||
return asciistring.ToString();
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetMewName() {
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
internal interface IRegisterInternal {
|
||||
|
||||
event Action<object> ValueChanged;
|
||||
|
||||
//props
|
||||
|
||||
MewtocolInterface AttachedInterface { get; }
|
||||
|
||||
RegisterType RegisterType { get; }
|
||||
|
||||
string Name { get; }
|
||||
|
||||
object Value { get; }
|
||||
|
||||
uint MemoryAddress { get; }
|
||||
|
||||
RegisterCollection ContainedCollection { get; }
|
||||
|
||||
// setters
|
||||
|
||||
void ClearValue();
|
||||
|
||||
// Accessors
|
||||
|
||||
string GetRegisterString();
|
||||
|
||||
string GetCombinedName();
|
||||
|
||||
string GetContainerName();
|
||||
|
||||
string GetMewName();
|
||||
|
||||
byte? GetSpecialAddress();
|
||||
|
||||
string GetStartingMemoryArea();
|
||||
|
||||
string GetValueString();
|
||||
|
||||
string BuildMewtocolQuery();
|
||||
|
||||
uint GetRegisterAddressLen();
|
||||
|
||||
string GetRegisterWordRangeString();
|
||||
|
||||
//others
|
||||
|
||||
void TriggerNotifyChange();
|
||||
|
||||
Task<object> ReadAsync();
|
||||
|
||||
Task<bool> WriteAsync(object data);
|
||||
|
||||
string ToString();
|
||||
|
||||
string ToString(bool detailed);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using MewtocolNet.Logging;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static MewtocolNet.RegisterBuilding.RBuild;
|
||||
|
||||
namespace MewtocolNet.Registers {
|
||||
|
||||
@@ -16,65 +12,34 @@ namespace MewtocolNet.Registers {
|
||||
/// Defines a register containing a number
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the numeric value</typeparam>
|
||||
public class NumberRegister<T> : BaseRegister {
|
||||
public class SingleRegister<T> : Register {
|
||||
|
||||
internal uint addressLength;
|
||||
|
||||
/// <summary>
|
||||
/// The rgisters memory length
|
||||
/// </summary>
|
||||
public uint AddressLength => addressLength;
|
||||
|
||||
|
||||
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
|
||||
public NumberRegister() =>
|
||||
public SingleRegister() =>
|
||||
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
|
||||
|
||||
internal NumberRegister (uint _address, string _name = null) {
|
||||
internal SingleRegister(uint _address, uint _reservedByteSize, DynamicSizeState dynamicSizeSt, string _name = null) {
|
||||
|
||||
memoryAddress = _address;
|
||||
name = _name;
|
||||
dynamicSizeState = dynamicSizeSt;
|
||||
addressLength = _reservedByteSize / 2;
|
||||
|
||||
Type numType = typeof(T);
|
||||
uint areaLen = 0;
|
||||
if (_reservedByteSize == 2) RegisterType = RegisterType.DT;
|
||||
if(_reservedByteSize == 4) RegisterType = RegisterType.DDT;
|
||||
if (typeof(T) == typeof(string)) RegisterType = RegisterType.DT_BYTE_RANGE;
|
||||
|
||||
if (typeof(T).IsEnum) {
|
||||
|
||||
//for enums
|
||||
|
||||
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;
|
||||
CheckAddressOverflow(memoryAddress, addressLength);
|
||||
|
||||
lastValue = null;
|
||||
Console.WriteLine();
|
||||
|
||||
} else {
|
||||
|
||||
//for all others known pre-defined numeric structs
|
||||
|
||||
var allowedTypes = PlcValueParser.GetAllowDotnetTypes();
|
||||
if (!allowedTypes.Contains(numType))
|
||||
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
|
||||
|
||||
areaLen = (uint)(Marshal.SizeOf(numType) / 2) - 1;
|
||||
RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
|
||||
|
||||
lastValue = null;
|
||||
|
||||
}
|
||||
|
||||
CheckAddressOverflow(memoryAddress, areaLen);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string BuildMewtocolQuery() {
|
||||
|
||||
StringBuilder asciistring = new StringBuilder("D");
|
||||
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
|
||||
|
||||
int offsetAddress = 0;
|
||||
if(RegisterType == RegisterType.DDT)
|
||||
offsetAddress = 1;
|
||||
|
||||
asciistring.Append((MemoryAddress + offsetAddress).ToString().PadLeft(5, '0'));
|
||||
return asciistring.ToString();
|
||||
|
||||
}
|
||||
|
||||
@@ -116,7 +81,7 @@ namespace MewtocolNet.Registers {
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2);
|
||||
public override uint GetRegisterAddressLen() => AddressLength;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<bool> WriteAsync(object value) {
|
||||
@@ -124,6 +89,9 @@ namespace MewtocolNet.Registers {
|
||||
if (!attachedInterface.IsConnected)
|
||||
throw MewtocolException.NotConnectedSend();
|
||||
|
||||
if (dynamicSizeState.HasFlag(DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate))
|
||||
await UpdateDynamicSize();
|
||||
|
||||
var encoded = PlcValueParser.Encode(this, (T)value);
|
||||
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
|
||||
|
||||
@@ -151,19 +119,62 @@ namespace MewtocolNet.Registers {
|
||||
if (!attachedInterface.IsConnected)
|
||||
throw MewtocolException.NotConnectedSend();
|
||||
|
||||
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2, false);
|
||||
if(dynamicSizeState.HasFlag(DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate))
|
||||
await UpdateDynamicSize();
|
||||
|
||||
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
|
||||
if (res == null) return null;
|
||||
|
||||
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
|
||||
.FirstOrDefault(x => x.IsSameAddressAndType(this));
|
||||
|
||||
if (matchingReg != null)
|
||||
if (matchingReg != null) {
|
||||
|
||||
if (matchingReg is SingleRegister<string> sreg && this is SingleRegister<string> selfSreg) {
|
||||
sreg.addressLength = selfSreg.addressLength;
|
||||
sreg.dynamicSizeState = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
|
||||
}
|
||||
|
||||
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
|
||||
|
||||
}
|
||||
|
||||
|
||||
return SetValueFromBytes(res);
|
||||
|
||||
}
|
||||
|
||||
internal override async Task UpdateDynamicSize() {
|
||||
|
||||
if (typeof(T) == typeof(string)) await UpdateDynamicSizeString();
|
||||
|
||||
dynamicSizeState = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
|
||||
|
||||
}
|
||||
|
||||
private async Task UpdateDynamicSizeString () {
|
||||
|
||||
Logger.Log($"Calibrating dynamic register ({GetRegisterWordRangeString()}) from PLC source", LogLevel.Verbose, attachedInterface);
|
||||
|
||||
//get the string describer bytes
|
||||
var bytes = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, 4);
|
||||
|
||||
if (bytes == null || bytes.Length == 0 || bytes.All(x => x == 0x0)) {
|
||||
|
||||
throw new MewtocolException($"The string register ({GetMewName()}{MemoryAddress}) doesn't exist in the PLC program");
|
||||
|
||||
}
|
||||
|
||||
var reservedSize = BitConverter.ToInt16(bytes, 0);
|
||||
var usedSize = BitConverter.ToInt16(bytes, 2);
|
||||
var wordsSize = Math.Max(0, (uint)(2 + (reservedSize + 1) / 2));
|
||||
|
||||
addressLength = wordsSize;
|
||||
|
||||
CheckAddressOverflow(memoryAddress, wordsSize);
|
||||
|
||||
}
|
||||
|
||||
internal override object SetValueFromBytes(byte[] bytes) {
|
||||
|
||||
AddSuccessRead();
|
||||
@@ -1,101 +0,0 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.Registers {
|
||||
|
||||
/// <summary>
|
||||
/// Defines a register containing a string
|
||||
/// </summary>
|
||||
public class StringRegister : BaseRegister {
|
||||
|
||||
internal short ReservedSize { get; set; }
|
||||
|
||||
internal short UsedSize { get; set; }
|
||||
|
||||
internal uint WordsSize { get; set; }
|
||||
|
||||
internal bool isCalibratedFromPlc = false;
|
||||
|
||||
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
|
||||
public StringRegister() =>
|
||||
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
|
||||
|
||||
internal StringRegister (uint _address, string _name = null) {
|
||||
|
||||
name = _name;
|
||||
memoryAddress = _address;
|
||||
RegisterType = RegisterType.DT_BYTE_RANGE;
|
||||
|
||||
CheckAddressOverflow(memoryAddress, 0);
|
||||
|
||||
lastValue = null;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string BuildMewtocolQuery() {
|
||||
|
||||
StringBuilder asciistring = new StringBuilder("D");
|
||||
|
||||
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
|
||||
asciistring.Append((MemoryAddress + Math.Max(1, WordsSize) - 1).ToString().PadLeft(5, '0'));
|
||||
|
||||
return asciistring.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString() => Value == null ? "null" : $"'{Value}'";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetRegisterString() => "DT";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize);
|
||||
|
||||
internal async Task CalibrateFromPLC () {
|
||||
|
||||
Logger.Log($"Calibrating string ({PLCAddressName}) from PLC source", LogLevel.Verbose, attachedInterface);
|
||||
|
||||
//get the string describer bytes
|
||||
var bytes = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, 4, false);
|
||||
|
||||
if (bytes == null || bytes.Length == 0 || bytes.All(x => x == 0x0)) {
|
||||
|
||||
throw new MewtocolException($"The string register ({PLCAddressName}) doesn't exist in the PLC program");
|
||||
|
||||
}
|
||||
|
||||
ReservedSize = BitConverter.ToInt16(bytes, 0);
|
||||
UsedSize = BitConverter.ToInt16(bytes, 2);
|
||||
WordsSize = Math.Max(0, (uint)(2 + (ReservedSize + 1) / 2));
|
||||
|
||||
CheckAddressOverflow(memoryAddress, WordsSize);
|
||||
|
||||
isCalibratedFromPlc = true;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override void UpdateHoldingValue(object val) {
|
||||
|
||||
if ((val == null && lastValue != null) || val != lastValue) {
|
||||
|
||||
lastValue = val;
|
||||
|
||||
TriggerNotifyChange();
|
||||
attachedInterface.InvokeRegisterChanged(this);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
39
MewtocolNet/SetupClasses/InterfaceSettings.cs
Normal file
39
MewtocolNet/SetupClasses/InterfaceSettings.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace MewtocolNet.SetupClasses {
|
||||
|
||||
public class InterfaceSettings {
|
||||
|
||||
/// <summary>
|
||||
/// <code>
|
||||
/// This feature can improve read write times by a big margin but also
|
||||
/// block outgoing messages inbetween polling cycles more frequently
|
||||
/// </code>
|
||||
/// The max distance of the gap between registers (if there is a gap between
|
||||
/// adjacent registers) to merge them into one request <br/>
|
||||
/// Example: <br/>
|
||||
/// <example>
|
||||
/// We have a register at DT100 (1 word long) and a
|
||||
/// register at DT101 (1 word long) <br/>
|
||||
/// - If the max distance is 0 it will not merge them into one request<br/>
|
||||
/// - If the max distance is 1 it will merge them into one request<br/>
|
||||
/// - If the max distance is 2 and the next register is at DT102 it will also merge them and ignore the spacer byte in the response<br/>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
|
||||
public int MaxOptimizationDistance { get; set; } = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The overwrite mode for poll levels <br/>
|
||||
/// When set to <see cref="PollLevelOverwriteMode.Lowest"/> the lowest average poll level for overlapping registers gets used <br/>
|
||||
/// When set to <see cref="PollLevelOverwriteMode.Highest"/> the highest average poll level for overlapping registers gets used
|
||||
/// </summary>
|
||||
public PollLevelOverwriteMode PollLevelOverwriteMode { get; set; } = PollLevelOverwriteMode.Highest;
|
||||
|
||||
/// <summary>
|
||||
/// Defines how many WORD blocks the interface will send on a DT area write request before splitting up messages <br/>
|
||||
/// Higher numbers will result in a longer send and receive thread blocking time
|
||||
/// </summary>
|
||||
public int MaxDataBlocksPerWrite { get; set; } = 8;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.SetupClasses {
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MewtocolNet.Helpers;
|
||||
using MewtocolNet.Exceptions;
|
||||
|
||||
namespace MewtocolNet.TypeConversion {
|
||||
|
||||
@@ -58,7 +55,7 @@ namespace MewtocolNet.TypeConversion {
|
||||
|
||||
//default short DT conversion
|
||||
new PlcTypeConversion<short>(RegisterType.DT) {
|
||||
HoldingRegisterType = typeof(NumberRegister<short>),
|
||||
HoldingRegisterType = typeof(SingleRegister<short>),
|
||||
PlcVarType = PlcVarType.INT,
|
||||
FromRaw = (reg, bytes) => BitConverter.ToInt16(bytes, 0),
|
||||
ToRaw = (reg, value) => BitConverter.GetBytes(value),
|
||||
@@ -66,7 +63,7 @@ namespace MewtocolNet.TypeConversion {
|
||||
|
||||
//default ushort DT conversion
|
||||
new PlcTypeConversion<ushort>(RegisterType.DT) {
|
||||
HoldingRegisterType = typeof(NumberRegister<ushort>),
|
||||
HoldingRegisterType = typeof(SingleRegister<ushort>),
|
||||
PlcVarType = PlcVarType.UINT,
|
||||
FromRaw = (reg, bytes) => BitConverter.ToUInt16(bytes, 0),
|
||||
ToRaw = (reg, value) => BitConverter.GetBytes(value),
|
||||
@@ -74,7 +71,7 @@ namespace MewtocolNet.TypeConversion {
|
||||
|
||||
//default Word DT conversion
|
||||
new PlcTypeConversion<Word>(RegisterType.DT) {
|
||||
HoldingRegisterType = typeof(NumberRegister<Word>),
|
||||
HoldingRegisterType = typeof(SingleRegister<Word>),
|
||||
PlcVarType = PlcVarType.WORD,
|
||||
FromRaw = (reg, bytes) => new Word(bytes),
|
||||
ToRaw = (reg, value) => value.ToByteArray(),
|
||||
@@ -82,7 +79,7 @@ namespace MewtocolNet.TypeConversion {
|
||||
|
||||
//default int DDT conversion
|
||||
new PlcTypeConversion<int>(RegisterType.DDT) {
|
||||
HoldingRegisterType = typeof(NumberRegister<int>),
|
||||
HoldingRegisterType = typeof(SingleRegister<int>),
|
||||
PlcVarType = PlcVarType.DINT,
|
||||
FromRaw = (reg, bytes) => BitConverter.ToInt32(bytes, 0),
|
||||
ToRaw = (reg, value) => BitConverter.GetBytes(value),
|
||||
@@ -90,7 +87,7 @@ namespace MewtocolNet.TypeConversion {
|
||||
|
||||
//default uint DDT conversion
|
||||
new PlcTypeConversion<uint>(RegisterType.DDT) {
|
||||
HoldingRegisterType = typeof(NumberRegister<uint>),
|
||||
HoldingRegisterType = typeof(SingleRegister<uint>),
|
||||
PlcVarType = PlcVarType.UDINT,
|
||||
FromRaw = (reg, bytes) => BitConverter.ToUInt32(bytes, 0),
|
||||
ToRaw = (reg, value) => BitConverter.GetBytes(value),
|
||||
@@ -98,7 +95,7 @@ namespace MewtocolNet.TypeConversion {
|
||||
|
||||
//default DWord DDT conversion
|
||||
new PlcTypeConversion<DWord>(RegisterType.DDT) {
|
||||
HoldingRegisterType = typeof(NumberRegister<DWord>),
|
||||
HoldingRegisterType = typeof(SingleRegister<DWord>),
|
||||
PlcVarType = PlcVarType.DWORD,
|
||||
FromRaw = (reg, bytes) => new DWord(bytes),
|
||||
ToRaw = (reg, value) => value.ToByteArray(),
|
||||
@@ -106,7 +103,7 @@ namespace MewtocolNet.TypeConversion {
|
||||
|
||||
//default float DDT conversion
|
||||
new PlcTypeConversion<float>(RegisterType.DDT) {
|
||||
HoldingRegisterType = typeof(NumberRegister<float>),
|
||||
HoldingRegisterType = typeof(SingleRegister<float>),
|
||||
PlcVarType = PlcVarType.REAL,
|
||||
FromRaw = (reg, bytes) => BitConverter.ToSingle(bytes, 0),
|
||||
ToRaw = (reg, value) => BitConverter.GetBytes(value),
|
||||
@@ -114,7 +111,7 @@ namespace MewtocolNet.TypeConversion {
|
||||
|
||||
//default TimeSpan DDT conversion
|
||||
new PlcTypeConversion<TimeSpan>(RegisterType.DDT) {
|
||||
HoldingRegisterType = typeof(NumberRegister<TimeSpan>),
|
||||
HoldingRegisterType = typeof(SingleRegister<TimeSpan>),
|
||||
PlcVarType = PlcVarType.TIME,
|
||||
FromRaw = (reg, bytes) => {
|
||||
|
||||
@@ -131,19 +128,11 @@ namespace MewtocolNet.TypeConversion {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
//default byte array DT Range conversion, direct pass through
|
||||
new PlcTypeConversion<byte[]>(RegisterType.DT_BYTE_RANGE) {
|
||||
HoldingRegisterType = typeof(ArrayRegister),
|
||||
FromRaw = (reg, bytes) => bytes,
|
||||
ToRaw = (reg, value) => value,
|
||||
},
|
||||
|
||||
//default string DT Range conversion Example bytes: (04 00 03 00 XX XX XX)
|
||||
//first 4 bytes are reserved size (2 bytes) and used size (2 bytes)
|
||||
//the remaining bytes are the ascii bytes for the string
|
||||
new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) {
|
||||
HoldingRegisterType = typeof(StringRegister),
|
||||
HoldingRegisterType = typeof(SingleRegister<string>),
|
||||
PlcVarType = PlcVarType.STRING,
|
||||
FromRaw = (reg, bytes) => {
|
||||
|
||||
@@ -157,24 +146,25 @@ namespace MewtocolNet.TypeConversion {
|
||||
short actualLen = BitConverter.ToInt16(bytes, 2);
|
||||
|
||||
//skip 4 bytes because they only describe the length
|
||||
return Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray());
|
||||
string gotVal = Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray());
|
||||
|
||||
return gotVal;
|
||||
|
||||
},
|
||||
ToRaw = (reg, value) => {
|
||||
|
||||
var sReg = (StringRegister)reg;
|
||||
|
||||
if(value.Length > sReg.ReservedSize)
|
||||
value = value.Substring(0, sReg.ReservedSize);
|
||||
|
||||
int padLen = sReg.ReservedSize;
|
||||
if(sReg.ReservedSize % 2 != 0) padLen++;
|
||||
int padLen = value.Length;
|
||||
if(value.Length % 2 != 0) padLen++;
|
||||
|
||||
var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0'));
|
||||
|
||||
List<byte> finalBytes = new List<byte>();
|
||||
finalBytes.AddRange(BitConverter.GetBytes(sReg.ReservedSize));
|
||||
finalBytes.AddRange(BitConverter.GetBytes((short)value.Length));
|
||||
|
||||
short reserved = (short)(reg.GetRegisterAddressLen() * 2 - 4);
|
||||
short used = (short)value.Length;
|
||||
|
||||
finalBytes.AddRange(BitConverter.GetBytes(reserved));
|
||||
finalBytes.AddRange(BitConverter.GetBytes(used));
|
||||
finalBytes.AddRange(strBytes);
|
||||
|
||||
return finalBytes.ToArray();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
internal interface IPlcTypeConverter {
|
||||
|
||||
object FromRawData(IRegister register, byte[] data);
|
||||
object FromRawData(Register register, byte[] data);
|
||||
|
||||
byte[] ToRawData(IRegister register, object value);
|
||||
byte[] ToRawData(Register register, object value);
|
||||
|
||||
Type GetDotnetType();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -13,9 +13,9 @@ namespace MewtocolNet {
|
||||
|
||||
public Type HoldingRegisterType { get; set; }
|
||||
|
||||
public Func<IRegister, byte[], T> FromRaw { get; set; }
|
||||
public Func<Register, byte[], T> FromRaw { get; set; }
|
||||
|
||||
public Func<IRegister, T, byte[]> ToRaw { get; set; }
|
||||
public Func<Register, T, byte[]> ToRaw { get; set; }
|
||||
|
||||
public PlcTypeConversion(RegisterType plcType) {
|
||||
|
||||
@@ -32,9 +32,9 @@ namespace MewtocolNet {
|
||||
|
||||
public PlcVarType GetPlcVarType() => PlcVarType;
|
||||
|
||||
public object FromRawData(IRegister register, byte[] data) => FromRaw.Invoke(register, data);
|
||||
public object FromRawData(Register register, byte[] data) => FromRaw.Invoke(register, data);
|
||||
|
||||
public byte[] ToRawData(IRegister register, object value) => ToRaw.Invoke(register, (T)value);
|
||||
public byte[] ToRawData(Register register, object value) => ToRaw.Invoke(register, (T)value);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using MewtocolNet.Registers;
|
||||
using MewtocolNet.TypeConversion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
|
||||
namespace MewtocolNet {
|
||||
@@ -12,54 +11,156 @@ namespace MewtocolNet {
|
||||
|
||||
private static List<IPlcTypeConverter> conversions => Conversions.items;
|
||||
|
||||
internal static T Parse<T> (IRegister register, byte[] bytes) {
|
||||
internal static T Parse<T>(Register register, byte[] bytes) {
|
||||
|
||||
IPlcTypeConverter converter;
|
||||
Type underlyingType;
|
||||
|
||||
//special case for enums
|
||||
if (typeof(T).IsEnum) {
|
||||
|
||||
var underlyingNumberType = typeof(T).GetEnumUnderlyingType();
|
||||
|
||||
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingNumberType);
|
||||
underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||
|
||||
} else {
|
||||
|
||||
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
|
||||
underlyingType = typeof(T);
|
||||
|
||||
}
|
||||
|
||||
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingType);
|
||||
|
||||
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 {underlyingType} doesn't exist");
|
||||
|
||||
return (T)converter.FromRawData(register, bytes);
|
||||
|
||||
}
|
||||
|
||||
internal static byte[] Encode<T> (IRegister register, T value) {
|
||||
internal static T ParseArray <T>(Register register, int[] indices, byte[] bytes) {
|
||||
|
||||
IPlcTypeConverter converter;
|
||||
Type underlyingElementType;
|
||||
|
||||
//special case for enums
|
||||
if (typeof(T).IsEnum) {
|
||||
|
||||
var underlyingNumberType = typeof(T).GetEnumUnderlyingType();
|
||||
|
||||
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingNumberType);
|
||||
underlyingElementType = typeof(T).GetElementType().GetEnumUnderlyingType();
|
||||
|
||||
} else {
|
||||
|
||||
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
|
||||
underlyingElementType = typeof(T).GetElementType();
|
||||
|
||||
}
|
||||
|
||||
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingElementType);
|
||||
|
||||
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 {underlyingElementType} doesn't exist");
|
||||
|
||||
//parse the array from one to n dimensions
|
||||
var outArray = Array.CreateInstance(underlyingElementType, indices);
|
||||
|
||||
if(outArray.GetType() == typeof(byte[])) {
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
}
|
||||
|
||||
int sizePerItem = underlyingElementType.DetermineTypeByteSize();
|
||||
var iterateItems = indices.Aggregate((a, x) => a * x);
|
||||
var indexer = new int[indices.Length];
|
||||
|
||||
for (int i = 0; i < iterateItems; i++) {
|
||||
|
||||
int j = i * sizePerItem;
|
||||
|
||||
var currentItem = bytes.Skip(j).Take(sizePerItem).ToArray();
|
||||
var value = converter.FromRawData(register, currentItem);
|
||||
|
||||
for (int remainder = i, k = indices.Length - 1; k >= 0; k--) {
|
||||
|
||||
int currentDimension = indices[k];
|
||||
indexer[k] = remainder % currentDimension;
|
||||
remainder = remainder / currentDimension;
|
||||
|
||||
}
|
||||
|
||||
outArray.SetValue(value, indexer);
|
||||
|
||||
}
|
||||
|
||||
return (T)(object)outArray;
|
||||
|
||||
}
|
||||
|
||||
static void ConvertFlatArrayToDim (
|
||||
IPlcTypeConverter converter,
|
||||
Register register,
|
||||
byte[] source,
|
||||
Array target,
|
||||
int sizePerVal,
|
||||
int[] dims,
|
||||
int currentIndex,
|
||||
int currentArrayIndex
|
||||
) {
|
||||
|
||||
if (currentIndex == dims.Length - 1) {
|
||||
|
||||
for (int i = 0; i < dims[currentIndex]; i++) {
|
||||
|
||||
byte[] rawDataItem = source.Skip(currentArrayIndex).Take(sizePerVal).ToArray();
|
||||
var value = converter.FromRawData(register, rawDataItem);
|
||||
|
||||
target.SetValue(value, i);
|
||||
currentArrayIndex += sizePerVal;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (int i = 0; i < dims[currentIndex]; i++) {
|
||||
|
||||
Array innerArray = (Array)target.GetValue(i);
|
||||
ConvertFlatArrayToDim(converter, register, source, innerArray, sizePerVal, dims, currentIndex + 1, currentArrayIndex);
|
||||
currentArrayIndex += innerArray.Length * sizePerVal;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
internal static byte[] Encode<T>(Register register, T value) {
|
||||
|
||||
IPlcTypeConverter converter;
|
||||
Type underlyingType;
|
||||
|
||||
//special case for enums
|
||||
if (typeof(T).IsEnum) {
|
||||
|
||||
underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||
|
||||
} else {
|
||||
|
||||
underlyingType = typeof(T);
|
||||
|
||||
}
|
||||
|
||||
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingType);
|
||||
|
||||
if (converter == null)
|
||||
throw new MewtocolException($"A converter for the type {underlyingType} doesn't exist");
|
||||
|
||||
return converter.ToRawData(register, value);
|
||||
|
||||
}
|
||||
|
||||
//internal static byte[] EncodeArray (IRegister register, T value) {
|
||||
|
||||
|
||||
//}
|
||||
|
||||
public static List<Type> GetAllowDotnetTypes() => conversions.Select(x => x.GetDotnetType()).ToList();
|
||||
|
||||
public static List<Type> GetAllowRegisterTypes() => conversions.Select(x => x.GetHoldingRegisterType()).ToList();
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.TypeConversion;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -30,7 +27,7 @@ namespace MewtocolNet {
|
||||
|
||||
internal static bool IsAllowedPlcCastingType(this Type type) {
|
||||
|
||||
if (type.IsEnum) return true;
|
||||
if (type.IsEnum || type == typeof(string)) return true;
|
||||
|
||||
return allowedCastingTypes.Contains(type);
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.UnderlyingRegisters {
|
||||
@@ -84,7 +82,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
public byte[] GetUnderlyingBytes(BaseRegister reg) {
|
||||
public byte[] GetUnderlyingBytes(Register reg) {
|
||||
|
||||
int byteLen = (int)(reg.GetRegisterAddressLen() * 2);
|
||||
|
||||
@@ -103,7 +101,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
public void SetUnderlyingBytes(BaseRegister reg, byte[] bytes) {
|
||||
public void SetUnderlyingBytes(Register reg, byte[] bytes) {
|
||||
|
||||
SetUnderlyingBytes(bytes, reg.MemoryAddress);
|
||||
|
||||
@@ -118,17 +116,16 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
private async Task CheckDynamicallySizedRegistersAsync () {
|
||||
internal async Task CheckDynamicallySizedRegistersAsync() {
|
||||
|
||||
//calibrating at runtime sized registers
|
||||
var uncalibratedStringRegisters = managedRegisters
|
||||
.SelectMany(x => x.Linked)
|
||||
.Where(x => x is StringRegister sreg && !sreg.isCalibratedFromPlc)
|
||||
.Cast<StringRegister>()
|
||||
.Where(x => x.dynamicSizeState.HasFlag(DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate))
|
||||
.ToList();
|
||||
|
||||
foreach (var register in uncalibratedStringRegisters)
|
||||
await register.CalibrateFromPLC();
|
||||
await register.UpdateDynamicSize();
|
||||
|
||||
if (uncalibratedStringRegisters.Count > 0)
|
||||
mewInterface.memoryManager.LinkAndMergeRegisters();
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
internal interface IMemoryArea {
|
||||
|
||||
byte[] GetUnderlyingBytes(BaseRegister reg);
|
||||
byte[] GetUnderlyingBytes(Register reg);
|
||||
|
||||
void SetUnderlyingBytes(BaseRegister reg, byte[] bytes);
|
||||
void SetUnderlyingBytes(Register reg, byte[] bytes);
|
||||
|
||||
void UpdateAreaRegisterValues();
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
@@ -11,7 +9,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
internal uint AddressEnd;
|
||||
|
||||
internal List<BaseRegister> Linked = new List<BaseRegister>();
|
||||
internal List<Register> Linked = new List<Register>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using MewtocolNet.Helpers;
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.SetupClasses;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -16,7 +13,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
internal int maxOptimizationDistance = 8;
|
||||
internal int maxRegistersPerGroup = -1;
|
||||
internal bool allowByteRegDupes;
|
||||
internal PollLevelOverwriteMode pollLevelOrMode = PollLevelOverwriteMode.Highest;
|
||||
|
||||
private int wrAreaSize;
|
||||
private int dtAreaSize;
|
||||
@@ -47,31 +44,31 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
internal void LinkAndMergeRegisters (List<BaseRegister> registers = null) {
|
||||
internal async Task OnPlcConnected () {
|
||||
|
||||
//for self calling
|
||||
if (registers == null) registers = GetAllRegisters().ToList();
|
||||
|
||||
//pre combine per address
|
||||
var groupedByAdd = registers
|
||||
.GroupBy(x => new {
|
||||
x.MemoryAddress,
|
||||
len = x.GetRegisterAddressLen(),
|
||||
spadd = x.GetSpecialAddress(),
|
||||
});
|
||||
|
||||
//poll level merging
|
||||
foreach (var addressGroup in groupedByAdd) {
|
||||
|
||||
//determine highest poll level for same addresses
|
||||
var highestPollLevel = addressGroup.Max(x => x.pollLevel);
|
||||
|
||||
//apply poll level to all registers in same group
|
||||
foreach (var reg in addressGroup)
|
||||
reg.pollLevel = highestPollLevel;
|
||||
//check all area for dynamic sized registers
|
||||
await CheckAllDynamicallySizedAreas();
|
||||
|
||||
}
|
||||
|
||||
internal void LinkAndMergeRegisters(List<Register> registers = null) {
|
||||
|
||||
//for self calling
|
||||
if (registers == null) {
|
||||
|
||||
//get a copy of the current ones
|
||||
registers = GetAllRegisters().ToList();
|
||||
//clear old ones
|
||||
ClearAllRegisters();
|
||||
|
||||
}
|
||||
|
||||
//maxes the highest poll level for all registers that contain each other
|
||||
registers
|
||||
.OrderByDescending(x => x.GetRegisterAddressLen())
|
||||
.ToList()
|
||||
.ForEach(x => x.AveragePollLevel(registers, pollLevelOrMode));
|
||||
|
||||
//insert into area
|
||||
foreach (var reg in registers) {
|
||||
|
||||
@@ -93,7 +90,6 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
}
|
||||
|
||||
//order
|
||||
|
||||
foreach (var lvl in pollLevels) {
|
||||
|
||||
foreach (var area in lvl.dataAreas) {
|
||||
@@ -102,11 +98,13 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
lvl.dataAreas = lvl.dataAreas.OrderBy(x => x.AddressStart).ToList();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void TestPollLevelExistence (BaseRegister reg) {
|
||||
private void TestPollLevelExistence(Register reg) {
|
||||
|
||||
if (!pollLevelConfigs.ContainsKey(1)) {
|
||||
pollLevelConfigs.Add(1, new PollLevelConfig {
|
||||
@@ -131,7 +129,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
private bool AddToWRArea (BaseRegister insertReg) {
|
||||
private bool AddToWRArea(Register insertReg) {
|
||||
|
||||
var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel);
|
||||
|
||||
@@ -187,7 +185,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
private void AddToDTArea (BaseRegister insertReg) {
|
||||
private void AddToDTArea(Register insertReg) {
|
||||
|
||||
uint regInsAddStart = insertReg.MemoryAddress;
|
||||
uint regInsAddEnd = insertReg.MemoryAddress + insertReg.GetRegisterAddressLen() - 1;
|
||||
@@ -260,7 +258,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
insertReg.underlyingMemory = targetArea;
|
||||
|
||||
if (insertReg.name == null) {
|
||||
if (insertReg.autoGenerated && insertReg.name == null) {
|
||||
insertReg.name = $"auto_{Guid.NewGuid().ToString("N")}";
|
||||
}
|
||||
|
||||
@@ -284,6 +282,21 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
dupedTypeReg.WithBoundProperties(insertReg.boundProperties);
|
||||
} else {
|
||||
existinglinkedGroup.Linked.Add(insertReg);
|
||||
existinglinkedGroup.Linked = existinglinkedGroup.Linked.OrderBy(x => x.MemoryAddress).ToList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal async Task CheckAllDynamicallySizedAreas () {
|
||||
|
||||
foreach (var pollLevel in pollLevels.ToArray()) {
|
||||
|
||||
foreach (var area in pollLevel.dataAreas.ToArray()) {
|
||||
|
||||
await area.CheckDynamicallySizedRegistersAsync();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -356,9 +369,9 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
sb.AppendLine($"\n> ==== Poll lvl {pollLevel.level} ====");
|
||||
|
||||
sb.AppendLine();
|
||||
if (pollLevelConfigs[pollLevel.level].delay != null) {
|
||||
if (pollLevelConfigs.ContainsKey(pollLevel.level) && pollLevelConfigs[pollLevel.level].delay != null) {
|
||||
sb.AppendLine($"> Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms");
|
||||
} else {
|
||||
} else if (pollLevelConfigs.ContainsKey(pollLevel.level)) {
|
||||
sb.AppendLine($"> Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations");
|
||||
}
|
||||
sb.AppendLine($"> Level read time: {pollLevel.lastReadTimeMs}ms");
|
||||
@@ -381,7 +394,10 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
foreach (var linkedG in area.managedRegisters) {
|
||||
|
||||
if (prevGroup != null && (linkedG.AddressStart - prevGroup.AddressEnd - 1 > 0)) {
|
||||
if (prevGroup != null &&
|
||||
linkedG.AddressStart != prevGroup.AddressStart &&
|
||||
linkedG.AddressEnd > prevGroup.AddressEnd &&
|
||||
linkedG.AddressStart - prevGroup.AddressEnd > 1) {
|
||||
|
||||
var dist = linkedG.AddressStart - prevGroup.AddressEnd - 1;
|
||||
|
||||
@@ -423,9 +439,19 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
internal IEnumerable<BaseRegister> GetAllRegisters () {
|
||||
internal void ClearAllRegisters () {
|
||||
|
||||
List<BaseRegister> registers = new List<BaseRegister>();
|
||||
foreach (var lvl in pollLevels) {
|
||||
|
||||
lvl.dataAreas.Clear();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal IEnumerable<Register> GetAllRegisters() {
|
||||
|
||||
List<Register> registers = new List<Register>();
|
||||
|
||||
foreach (var lvl in pollLevels) {
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -15,7 +14,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
internal byte[] wordData = new byte[2];
|
||||
|
||||
internal List<BaseRegister> linkedRegisters = new List<BaseRegister>();
|
||||
internal List<Register> linkedRegisters = new List<Register>();
|
||||
|
||||
public ulong AddressStart => addressStart;
|
||||
|
||||
@@ -30,24 +29,24 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
|
||||
}
|
||||
public void SetUnderlyingBytes(BaseRegister reg, byte[] bytes) {
|
||||
public void SetUnderlyingBytes(Register reg, byte[] bytes) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
public byte[] GetUnderlyingBytes(BaseRegister reg) {
|
||||
public byte[] GetUnderlyingBytes(Register reg) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> ReadRegisterAsync(BaseRegister reg) {
|
||||
public async Task<bool> ReadRegisterAsync(Register reg) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes) {
|
||||
public async Task<bool> WriteRegisterAsync(Register reg, byte[] bytes) {
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user