mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Add underlying byte data for registers
- change backend logic for register r/w - remade interface builder pattern for better syntactic sugar - refined tests
This commit is contained in:
@@ -25,7 +25,7 @@ internal class OnlineCommand : CommandLineExcecuteable {
|
||||
string ip = split[0];
|
||||
int port = int.Parse(split[1]);
|
||||
|
||||
using (var plc = Mewtocol.Ethernet(ip, port)) {
|
||||
using (var plc = Mewtocol.Ethernet(ip, port).Build()) {
|
||||
|
||||
await AfterSetup(plc);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ internal class ScanCommand : CommandLineExcecuteable {
|
||||
ctx.Status($"Getting cassette PLC {item.Cassette.IPAddress}:{item.Cassette.Port}")
|
||||
.Spinner(Spinner.Known.Dots);
|
||||
|
||||
var dev = Mewtocol.Ethernet(item.Cassette.IPAddress, item.Cassette.Port);
|
||||
var dev = Mewtocol.Ethernet(item.Cassette.IPAddress, item.Cassette.Port).Build();
|
||||
dev.ConnectTimeout = 1000;
|
||||
await dev.ConnectAsync();
|
||||
item.PLCInf = dev.PlcInfo;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
using MewTerminal.Commands;
|
||||
using MewTerminal.Commands.OnlineCommands;
|
||||
using MewtocolNet.Logging;
|
||||
using Spectre.Console;
|
||||
using System.Globalization;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MewtocolNet.DocAttributes;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -118,7 +119,9 @@ namespace MewtocolNet {
|
||||
/// <returns>A <see cref="T:byte[]"/> or null of failed</returns>
|
||||
internal static byte[] ParseDTRawStringAsBytes (this string _onString) {
|
||||
|
||||
var res = new Regex(@"\%([0-9]{2})\$RD(?<data>.*)(?<csum>..)..").Match(_onString);
|
||||
_onString = _onString.Replace("\r", "");
|
||||
|
||||
var res = new Regex(@"\%([0-9]{2})\$RD(?<data>.*)(?<csum>..)").Match(_onString);
|
||||
if (res.Success) {
|
||||
|
||||
string val = res.Groups["data"].Value;
|
||||
@@ -248,6 +251,16 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
internal static bool CompareIsDuplicateNonCast (this BaseRegister reg1, BaseRegister compare) {
|
||||
|
||||
bool valCompare = reg1.GetType() != compare.GetType() &&
|
||||
reg1.MemoryAddress == compare.MemoryAddress &&
|
||||
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
|
||||
|
||||
return valCompare;
|
||||
|
||||
}
|
||||
|
||||
internal static bool CompareIsNameDuplicate(this IRegisterInternal reg1, IRegisterInternal compare) {
|
||||
|
||||
return ( reg1.Name != null || compare.Name != null) && reg1.Name == compare.Name;
|
||||
|
||||
@@ -124,6 +124,11 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
IEnumerable<IRegister> GetAllRegisters();
|
||||
|
||||
/// <summary>
|
||||
/// Explains the register internal layout at this moment in time
|
||||
/// </summary>
|
||||
string Explain();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,18 +38,6 @@ namespace MewtocolNet {
|
||||
/// <param name="_station">Station Number of the PLC</param>
|
||||
void ConfigureConnection(string _ip, int _port = 9094, int _station = 1);
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a poller to the interface
|
||||
/// </summary>
|
||||
IPlcEthernet WithPoller();
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a register collection object to
|
||||
/// the interface that can be updated automatically.
|
||||
/// </summary>
|
||||
/// <param name="collection">The type of the collection base class</param>
|
||||
IPlcEthernet AddRegisterCollection(RegisterCollection collection);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -58,18 +58,6 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
Task ConnectAsync(Action onTryingConfig);
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a poller to the interface
|
||||
/// </summary>
|
||||
IPlcSerial WithPoller();
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a register collection object to
|
||||
/// the interface that can be updated automatically.
|
||||
/// </summary>
|
||||
/// <param name="collection">The type of the collection base class</param>
|
||||
IPlcSerial AddRegisterCollection(RegisterCollection collection);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@@ -20,11 +22,13 @@ namespace MewtocolNet {
|
||||
/// <param name="port"></param>
|
||||
/// <param name="station">Plc station number</param>
|
||||
/// <returns></returns>
|
||||
public static IPlcEthernet Ethernet (string ip, int port = 9094, int station = 1) {
|
||||
public static PostInit<IPlcEthernet> Ethernet (string ip, int port = 9094, int station = 1) {
|
||||
|
||||
var instance = new MewtocolInterfaceTcp();
|
||||
instance.ConfigureConnection(ip, port, station);
|
||||
return instance;
|
||||
return new PostInit<IPlcEthernet> {
|
||||
intf = instance
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -35,11 +39,13 @@ namespace MewtocolNet {
|
||||
/// <param name="port"></param>
|
||||
/// <param name="station">Plc station number</param>
|
||||
/// <returns></returns>
|
||||
public static IPlcEthernet Ethernet(IPAddress ip, int port = 9094, int station = 1) {
|
||||
public static PostInit<IPlcEthernet> Ethernet(IPAddress ip, int port = 9094, int station = 1) {
|
||||
|
||||
var instance = new MewtocolInterfaceTcp();
|
||||
instance.ConfigureConnection(ip, port, station);
|
||||
return instance;
|
||||
return new PostInit<IPlcEthernet> {
|
||||
intf = instance
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -53,13 +59,15 @@ namespace MewtocolNet {
|
||||
/// <param name="stopBits"></param>
|
||||
/// <param name="station"></param>
|
||||
/// <returns></returns>
|
||||
public static IPlcSerial Serial (string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, int station = 1) {
|
||||
public static PostInit<IPlcSerial> Serial (string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, int station = 1) {
|
||||
|
||||
TestPortName(portName);
|
||||
|
||||
var instance = new MewtocolInterfaceSerial();
|
||||
instance.ConfigureConnection(portName, (int)baudRate, (int)dataBits, parity, stopBits, station);
|
||||
return instance;
|
||||
return new PostInit<IPlcSerial> {
|
||||
intf = instance
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -69,14 +77,16 @@ namespace MewtocolNet {
|
||||
/// <param name="portName"></param>
|
||||
/// <param name="station"></param>
|
||||
/// <returns></returns>
|
||||
public static IPlcSerial SerialAuto (string portName, int station = 1) {
|
||||
public static PostInit<IPlcSerial> SerialAuto (string portName, int station = 1) {
|
||||
|
||||
TestPortName(portName);
|
||||
|
||||
var instance = new MewtocolInterfaceSerial();
|
||||
instance.ConfigureConnection(portName, station);
|
||||
instance.ConfigureConnectionAuto();
|
||||
return instance;
|
||||
return new PostInit<IPlcSerial> {
|
||||
intf = instance
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -89,6 +99,112 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
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; } = 8;
|
||||
|
||||
/// <summary>
|
||||
/// The max number of registers per request group
|
||||
/// </summary>
|
||||
public int MaxRegistersPerGroup { get; set; } = -1;
|
||||
|
||||
}
|
||||
|
||||
public class PostInit<T> {
|
||||
|
||||
internal T intf;
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a auto poller to the interface that reads all registers
|
||||
/// cyclic
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public PostInit<T> WithPoller() {
|
||||
|
||||
if (intf is MewtocolInterface imew) {
|
||||
imew.usePoller = true;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// General setting for the memory manager
|
||||
/// </summary>
|
||||
public PostInit<T> WithMemoryManagerSettings (Action<MemoryManagerSettings> settings) {
|
||||
|
||||
var res = new MemoryManagerSettings();
|
||||
settings.Invoke(res);
|
||||
|
||||
if (res.MaxOptimizationDistance < 0)
|
||||
throw new NotSupportedException($"A value lower than 0 is not allowed for " +
|
||||
$"{nameof(MemoryManagerSettings.MaxOptimizationDistance)}");
|
||||
|
||||
if (intf is MewtocolInterface imew) {
|
||||
|
||||
imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance;
|
||||
imew.memoryManager.maxRegistersPerGroup = res.MaxRegistersPerGroup;
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A builder for attaching register collections
|
||||
/// </summary>
|
||||
public EndInit<T> WithRegisterCollections(Action<RegisterCollectionCollector> collector) {
|
||||
|
||||
var res = new RegisterCollectionCollector();
|
||||
collector.Invoke(res);
|
||||
|
||||
if (intf is MewtocolInterface imew) {
|
||||
imew.WithRegisterCollections(res.collections);
|
||||
}
|
||||
|
||||
return new EndInit<T> {
|
||||
postInit = this
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds and returns the final plc interface
|
||||
/// </summary>
|
||||
public T Build() => intf;
|
||||
|
||||
}
|
||||
|
||||
public class EndInit<T> {
|
||||
|
||||
internal PostInit<T> postInit;
|
||||
|
||||
/// <summary>
|
||||
/// Builds and returns the final plc interface
|
||||
/// </summary>
|
||||
public T Build() => postInit.intf;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
public partial class MewtocolInterface : IPlc, INotifyPropertyChanged, IDisposable {
|
||||
public abstract partial class MewtocolInterface : IPlc, INotifyPropertyChanged, IDisposable {
|
||||
|
||||
#region Private fields
|
||||
|
||||
@@ -49,6 +50,7 @@ namespace MewtocolNet {
|
||||
internal volatile bool pollerTaskStopped = true;
|
||||
internal volatile bool pollerFirstCycle;
|
||||
internal bool usePoller = false;
|
||||
internal MemoryAreaManager memoryManager;
|
||||
|
||||
internal List<BaseRegister> RegistersUnderlying { get; private set; } = new List<BaseRegister>();
|
||||
internal IEnumerable<IRegisterInternal> RegistersInternal => RegistersUnderlying.Cast<IRegisterInternal>();
|
||||
@@ -142,6 +144,8 @@ namespace MewtocolNet {
|
||||
|
||||
private protected MewtocolInterface () {
|
||||
|
||||
memoryManager = new MemoryAreaManager(this);
|
||||
|
||||
Connected += MewtocolInterface_Connected;
|
||||
RegisterChanged += OnRegisterChanged;
|
||||
|
||||
@@ -164,6 +168,8 @@ namespace MewtocolNet {
|
||||
$"{(o.Name != null ? $"({o.Name}) " : "")}" +
|
||||
$"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this);
|
||||
|
||||
OnRegisterChangedUpdateProps((IRegisterInternal)o);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -352,8 +358,9 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
//request next frame
|
||||
var writeBuffer = Encoding.UTF8.GetBytes("%01**&\r");
|
||||
var writeBuffer = Encoding.UTF8.GetBytes($"%{GetStationNumber()}**&\r");
|
||||
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
||||
Logger.Log($">> Requested next frame", LogLevel.Critical, this);
|
||||
wasMultiFramedResponse = true;
|
||||
|
||||
}
|
||||
@@ -521,6 +528,8 @@ namespace MewtocolNet {
|
||||
|
||||
#endregion
|
||||
|
||||
public string Explain() => memoryManager.ExplainLayout();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ using MewtocolNet.Logging;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using MewtocolNet.Registers;
|
||||
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;
|
||||
@@ -18,7 +20,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// The PLC com interface class
|
||||
/// </summary>
|
||||
public partial class MewtocolInterface {
|
||||
public abstract partial class MewtocolInterface {
|
||||
|
||||
internal Task pollCycleTask;
|
||||
|
||||
@@ -30,14 +32,16 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// Current poller cycle duration
|
||||
/// </summary>
|
||||
public int PollerCycleDurationMs {
|
||||
get => pollerCycleDurationMs;
|
||||
public int PollerCycleDurationMs {
|
||||
get => pollerCycleDurationMs;
|
||||
private set {
|
||||
pollerCycleDurationMs = value;
|
||||
OnPropChange();
|
||||
}
|
||||
}
|
||||
|
||||
private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
|
||||
|
||||
#region Register Polling
|
||||
|
||||
/// <summary>
|
||||
@@ -70,7 +74,7 @@ namespace MewtocolNet {
|
||||
/// useful if you want to use a custom update frequency
|
||||
/// </summary>
|
||||
/// <returns>The number of inidvidual mewtocol commands sent</returns>
|
||||
public async Task<int> RunPollerCylceManual () {
|
||||
public async Task<int> RunPollerCylceManual() {
|
||||
|
||||
if (!pollerTaskStopped)
|
||||
throw new NotSupportedException($"The poller is already running, " +
|
||||
@@ -86,7 +90,7 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
//polls all registers one by one (slow)
|
||||
internal async Task Poll () {
|
||||
internal async Task Poll() {
|
||||
|
||||
Logger.Log("Poller is attaching", LogLevel.Info, this);
|
||||
|
||||
@@ -111,13 +115,15 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
private async Task OnMultiFrameCycle () {
|
||||
private async Task OnMultiFrameCycle() {
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
await UpdateRCPRegisters();
|
||||
//await UpdateRCPRegisters();
|
||||
|
||||
await UpdateDTRegisters();
|
||||
//await UpdateDTRegisters();
|
||||
|
||||
await memoryManager.PollAllAreasAsync();
|
||||
|
||||
await GetPLCInfoAsync();
|
||||
|
||||
@@ -130,7 +136,7 @@ namespace MewtocolNet {
|
||||
|
||||
#region Smart register polling methods
|
||||
|
||||
private async Task UpdateRCPRegisters () {
|
||||
private async Task UpdateRCPRegisters() {
|
||||
|
||||
//build booleans
|
||||
var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister))
|
||||
@@ -145,7 +151,7 @@ namespace MewtocolNet {
|
||||
|
||||
int toReadRegistersCount = 8;
|
||||
|
||||
if(i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder;
|
||||
if (i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder;
|
||||
|
||||
var rcpString = new StringBuilder($"%{GetStationNumber()}#RCP{toReadRegistersCount}");
|
||||
|
||||
@@ -166,23 +172,23 @@ namespace MewtocolNet {
|
||||
|
||||
var register = rcpList[i + k];
|
||||
|
||||
if((bool)register.Value != resultBitArray[k]) {
|
||||
if ((bool)register.Value != resultBitArray[k]) {
|
||||
register.SetValueFromPLC(resultBitArray[k]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task UpdateDTRegisters () {
|
||||
private async Task UpdateDTRegisters() {
|
||||
|
||||
foreach (var reg in RegistersUnderlying) {
|
||||
|
||||
var type = reg.GetType();
|
||||
|
||||
if(reg.RegisterType.IsNumericDTDDT() || reg.RegisterType == RegisterType.DT_BYTE_RANGE) {
|
||||
if (reg.RegisterType.IsNumericDTDDT() || reg.RegisterType == RegisterType.DT_BYTE_RANGE) {
|
||||
|
||||
var lastVal = reg.Value;
|
||||
var rwReg = (IRegisterInternal)reg;
|
||||
@@ -203,144 +209,81 @@ namespace MewtocolNet {
|
||||
|
||||
#region Register Colleciton adding
|
||||
|
||||
internal MewtocolInterface WithRegisterCollection (RegisterCollection collection) {
|
||||
/// <summary>
|
||||
/// Adds the given register collection and all its registers with attributes to the register list
|
||||
/// </summary>
|
||||
internal void WithRegisterCollections(List<RegisterCollection> collections) {
|
||||
|
||||
collection.PLCInterface = this;
|
||||
if (registerCollections.Count != 0)
|
||||
throw new NotSupportedException("Register collections can only be build once");
|
||||
|
||||
var props = collection.GetType().GetProperties();
|
||||
List<RegisterBuildInfo> buildInfos = new List<RegisterBuildInfo>();
|
||||
|
||||
foreach (var prop in props) {
|
||||
foreach (var collection in collections) {
|
||||
|
||||
var attributes = prop.GetCustomAttributes(true);
|
||||
collection.PLCInterface = this;
|
||||
|
||||
string propName = prop.Name;
|
||||
foreach (var attr in attributes) {
|
||||
var props = collection.GetType().GetProperties();
|
||||
|
||||
if (attr is RegisterAttribute cAttribute) {
|
||||
foreach (var prop in props) {
|
||||
|
||||
var attributes = prop.GetCustomAttributes(true);
|
||||
|
||||
string propName = prop.Name;
|
||||
foreach (var attr in attributes) {
|
||||
|
||||
if (attr is RegisterAttribute cAttribute) {
|
||||
|
||||
if (!prop.PropertyType.IsAllowedPlcCastingType()) {
|
||||
throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})");
|
||||
}
|
||||
|
||||
var dotnetType = prop.PropertyType;
|
||||
|
||||
buildInfos.Add(new RegisterBuildInfo {
|
||||
mewAddress = cAttribute.MewAddress,
|
||||
dotnetCastType = dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType,
|
||||
collectionTarget = collection,
|
||||
boundPropTarget = prop,
|
||||
});
|
||||
|
||||
if(!prop.PropertyType.IsAllowedPlcCastingType()) {
|
||||
throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})");
|
||||
}
|
||||
|
||||
var dotnetType = prop.PropertyType;
|
||||
|
||||
AddRegister(new RegisterBuildInfo {
|
||||
mewAddress = cAttribute.MewAddress,
|
||||
memoryAddress = cAttribute.MemoryArea,
|
||||
specialAddress = cAttribute.SpecialAddress,
|
||||
memorySizeBytes = cAttribute.ByteLength,
|
||||
registerType = cAttribute.RegisterType,
|
||||
dotnetCastType = dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType,
|
||||
collectionType = collection.GetType(),
|
||||
name = prop.Name,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (collection != null) {
|
||||
registerCollections.Add(collection);
|
||||
collection.OnInterfaceLinked(this);
|
||||
}
|
||||
|
||||
Connected += (i) => {
|
||||
if (collection != null)
|
||||
collection.OnInterfaceLinkedAndOnline(this);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
RegisterChanged += (reg) => {
|
||||
AddRegisters(buildInfos);
|
||||
|
||||
//register is used bitwise
|
||||
if (reg.GetType() == typeof(BytesRegister)) {
|
||||
}
|
||||
|
||||
for (int i = 0; i < props.Length; i++) {
|
||||
/// <summary>
|
||||
/// Writes back the values changes of the underlying registers to the corrosponding property
|
||||
/// </summary>
|
||||
private void OnRegisterChangedUpdateProps(IRegisterInternal reg) {
|
||||
|
||||
var prop = props[i];
|
||||
var bitWiseFound = prop.GetCustomAttributes(true)
|
||||
.FirstOrDefault(y => y.GetType() == typeof(RegisterAttribute) && ((RegisterAttribute)y).MemoryArea == reg.MemoryAddress);
|
||||
var collection = reg.ContainedCollection;
|
||||
if (collection == null) return;
|
||||
|
||||
if (bitWiseFound != null) {
|
||||
var props = collection.GetType().GetProperties();
|
||||
|
||||
var casted = (RegisterAttribute)bitWiseFound;
|
||||
var bitIndex = casted.AssignedBitIndex;
|
||||
//set the specific bit array if needed
|
||||
//prop.SetValue(collection, bitAr);
|
||||
//collection.TriggerPropertyChanged(prop.Name);
|
||||
|
||||
BitArray bitAr = null;
|
||||
|
||||
if (reg is NumberRegister<short> reg16) {
|
||||
var bytes = BitConverter.GetBytes((short)reg16.Value);
|
||||
bitAr = new BitArray(bytes);
|
||||
} else if (reg is NumberRegister<int> reg32) {
|
||||
var bytes = BitConverter.GetBytes((int)reg32.Value);
|
||||
bitAr = new BitArray(bytes);
|
||||
}
|
||||
|
||||
if (bitAr != null && bitIndex < bitAr.Length && bitIndex >= 0) {
|
||||
|
||||
//set the specific bit index if needed
|
||||
prop.SetValue(collection, bitAr[bitIndex]);
|
||||
collection.TriggerPropertyChanged(prop.Name);
|
||||
|
||||
} else if (bitAr != null) {
|
||||
|
||||
//set the specific bit array if needed
|
||||
prop.SetValue(collection, bitAr);
|
||||
collection.TriggerPropertyChanged(prop.Name);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//updating normal properties
|
||||
var foundToUpdate = props.FirstOrDefault(x => x.Name == reg.Name);
|
||||
|
||||
if (foundToUpdate != null) {
|
||||
|
||||
var foundAttributes = foundToUpdate.GetCustomAttributes(true);
|
||||
var foundAttr = foundAttributes.FirstOrDefault(x => x.GetType() == typeof(RegisterAttribute));
|
||||
|
||||
if (foundAttr == null)
|
||||
return;
|
||||
|
||||
var registerAttr = (RegisterAttribute)foundAttr;
|
||||
|
||||
//check if bit parse mode
|
||||
if (registerAttr.AssignedBitIndex == -1) {
|
||||
|
||||
HashSet<Type> NumericTypes = new HashSet<Type> {
|
||||
typeof(bool),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(float),
|
||||
typeof(TimeSpan),
|
||||
typeof(string)
|
||||
};
|
||||
|
||||
var regValue = ((IRegister)reg).Value;
|
||||
|
||||
if (NumericTypes.Any(x => foundToUpdate.PropertyType == x)) {
|
||||
foundToUpdate.SetValue(collection, regValue);
|
||||
}
|
||||
|
||||
if (foundToUpdate.PropertyType.IsEnum) {
|
||||
foundToUpdate.SetValue(collection, regValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
collection.TriggerPropertyChanged(foundToUpdate.Name);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if (collection != null)
|
||||
collection.OnInterfaceLinked(this);
|
||||
|
||||
Connected += (i) => {
|
||||
if (collection != null)
|
||||
collection.OnInterfaceLinkedAndOnline(this);
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
@@ -349,10 +292,10 @@ namespace MewtocolNet {
|
||||
#region Register Adding
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddRegister(IRegister register) => AddRegister(register as BaseRegister);
|
||||
public void AddRegister (IRegister register) => AddRegister(register as BaseRegister);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddRegister(BaseRegister register) {
|
||||
public void AddRegister (BaseRegister register) {
|
||||
|
||||
if (CheckDuplicateRegister(register))
|
||||
throw MewtocolException.DupeRegister(register);
|
||||
@@ -368,28 +311,56 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
internal void AddRegister (RegisterBuildInfo buildInfo) {
|
||||
// Used for internal property based register building
|
||||
internal void AddRegisters (List<RegisterBuildInfo> buildInfos) {
|
||||
|
||||
var builtRegister = buildInfo.Build();
|
||||
//build all from attribute
|
||||
List<BaseRegister> registers = new List<BaseRegister>();
|
||||
|
||||
//is bitwise and the register list already contains that area register
|
||||
if(builtRegister.GetType() == typeof(BytesRegister) && CheckDuplicateRegister(builtRegister, out var existing)) {
|
||||
foreach (var buildInfo in buildInfos) {
|
||||
|
||||
return;
|
||||
var builtRegister = buildInfo.BuildForCollectionAttribute();
|
||||
|
||||
int? linkLen = null;
|
||||
|
||||
if(builtRegister is BytesRegister bReg) {
|
||||
|
||||
linkLen = (int?)bReg.ReservedBytesSize ?? bReg.ReservedBitSize;
|
||||
|
||||
}
|
||||
|
||||
//attach the property and collection
|
||||
builtRegister.WithBoundProperty(new RegisterPropTarget {
|
||||
BoundProperty = buildInfo.boundPropTarget,
|
||||
LinkLength = linkLen,
|
||||
});
|
||||
|
||||
builtRegister.WithRegisterCollection(buildInfo.collectionTarget);
|
||||
|
||||
builtRegister.attachedInterface = this;
|
||||
registers.Add(builtRegister);
|
||||
|
||||
}
|
||||
|
||||
if (CheckDuplicateRegister(builtRegister))
|
||||
throw MewtocolException.DupeRegister(builtRegister);
|
||||
//order by address
|
||||
registers = registers.OrderBy(x => x.GetSpecialAddress()).ToList();
|
||||
registers = registers.OrderBy(x => x.MemoryAddress).ToList();
|
||||
|
||||
if(CheckDuplicateNameRegister(builtRegister))
|
||||
throw MewtocolException.DupeNameRegister(builtRegister);
|
||||
//link to memory manager
|
||||
for (int i = 0, j = 0; i < registers.Count; i++) {
|
||||
|
||||
if (CheckOverlappingRegister(builtRegister, out var regB))
|
||||
throw MewtocolException.OverlappingRegister(builtRegister, regB);
|
||||
BaseRegister reg = registers[i];
|
||||
reg.name = $"auto_prop_register_{j + 1}";
|
||||
|
||||
builtRegister.attachedInterface = this;
|
||||
RegistersUnderlying.Add(builtRegister);
|
||||
//link the memory area to the register
|
||||
if (memoryManager.LinkRegister(reg)) {
|
||||
|
||||
RegistersUnderlying.Add(reg);
|
||||
j++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -480,7 +451,6 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
|
||||
internal void PropertyRegisterWasSet(string propName, object value) {
|
||||
|
||||
_ = SetRegisterAsync(GetRegister(propName), value);
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
public partial class MewtocolInterface {
|
||||
public abstract partial class MewtocolInterface {
|
||||
|
||||
#region PLC info getters
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ using MewtocolNet.RegisterAttributes;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
public class MewtocolInterfaceSerial : MewtocolInterface, IPlcSerial {
|
||||
public sealed class MewtocolInterfaceSerial : MewtocolInterface, IPlcSerial {
|
||||
|
||||
private bool autoSerial;
|
||||
|
||||
@@ -50,13 +50,6 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
public IPlcSerial AddRegisterCollection (RegisterCollection collection) {
|
||||
|
||||
WithRegisterCollection(collection);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetConnectionInfo() {
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// The PLC com interface class
|
||||
/// </summary>
|
||||
public class MewtocolInterfaceTcp : MewtocolInterface, IPlcEthernet {
|
||||
public sealed class MewtocolInterfaceTcp : MewtocolInterface, IPlcEthernet {
|
||||
|
||||
//TCP
|
||||
internal TcpClient client;
|
||||
@@ -29,22 +29,6 @@ namespace MewtocolNet {
|
||||
|
||||
internal MewtocolInterfaceTcp () : base() { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPlcEthernet WithPoller () {
|
||||
|
||||
usePoller = true;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPlcEthernet AddRegisterCollection (RegisterCollection collection) {
|
||||
|
||||
WithRegisterCollection(collection);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
#region TCP connection state handling
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
20
MewtocolNet/RegisterAttributes/PollFrequencyAttribute.cs
Normal file
20
MewtocolNet/RegisterAttributes/PollFrequencyAttribute.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace MewtocolNet.RegisterAttributes {
|
||||
/// <summary>
|
||||
/// Defines the behavior of a register property
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class PollFrequencyAttribute : Attribute {
|
||||
|
||||
internal int skipEachCycle;
|
||||
|
||||
public PollFrequencyAttribute(int eachCycleN) {
|
||||
|
||||
skipEachCycle = eachCycleN;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,13 +9,6 @@ namespace MewtocolNet.RegisterAttributes {
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class RegisterAttribute : Attribute {
|
||||
|
||||
internal RegisterType? RegisterType;
|
||||
|
||||
internal uint MemoryArea = 0;
|
||||
internal uint ByteLength = 2;
|
||||
internal byte SpecialAddress = 0x0;
|
||||
|
||||
internal BitCount BitCount;
|
||||
internal int AssignedBitIndex = -1;
|
||||
|
||||
internal string MewAddress = null;
|
||||
@@ -26,65 +19,6 @@ namespace MewtocolNet.RegisterAttributes {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for string type or numeric registers
|
||||
/// </summary>
|
||||
/// <param name="memoryArea">The area in the plcs memory</param>
|
||||
public RegisterAttribute(uint memoryArea) {
|
||||
|
||||
MemoryArea = memoryArea;
|
||||
|
||||
}
|
||||
|
||||
public RegisterAttribute(uint memoryArea, uint byteLength) {
|
||||
|
||||
MemoryArea = memoryArea;
|
||||
ByteLength = byteLength;
|
||||
|
||||
}
|
||||
|
||||
public RegisterAttribute(uint memoryArea, BitCount bitCount) {
|
||||
|
||||
MemoryArea = memoryArea;
|
||||
BitCount = bitCount;
|
||||
AssignedBitIndex = 0;
|
||||
|
||||
RegisterType = BitCount == BitCount.B16 ? MewtocolNet.RegisterType.DT : MewtocolNet.RegisterType.DDT;
|
||||
|
||||
}
|
||||
|
||||
public RegisterAttribute(uint memoryArea, BitCount bitCount, int bitIndex) {
|
||||
|
||||
MemoryArea = memoryArea;
|
||||
BitCount = bitCount;
|
||||
AssignedBitIndex = bitIndex;
|
||||
|
||||
RegisterType = BitCount == BitCount.B16 ? MewtocolNet.RegisterType.DT : MewtocolNet.RegisterType.DDT;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for boolean registers
|
||||
/// </summary>
|
||||
public RegisterAttribute(IOType type, byte spAdress = 0x0) {
|
||||
|
||||
MemoryArea = 0;
|
||||
RegisterType = (RegisterType)(int)type;
|
||||
SpecialAddress = spAdress;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for boolean registers
|
||||
/// </summary>
|
||||
public RegisterAttribute(IOType type, uint memoryArea, byte spAdress = 0x0) {
|
||||
|
||||
MemoryArea = memoryArea;
|
||||
RegisterType = (RegisterType)(int)type;
|
||||
SpecialAddress = spAdress;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.RegisterAttributes {
|
||||
|
||||
public class RegisterCollectionCollector {
|
||||
|
||||
internal List<RegisterCollection> collections = new List<RegisterCollection>();
|
||||
|
||||
public RegisterCollectionCollector AddCollection (RegisterCollection collection) {
|
||||
|
||||
collections.Add(collection);
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public RegisterCollectionCollector AddCollection<T> () where T : RegisterCollection {
|
||||
|
||||
var instance = (RegisterCollection)Activator.CreateInstance(typeof(T));
|
||||
|
||||
collections.Add(instance);
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
26
MewtocolNet/RegisterAttributes/RegisterPropTarget.cs
Normal file
26
MewtocolNet/RegisterAttributes/RegisterPropTarget.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.RegisterAttributes {
|
||||
|
||||
internal class RegisterPropTarget {
|
||||
|
||||
//propinfo of the bound property
|
||||
internal PropertyInfo BoundProperty;
|
||||
|
||||
//general number of bits or bytes to read back to the prop
|
||||
internal int? LinkLength;
|
||||
|
||||
public override string ToString() {
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append($"{BoundProperty}");
|
||||
if(LinkLength != null) sb.Append($" -Len: {LinkLength}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
@@ -20,14 +21,20 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
internal byte? specialAddress;
|
||||
|
||||
internal RegisterType? registerType;
|
||||
|
||||
internal Type dotnetCastType;
|
||||
internal Type collectionType;
|
||||
|
||||
internal RegisterCollection collectionTarget;
|
||||
internal PropertyInfo boundPropTarget;
|
||||
|
||||
internal BaseRegister BuildForCollectionAttribute () {
|
||||
|
||||
return (BaseRegister)RegBuilder.Factory.FromPlcRegName(mewAddress, name).AsType(dotnetCastType).Build();
|
||||
|
||||
}
|
||||
|
||||
internal BaseRegister Build () {
|
||||
|
||||
//Has mew address use this before the default checks
|
||||
if (mewAddress != null) return BuildFromMewAddress();
|
||||
|
||||
//parse enums
|
||||
if (dotnetCastType.IsEnum) {
|
||||
|
||||
@@ -46,8 +53,8 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
var parameters = new object[] { memoryAddress, name };
|
||||
var instance = (BaseRegister)constr.Invoke(parameters);
|
||||
|
||||
if (collectionType != null)
|
||||
instance.WithCollectionType(collectionType);
|
||||
if (collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
@@ -56,22 +63,18 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
//parse all others where the type is known
|
||||
RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault();
|
||||
Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType();
|
||||
bool isBytesRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister);
|
||||
|
||||
bool isBoolRegister = regType.IsBoolean();
|
||||
|
||||
bool isBytesArrRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister) && dotnetCastType == typeof(byte[]);
|
||||
|
||||
bool isBytesBitsRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister) && dotnetCastType == typeof(BitArray);
|
||||
|
||||
bool isStringRegister = !registerClassType.IsGenericType && registerClassType == typeof(StringRegister);
|
||||
|
||||
if (regType.IsNumericDTDDT() && (dotnetCastType == typeof(bool))) {
|
||||
bool isNormalNumericResiter = regType.IsNumericDTDDT() && !isBytesArrRegister && !isBytesBitsRegister && !isStringRegister;
|
||||
|
||||
//-------------------------------------------
|
||||
//as numeric register with boolean bit target
|
||||
//create a new bregister instance
|
||||
var instance = new BytesRegister(memoryAddress, memorySizeBytes.Value, name);
|
||||
|
||||
if (collectionType != null)
|
||||
instance.WithCollectionType(collectionType);
|
||||
|
||||
return instance;
|
||||
|
||||
} else if (regType.IsNumericDTDDT() && !isBytesRegister && !isStringRegister) {
|
||||
if (isNormalNumericResiter) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as numeric register
|
||||
@@ -83,28 +86,43 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
var parameters = new object[] { memoryAddress, name };
|
||||
var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null);
|
||||
|
||||
if(collectionType != null)
|
||||
instance.WithCollectionType(collectionType);
|
||||
if(collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
if(isBytesRegister) {
|
||||
if(isBytesArrRegister) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as byte range register
|
||||
|
||||
BytesRegister instance = new BytesRegister(memoryAddress, memorySizeBytes.Value, name);
|
||||
instance.ReservedBytesSize = (ushort)memorySizeBytes.Value;
|
||||
|
||||
if (collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
if(isBytesBitsRegister) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as bit range register
|
||||
|
||||
BytesRegister instance;
|
||||
|
||||
if(memorySizeBits != null) {
|
||||
if (memorySizeBits != null) {
|
||||
instance = new BytesRegister(memoryAddress, memorySizeBits.Value, name);
|
||||
} else {
|
||||
instance = new BytesRegister(memoryAddress, memorySizeBytes.Value, name);
|
||||
instance = new BytesRegister(memoryAddress, 16, name);
|
||||
}
|
||||
|
||||
if (collectionType != null)
|
||||
instance.WithCollectionType(collectionType);
|
||||
if (collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
@@ -116,14 +134,14 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
//as byte range register
|
||||
var instance = (BaseRegister)new StringRegister(memoryAddress, name);
|
||||
|
||||
if (collectionType != null)
|
||||
instance.WithCollectionType(collectionType);
|
||||
if (collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
if (regType.IsBoolean()) {
|
||||
if (isBoolRegister) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as boolean register
|
||||
@@ -134,8 +152,8 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
var instance = new BoolRegister(io, spAddr.Value, areaAddr, name);
|
||||
|
||||
if (collectionType != null)
|
||||
((IRegisterInternal)instance).WithCollectionType(collectionType);
|
||||
if (collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
@@ -145,12 +163,6 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
}
|
||||
|
||||
private BaseRegister BuildFromMewAddress () {
|
||||
|
||||
return (BaseRegister)RegBuilder.Factory.FromPlcRegName(mewAddress, name).AsType(dotnetCastType).Build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using System;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -12,12 +16,19 @@ namespace MewtocolNet.Registers {
|
||||
/// </summary>
|
||||
public event Action<object> ValueChanged;
|
||||
|
||||
//links to
|
||||
internal RegisterCollection containedCollection;
|
||||
internal MewtocolInterface attachedInterface;
|
||||
internal List<RegisterPropTarget> boundToProps = new List<RegisterPropTarget>();
|
||||
|
||||
internal IMemoryArea underlyingMemory;
|
||||
internal object lastValue = null;
|
||||
internal Type collectionType;
|
||||
internal string name;
|
||||
internal uint memoryAddress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RegisterCollection ContainedCollection => containedCollection;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MewtocolInterface AttachedInterface => attachedInterface;
|
||||
|
||||
@@ -27,9 +38,6 @@ namespace MewtocolNet.Registers {
|
||||
/// <inheritdoc/>
|
||||
public RegisterType RegisterType { get; protected set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Type CollectionType => collectionType;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => name;
|
||||
|
||||
@@ -62,12 +70,22 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
public void WithCollectionType(Type colType) => collectionType = colType;
|
||||
internal virtual object SetValueFromBytes(byte[] bytes) => throw new NotImplementedException();
|
||||
|
||||
internal void WithRegisterCollection (RegisterCollection collection) => containedCollection = collection;
|
||||
|
||||
internal void WithBoundProperty(RegisterPropTarget propInfo) => boundToProps.Add(propInfo);
|
||||
|
||||
#region Read / Write
|
||||
|
||||
public virtual Task<object> ReadAsync() => throw new NotImplementedException();
|
||||
|
||||
public virtual Task<bool> WriteAsync(object data) => throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Default accessors
|
||||
|
||||
public Type GetCollectionType() => CollectionType;
|
||||
|
||||
public RegisterType GetRegisterType() => RegisterType;
|
||||
|
||||
public virtual string BuildMewtocolQuery() {
|
||||
@@ -89,9 +107,9 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
public virtual string GetRegisterString() => RegisterType.ToString();
|
||||
|
||||
public virtual string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
|
||||
public virtual string GetCombinedName() => $"{GetContainerName()}{(GetContainerName() != null ? "." : "")}{Name ?? "Unnamed"}";
|
||||
|
||||
public virtual string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
|
||||
public virtual string GetContainerName() => $"{(containedCollection != null ? $"{containedCollection.GetType().Name}" : null)}";
|
||||
|
||||
public virtual string GetMewName() => $"{GetRegisterString()}{MemoryAddress}";
|
||||
|
||||
@@ -101,14 +119,6 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read / Write
|
||||
|
||||
public virtual async Task<object> ReadAsync() => throw new NotImplementedException();
|
||||
|
||||
public virtual async Task<bool> WriteAsync(object data) => throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
protected virtual void CheckAddressOverflow (uint addressStart, uint addressLen) {
|
||||
|
||||
if (addressStart < 0)
|
||||
@@ -119,18 +129,33 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
public override string ToString() => $"{GetMewName()}{(Name != null ? $" ({Name})" : "")} - Value: {GetValueString()}";
|
||||
public override string ToString() {
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(GetMewName());
|
||||
if(Name != null) sb.Append($" ({Name})");
|
||||
if (Value != null) sb.Append($" Val: {GetValueString()}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
}
|
||||
|
||||
public virtual string ToString(bool additional) {
|
||||
|
||||
if (!additional) return this.ToString();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine($"PLC Naming: {GetMewName()}");
|
||||
sb.AppendLine($"MewName: {GetMewName()}");
|
||||
sb.AppendLine($"Name: {Name ?? "Not named"}");
|
||||
sb.AppendLine($"Value: {GetValueString()}");
|
||||
sb.AppendLine($"Register Type: {RegisterType}");
|
||||
sb.AppendLine($"Memory Address: {MemoryAddress}");
|
||||
sb.AppendLine($"Address: {GetRegisterWordRangeString()}");
|
||||
if(GetSpecialAddress() != null) sb.AppendLine($"SPAddress: {GetSpecialAddress()}");
|
||||
if (GetType().IsGenericType) sb.AppendLine($"Type: NumberRegister<{GetType().GenericTypeArguments[0]}>");
|
||||
else sb.AppendLine($"Type: {GetType()}");
|
||||
if(containedCollection != null) sb.AppendLine($"In collection: {containedCollection.GetType()}");
|
||||
if(boundToProps != null && boundToProps.Count != 0)
|
||||
sb.AppendLine($"Bound props: {string.Join(",", boundToProps)}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
@@ -63,7 +64,8 @@ namespace MewtocolNet.Registers {
|
||||
if (!attachedInterface.IsConnected) return null;
|
||||
|
||||
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
||||
if(read == null) return null;
|
||||
if(read == null) return null;
|
||||
|
||||
var parsed = PlcValueParser.Parse<bool>(this, read);
|
||||
|
||||
SetValueFromPLC(parsed);
|
||||
@@ -76,8 +78,13 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
if (!attachedInterface.IsConnected) return false;
|
||||
|
||||
var res = await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (bool)data));
|
||||
if (res) SetValueFromPLC(data);
|
||||
var encoded = PlcValueParser.Encode(this, (bool)data);
|
||||
|
||||
var res = await attachedInterface.WriteRawRegisterAsync(this, encoded);
|
||||
if (res) {
|
||||
SetValueFromPLC(data);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
@@ -132,23 +139,6 @@ namespace MewtocolNet.Registers {
|
||||
/// <inheritdoc/>
|
||||
public override uint GetRegisterAddressLen () => 1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString(bool additional) {
|
||||
|
||||
if (!additional) return this.ToString();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine($"PLC Naming: {GetMewName()}");
|
||||
sb.AppendLine($"Name: {Name ?? "Not named"}");
|
||||
sb.AppendLine($"Value: {GetValueString()}");
|
||||
sb.AppendLine($"Register Type: {RegisterType}");
|
||||
sb.AppendLine($"Memory Address: {MemoryAddress}");
|
||||
sb.AppendLine($"Special Address: {SpecialAddress:X1}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace MewtocolNet.Registers {
|
||||
/// </summary>
|
||||
public uint AddressLength => addressLength;
|
||||
|
||||
internal uint ReservedBytesSize { get; private set; }
|
||||
internal uint ReservedBytesSize { get; set; }
|
||||
|
||||
internal ushort? ReservedBitSize { get; private set; }
|
||||
internal ushort? ReservedBitSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines a register containing bytes
|
||||
@@ -128,15 +128,22 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
if (!attachedInterface.IsConnected) return null;
|
||||
|
||||
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
||||
if (read == null) return null;
|
||||
var res = await underlyingMemory.ReadRegisterAsync(this);
|
||||
if (!res) return null;
|
||||
|
||||
var bytes = underlyingMemory.GetUnderlyingBytes(this);
|
||||
|
||||
return SetValueFromBytes(bytes);
|
||||
|
||||
}
|
||||
|
||||
internal override object SetValueFromBytes(byte[] bytes) {
|
||||
|
||||
object parsed;
|
||||
|
||||
if(ReservedBitSize != null) {
|
||||
parsed = PlcValueParser.Parse<BitArray>(this, read);
|
||||
if (ReservedBitSize != null) {
|
||||
parsed = PlcValueParser.Parse<BitArray>(this, bytes);
|
||||
} else {
|
||||
parsed = PlcValueParser.Parse<byte[]>(this, read);
|
||||
parsed = PlcValueParser.Parse<byte[]>(this, bytes);
|
||||
}
|
||||
|
||||
SetValueFromPLC(parsed);
|
||||
@@ -157,8 +164,10 @@ namespace MewtocolNet.Registers {
|
||||
encoded = PlcValueParser.Encode(this, (byte[])data);
|
||||
}
|
||||
|
||||
var res = await attachedInterface.WriteRawRegisterAsync(this, encoded);
|
||||
if (res) SetValueFromPLC(data);
|
||||
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
|
||||
if (res) {
|
||||
SetValueFromPLC(data);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -19,9 +20,9 @@ namespace MewtocolNet {
|
||||
|
||||
uint MemoryAddress { get; }
|
||||
|
||||
// setters
|
||||
RegisterCollection ContainedCollection { get; }
|
||||
|
||||
void WithCollectionType(Type colType);
|
||||
// setters
|
||||
|
||||
void SetValueFromPLC(object value);
|
||||
|
||||
@@ -29,8 +30,6 @@ namespace MewtocolNet {
|
||||
|
||||
// Accessors
|
||||
|
||||
Type GetCollectionType();
|
||||
|
||||
string GetRegisterString();
|
||||
|
||||
string GetCombinedName();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -131,11 +133,18 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
if (!attachedInterface.IsConnected) return null;
|
||||
|
||||
var read = await attachedInterface.ReadRawRegisterAsync(this);
|
||||
if (read == null) return null;
|
||||
var res = await underlyingMemory.ReadRegisterAsync(this);
|
||||
if (!res) return null;
|
||||
|
||||
var parsed = PlcValueParser.Parse<T>(this, read);
|
||||
var bytes = underlyingMemory.GetUnderlyingBytes(this);
|
||||
|
||||
return SetValueFromBytes(bytes);
|
||||
|
||||
}
|
||||
|
||||
internal override object SetValueFromBytes(byte[] bytes) {
|
||||
|
||||
var parsed = PlcValueParser.Parse<T>(this, bytes);
|
||||
SetValueFromPLC(parsed);
|
||||
return parsed;
|
||||
|
||||
@@ -146,8 +155,13 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
if (!attachedInterface.IsConnected) return false;
|
||||
|
||||
var res = await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (T)data));
|
||||
if (res) SetValueFromPLC(data);
|
||||
var encoded = PlcValueParser.Encode(this, (T)data);
|
||||
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
|
||||
|
||||
if (res) {
|
||||
SetValueFromPLC(data);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
@@ -130,7 +130,8 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
var res = await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (string)data));
|
||||
var encoded = PlcValueParser.Encode(this, (string)data);
|
||||
var res = await attachedInterface.WriteRawRegisterAsync(this, encoded);
|
||||
|
||||
if (res) {
|
||||
SetValueFromPLC(data);
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MewtocolNet.Helpers;
|
||||
|
||||
namespace MewtocolNet.TypeConversion {
|
||||
|
||||
@@ -134,9 +135,9 @@ namespace MewtocolNet.TypeConversion {
|
||||
PlcVarType = PlcVarType.REAL,
|
||||
FromRaw = (reg, bytes) => {
|
||||
|
||||
var val = BitConverter.ToUInt32(bytes, 0);
|
||||
byte[] floatVals = BitConverter.GetBytes(val);
|
||||
float finalFloat = BitConverter.ToSingle(floatVals, 0);
|
||||
//bytes = new byte[] { 0xCD, 0xCC, 0x8C, 0x40 };
|
||||
|
||||
float finalFloat = BitConverter.ToSingle(bytes, 0);
|
||||
|
||||
return finalFloat;
|
||||
|
||||
|
||||
152
MewtocolNet/UnderlyingRegisters/DTArea.cs
Normal file
152
MewtocolNet/UnderlyingRegisters/DTArea.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
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 {
|
||||
public class DTArea : IMemoryArea {
|
||||
|
||||
private MewtocolInterface mewInterface;
|
||||
|
||||
internal RegisterType registerType;
|
||||
internal ulong addressStart;
|
||||
internal ulong addressEnd;
|
||||
|
||||
internal byte[] underlyingBytes = new byte[2];
|
||||
|
||||
internal List<BaseRegister> linkedRegisters = new List<BaseRegister>();
|
||||
|
||||
public ulong AddressStart => addressStart;
|
||||
public ulong AddressEnd => addressEnd;
|
||||
|
||||
internal DTArea (MewtocolInterface mewIf) {
|
||||
|
||||
mewInterface = mewIf;
|
||||
|
||||
}
|
||||
|
||||
internal void BoundaryUdpdate (uint? addrFrom = null, uint? addrTo = null) {
|
||||
|
||||
var addFrom = addrFrom ?? addressStart;
|
||||
var addTo = addrTo ?? addressEnd;
|
||||
|
||||
var oldFrom = addressStart;
|
||||
var oldUnderlying = underlyingBytes.ToArray();
|
||||
|
||||
underlyingBytes = new byte[(addTo + 1 - addFrom) * 2];
|
||||
|
||||
//copy old bytes to new array
|
||||
var offset = (int)(oldFrom - addFrom) * 2;
|
||||
oldUnderlying.CopyTo(oldUnderlying, offset);
|
||||
|
||||
addressStart = addFrom;
|
||||
addressEnd = addTo;
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> ReadRegisterAsync (BaseRegister reg) {
|
||||
|
||||
return await RequestByteReadAsync(reg.MemoryAddress, reg.MemoryAddress + reg.GetRegisterAddressLen() - 1);
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> WriteRegisterAsync (BaseRegister reg, byte[] bytes) {
|
||||
|
||||
return await RequestByteWriteAsync(reg.MemoryAddress, bytes);
|
||||
|
||||
}
|
||||
|
||||
internal async Task<bool> RequestByteReadAsync (ulong addStart, ulong addEnd) {
|
||||
|
||||
var station = mewInterface.GetStationNumber();
|
||||
|
||||
string requeststring = $"%{station}#RD{GetMewtocolIdent(addStart, addEnd)}";
|
||||
var result = await mewInterface.SendCommandAsync(requeststring);
|
||||
|
||||
if(result.Success) {
|
||||
|
||||
var resBytes = result.Response.ParseDTRawStringAsBytes();
|
||||
SetUnderlyingBytes(resBytes, addStart);
|
||||
|
||||
}
|
||||
|
||||
return result.Success;
|
||||
|
||||
}
|
||||
|
||||
internal async Task<bool> RequestByteWriteAsync(ulong addStart, byte[] bytes) {
|
||||
|
||||
var station = mewInterface.GetStationNumber();
|
||||
var addEnd = addStart + ((ulong)bytes.Length / 2) - 1;
|
||||
|
||||
string requeststring = $"%{station}#WD{GetMewtocolIdent(addStart, addEnd)}{bytes.ToHexString()}";
|
||||
var result = await mewInterface.SendCommandAsync(requeststring);
|
||||
|
||||
if (result.Success) {
|
||||
|
||||
SetUnderlyingBytes(bytes, addStart);
|
||||
|
||||
}
|
||||
|
||||
return result.Success;
|
||||
|
||||
}
|
||||
|
||||
public byte[] GetUnderlyingBytes(BaseRegister reg) {
|
||||
|
||||
int byteLen = (int)(reg.GetRegisterAddressLen() * 2);
|
||||
|
||||
return GetUnderlyingBytes(reg.MemoryAddress, byteLen);
|
||||
|
||||
}
|
||||
|
||||
internal byte[] GetUnderlyingBytes (uint addStart, int addLen) {
|
||||
|
||||
int byteLen = (int)(addLen * 2);
|
||||
|
||||
int copyOffset = (int)((addStart - addressStart) * 2);
|
||||
var gotBytes = underlyingBytes.Skip(copyOffset).Take(byteLen).ToArray();
|
||||
|
||||
return gotBytes;
|
||||
|
||||
}
|
||||
|
||||
public void SetUnderlyingBytes(BaseRegister reg, byte[] bytes) {
|
||||
|
||||
SetUnderlyingBytes(bytes, reg.MemoryAddress);
|
||||
|
||||
}
|
||||
|
||||
private void SetUnderlyingBytes(byte[] bytes, ulong addStart) {
|
||||
|
||||
int copyOffset = (int)((addStart - addressStart) * 2);
|
||||
bytes.CopyTo(underlyingBytes, copyOffset);
|
||||
|
||||
}
|
||||
|
||||
private string GetMewtocolIdent () {
|
||||
|
||||
StringBuilder asciistring = new StringBuilder("D");
|
||||
asciistring.Append(AddressStart.ToString().PadLeft(5, '0'));
|
||||
asciistring.Append(AddressEnd.ToString().PadLeft(5, '0'));
|
||||
return asciistring.ToString();
|
||||
|
||||
}
|
||||
|
||||
private string GetMewtocolIdent(ulong addStart, ulong addEnd) {
|
||||
|
||||
StringBuilder asciistring = new StringBuilder("D");
|
||||
asciistring.Append(addStart.ToString().PadLeft(5, '0'));
|
||||
asciistring.Append(addEnd.ToString().PadLeft(5, '0'));
|
||||
return asciistring.ToString();
|
||||
|
||||
}
|
||||
|
||||
public override string ToString() => $"DT{AddressStart}-{AddressEnd}";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
16
MewtocolNet/UnderlyingRegisters/IMemoryArea.cs
Normal file
16
MewtocolNet/UnderlyingRegisters/IMemoryArea.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
internal interface IMemoryArea {
|
||||
|
||||
byte[] GetUnderlyingBytes(BaseRegister reg);
|
||||
|
||||
Task<bool> ReadRegisterAsync(BaseRegister reg);
|
||||
|
||||
Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
318
MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs
Normal file
318
MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
internal class MemoryAreaManager {
|
||||
|
||||
internal int maxOptimizationDistance = 8;
|
||||
internal int maxRegistersPerGroup = -1;
|
||||
|
||||
internal MewtocolInterface mewInterface;
|
||||
|
||||
// WR areas are n of words, each word has 2 bytes representing the "special address component"
|
||||
|
||||
//X WR
|
||||
internal List<WRArea> externalRelayInAreas;
|
||||
|
||||
//Y WR
|
||||
internal List<WRArea> externalRelayOutAreas;
|
||||
|
||||
//R WR
|
||||
internal List<WRArea> internalRelayAreas;
|
||||
|
||||
//DT
|
||||
internal List<DTArea> dataAreas;
|
||||
|
||||
internal MemoryAreaManager (MewtocolInterface mewIf, int wrSize = 512, int dtSize = 32_765) {
|
||||
|
||||
mewInterface = mewIf;
|
||||
Setup(wrSize, dtSize);
|
||||
|
||||
}
|
||||
|
||||
// Later on pass memory area sizes here
|
||||
internal void Setup (int wrSize, int dtSize) {
|
||||
|
||||
externalRelayInAreas = new List<WRArea>(wrSize * 16);
|
||||
externalRelayOutAreas = new List<WRArea>(wrSize * 16);
|
||||
internalRelayAreas = new List<WRArea>(wrSize * 16);
|
||||
dataAreas = new List<DTArea>(dtSize);
|
||||
|
||||
}
|
||||
|
||||
internal bool LinkRegister (BaseRegister reg) {
|
||||
|
||||
switch (reg.RegisterType) {
|
||||
case RegisterType.X:
|
||||
return AddWRArea(reg, externalRelayInAreas);
|
||||
case RegisterType.Y:
|
||||
return AddWRArea(reg, externalRelayOutAreas);
|
||||
case RegisterType.R:
|
||||
return AddWRArea(reg, internalRelayAreas);
|
||||
case RegisterType.DT:
|
||||
case RegisterType.DDT:
|
||||
case RegisterType.DT_BYTE_RANGE:
|
||||
return AddDTArea(reg);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private bool AddWRArea (BaseRegister insertReg, List<WRArea> collection) {
|
||||
|
||||
WRArea area = collection.FirstOrDefault(x => x.AddressStart == insertReg.MemoryAddress);
|
||||
|
||||
if(area != null) {
|
||||
|
||||
var existingLinkedRegister = area.linkedRegisters
|
||||
.FirstOrDefault(x => x.CompareIsDuplicate(insertReg));
|
||||
|
||||
if(existingLinkedRegister != null) {
|
||||
|
||||
foreach (var prop in insertReg.boundToProps)
|
||||
existingLinkedRegister.WithBoundProperty(prop);
|
||||
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
insertReg.underlyingMemory = area;
|
||||
area.linkedRegisters.Add(insertReg);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
area = new WRArea(mewInterface) {
|
||||
registerType = insertReg.RegisterType,
|
||||
addressStart = insertReg.MemoryAddress,
|
||||
};
|
||||
|
||||
insertReg.underlyingMemory = area;
|
||||
area.linkedRegisters.Add(insertReg);
|
||||
|
||||
collection.Add(area);
|
||||
collection = collection.OrderBy(x => x.AddressStart).ToList();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool AddDTArea (BaseRegister insertReg) {
|
||||
|
||||
uint regInsAddStart = insertReg.MemoryAddress;
|
||||
uint regInsAddEnd = insertReg.MemoryAddress + insertReg.GetRegisterAddressLen() - 1;
|
||||
|
||||
DTArea targetArea = null;
|
||||
|
||||
foreach (var dtArea in dataAreas) {
|
||||
|
||||
bool matchingAddress = regInsAddStart >= dtArea.AddressStart &&
|
||||
regInsAddEnd <= dtArea.addressEnd;
|
||||
|
||||
//found matching
|
||||
if (matchingAddress) {
|
||||
|
||||
//check if the area has registers linked that are overlapping (not matching)
|
||||
var foundDupe = dtArea.linkedRegisters.FirstOrDefault(x => x.CompareIsDuplicateNonCast(insertReg));
|
||||
|
||||
if(foundDupe != null) {
|
||||
throw new NotSupportedException(
|
||||
message: $"Can't have registers of different types at the same referenced plc address: " +
|
||||
$"{insertReg.PLCAddressName} ({insertReg.GetType()}) <=> " +
|
||||
$"{foundDupe.PLCAddressName} ({foundDupe.GetType()})"
|
||||
);
|
||||
}
|
||||
|
||||
targetArea = dtArea;
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
//found adjacent before
|
||||
if(dtArea.AddressEnd <= regInsAddStart) {
|
||||
|
||||
ulong distance = regInsAddStart - dtArea.AddressEnd;
|
||||
|
||||
if (distance <= (uint)maxOptimizationDistance) {
|
||||
|
||||
//expand the boundaries for the area to include the new adjacent area
|
||||
dtArea.BoundaryUdpdate(addrTo: regInsAddEnd);
|
||||
targetArea = dtArea;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//found adjacent after
|
||||
if (dtArea.AddressStart >= regInsAddEnd) {
|
||||
|
||||
ulong distance = dtArea.AddressStart - regInsAddEnd;
|
||||
|
||||
if (distance <= (uint)maxOptimizationDistance) {
|
||||
|
||||
//expand the boundaries for the area to include the new adjacent area
|
||||
dtArea.BoundaryUdpdate(addrFrom: regInsAddStart);
|
||||
|
||||
targetArea = dtArea;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (targetArea == null) {
|
||||
|
||||
targetArea = new DTArea(mewInterface) {
|
||||
addressStart = regInsAddStart,
|
||||
addressEnd = regInsAddEnd,
|
||||
registerType = insertReg.RegisterType,
|
||||
};
|
||||
|
||||
targetArea.BoundaryUdpdate();
|
||||
|
||||
dataAreas.Add(targetArea);
|
||||
|
||||
}
|
||||
|
||||
insertReg.underlyingMemory = targetArea;
|
||||
|
||||
var existingLinkedRegister = targetArea.linkedRegisters
|
||||
.FirstOrDefault(x => x.CompareIsDuplicate(insertReg));
|
||||
|
||||
if (existingLinkedRegister != null) {
|
||||
|
||||
foreach (var prop in insertReg.boundToProps)
|
||||
existingLinkedRegister.WithBoundProperty(prop);
|
||||
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
targetArea.linkedRegisters.Add(insertReg);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal async Task PollAllAreasAsync () {
|
||||
|
||||
foreach (var dtArea in dataAreas) {
|
||||
|
||||
//set the whole memory area at once
|
||||
var res = await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
|
||||
|
||||
foreach (var register in dtArea.linkedRegisters) {
|
||||
|
||||
var regStart = register.MemoryAddress;
|
||||
var addLen = (int)register.GetRegisterAddressLen();
|
||||
|
||||
var bytes = dtArea.GetUnderlyingBytes(regStart, addLen);
|
||||
register.SetValueFromBytes(bytes);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal void Merge () {
|
||||
|
||||
//merge gaps that the algorithm didn't catch be rerunning the register attachment
|
||||
|
||||
var allDataAreaRegisters = dataAreas.SelectMany(x => x.linkedRegisters).ToList();
|
||||
dataAreas = new List<DTArea>(allDataAreaRegisters.Capacity);
|
||||
|
||||
foreach (var reg in allDataAreaRegisters)
|
||||
AddDTArea(reg);
|
||||
|
||||
}
|
||||
|
||||
internal string ExplainLayout () {
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("---- DT Area ----");
|
||||
|
||||
sb.AppendLine($"Optimization distance: {maxOptimizationDistance}");
|
||||
|
||||
foreach (var area in dataAreas) {
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"=> {area} = {area.underlyingBytes.Length} bytes");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(string.Join("\n", area.underlyingBytes.ToHexString(" ").SplitInParts(3 * 8)));
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sb.AppendLine("---- WR X Area ----");
|
||||
|
||||
foreach (var area in externalRelayInAreas) {
|
||||
|
||||
sb.AppendLine(area.ToString());
|
||||
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sb.AppendLine("---- WR Y Area ----");
|
||||
|
||||
foreach (var area in externalRelayOutAreas) {
|
||||
|
||||
sb.AppendLine(area.ToString());
|
||||
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sb.AppendLine("---- WR R Area ----");
|
||||
|
||||
foreach (var area in internalRelayAreas) {
|
||||
|
||||
sb.AppendLine(area.ToString());
|
||||
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
83
MewtocolNet/UnderlyingRegisters/WRArea.cs
Normal file
83
MewtocolNet/UnderlyingRegisters/WRArea.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
public class WRArea : IMemoryArea {
|
||||
|
||||
private MewtocolInterface mewInterface;
|
||||
|
||||
internal RegisterType registerType;
|
||||
internal ulong addressStart;
|
||||
|
||||
internal byte[] wordData = new byte[2];
|
||||
|
||||
internal List<BaseRegister> linkedRegisters = new List<BaseRegister>();
|
||||
|
||||
public ulong AddressStart => addressStart;
|
||||
|
||||
internal WRArea(MewtocolInterface mewIf) {
|
||||
|
||||
mewInterface = mewIf;
|
||||
|
||||
}
|
||||
|
||||
public byte[] GetUnderlyingBytes(BaseRegister reg) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> ReadRegisterAsync(BaseRegister reg) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public string GetMewtocolIdent() => GetMewtocolIdentsAllBits();
|
||||
|
||||
public string GetMewtocolIdentsAllBits () {
|
||||
|
||||
StringBuilder asciistring = new StringBuilder();
|
||||
|
||||
for (byte i = 0; i < 16; i++) {
|
||||
|
||||
asciistring.Append(GetMewtocolIdentSingleBit(i));
|
||||
|
||||
}
|
||||
|
||||
return asciistring.ToString();
|
||||
|
||||
}
|
||||
|
||||
public string GetMewtocolIdentSingleBit (byte specialAddress) {
|
||||
|
||||
//(R|X|Y)(area add [3] + special add [1])
|
||||
StringBuilder asciistring = new StringBuilder();
|
||||
|
||||
string prefix = registerType.ToString();
|
||||
string mem = AddressStart.ToString();
|
||||
string sp = specialAddress.ToString("X1");
|
||||
|
||||
asciistring.Append(prefix);
|
||||
asciistring.Append(mem.PadLeft(3, '0'));
|
||||
asciistring.Append(sp);
|
||||
|
||||
return asciistring.ToString();
|
||||
|
||||
}
|
||||
|
||||
public override string ToString() => $"{registerType}{AddressStart} 0-F";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,10 +14,10 @@ namespace MewtocolTests {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
private void Test(IRegisterInternal reg, string propName, uint expectAddr, string expectPlcName) {
|
||||
private void Test(IRegisterInternal reg, uint expectAddr, string expectPlcName) {
|
||||
|
||||
Assert.NotNull(reg);
|
||||
Assert.Equal(propName, reg.Name);
|
||||
Assert.StartsWith("auto_prop_register_", reg.Name);
|
||||
Assert.Null(reg.Value);
|
||||
|
||||
Assert.Equal(expectAddr, reg.MemoryAddress);
|
||||
@@ -32,83 +32,103 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "Boolean generation")]
|
||||
public void BooleanGen() {
|
||||
|
||||
var interf = Mewtocol.Ethernet("192.168.0.1");
|
||||
interf.AddRegisterCollection(new TestBoolRegisters());
|
||||
var interf = Mewtocol.Ethernet("192.168.0.1")
|
||||
.WithRegisterCollections(x =>
|
||||
x.AddCollection(new TestBoolRegisters())
|
||||
).Build();
|
||||
|
||||
var register1 = interf.GetRegister(nameof(TestBoolRegisters.RType));
|
||||
var register2 = interf.GetRegister(nameof(TestBoolRegisters.XType));
|
||||
output.WriteLine(((MewtocolInterface)interf).memoryManager.ExplainLayout());
|
||||
|
||||
var register3 = interf.GetRegister(nameof(TestBoolRegisters.RType_MewString));
|
||||
var register1 = interf.GetRegister("auto_prop_register_1");
|
||||
var register2 = interf.GetRegister("auto_prop_register_2");
|
||||
var register3 = interf.GetRegister("auto_prop_register_3");
|
||||
|
||||
Test((IRegisterInternal)register1, nameof(TestBoolRegisters.RType), 85, "R85A");
|
||||
Test((IRegisterInternal)register2, nameof(TestBoolRegisters.XType), 0, "XD");
|
||||
|
||||
Test((IRegisterInternal)register3, nameof(TestBoolRegisters.RType_MewString), 85, "R85B");
|
||||
Test((IRegisterInternal)register1, 0, "XD");
|
||||
Test((IRegisterInternal)register2, 85, "R85A");
|
||||
Test((IRegisterInternal)register3, 85, "R85B");
|
||||
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Number 16 bit generation")]
|
||||
public void N16BitGen () {
|
||||
|
||||
var interf = Mewtocol.Ethernet("192.168.0.1");
|
||||
interf.AddRegisterCollection(new Nums16Bit());
|
||||
var interf = Mewtocol.Ethernet("192.168.0.1")
|
||||
.WithRegisterCollections(x =>
|
||||
x.AddCollection(new Nums16Bit())
|
||||
).Build();
|
||||
|
||||
var register1 = interf.GetRegister(nameof(Nums16Bit.Int16Type));
|
||||
var register2 = interf.GetRegister(nameof(Nums16Bit.UInt16Type));
|
||||
var register3 = interf.GetRegister(nameof(Nums16Bit.Enum16Type));
|
||||
|
||||
var register4 = interf.GetRegister(nameof(Nums16Bit.Int16Type_MewString));
|
||||
var register5 = interf.GetRegister(nameof(Nums16Bit.Enum16Type_MewString));
|
||||
var register1 = interf.GetRegister("auto_prop_register_1");
|
||||
var register2 = interf.GetRegister("auto_prop_register_2");
|
||||
var register3 = interf.GetRegister("auto_prop_register_3");
|
||||
|
||||
//test generic properties
|
||||
Test((IRegisterInternal)register1, nameof(Nums16Bit.Int16Type), 899, "DT899");
|
||||
Test((IRegisterInternal)register2, nameof(Nums16Bit.UInt16Type), 342, "DT342");
|
||||
Test((IRegisterInternal)register3, nameof(Nums16Bit.Enum16Type), 50, "DT50");
|
||||
|
||||
Test((IRegisterInternal)register4, nameof(Nums16Bit.Int16Type_MewString), 900, "DT900");
|
||||
Test((IRegisterInternal)register5, nameof(Nums16Bit.Enum16Type_MewString), 51, "DT51");
|
||||
Test((IRegisterInternal)register1, 50, "DT50");
|
||||
Test((IRegisterInternal)register2, 342, "DT342");
|
||||
Test((IRegisterInternal)register3, 899, "DT899");
|
||||
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Number 32 bit generation")]
|
||||
public void N32BitGen () {
|
||||
|
||||
var interf = Mewtocol.Ethernet("192.168.0.1");
|
||||
interf.AddRegisterCollection(new Nums32Bit());
|
||||
var interf = Mewtocol.Ethernet("192.168.0.1")
|
||||
.WithRegisterCollections(x => x
|
||||
.AddCollection(new Nums32Bit())
|
||||
).Build();
|
||||
|
||||
var register1 = interf.GetRegister(nameof(Nums32Bit.Int32Type));
|
||||
var register2 = interf.GetRegister(nameof(Nums32Bit.UInt32Type));
|
||||
var register3 = interf.GetRegister(nameof(Nums32Bit.Enum32Type));
|
||||
var register4 = interf.GetRegister(nameof(Nums32Bit.FloatType));
|
||||
var register5 = interf.GetRegister(nameof(Nums32Bit.TimeSpanType));
|
||||
output.WriteLine(((MewtocolInterface)interf).memoryManager.ExplainLayout());
|
||||
|
||||
var register6 = interf.GetRegister(nameof(Nums32Bit.Enum32Type_MewString));
|
||||
var register7 = interf.GetRegister(nameof(Nums32Bit.TimeSpanType_MewString));
|
||||
var register1 = interf.GetRegister("auto_prop_register_1");
|
||||
var register2 = interf.GetRegister("auto_prop_register_2");
|
||||
var register3 = interf.GetRegister("auto_prop_register_3");
|
||||
|
||||
//only one generated because same type
|
||||
var register4 = interf.GetRegister("auto_prop_register_4");
|
||||
|
||||
var register6 = interf.GetRegister("auto_prop_register_5");
|
||||
var register7 = interf.GetRegister("auto_prop_register_6");
|
||||
|
||||
//test generic properties
|
||||
Test((IRegisterInternal)register1, nameof(Nums32Bit.Int32Type), 7001, "DDT7001");
|
||||
Test((IRegisterInternal)register2, nameof(Nums32Bit.UInt32Type), 765, "DDT765");
|
||||
Test((IRegisterInternal)register3, nameof(Nums32Bit.Enum32Type), 51, "DDT51");
|
||||
Test((IRegisterInternal)register4, nameof(Nums32Bit.FloatType), 7003, "DDT7003");
|
||||
Test((IRegisterInternal)register5, nameof(Nums32Bit.TimeSpanType), 7012, "DDT7012");
|
||||
Test((IRegisterInternal)register1, 7000, "DDT7000");
|
||||
Test((IRegisterInternal)register2, 7002, "DDT7002");
|
||||
Test((IRegisterInternal)register3, 7004, "DDT7004");
|
||||
|
||||
Test((IRegisterInternal)register4, 7006, "DDT7006");
|
||||
|
||||
Test((IRegisterInternal)register6, nameof(Nums32Bit.Enum32Type_MewString), 53, "DDT53");
|
||||
Test((IRegisterInternal)register7, nameof(Nums32Bit.TimeSpanType_MewString), 7014, "DDT7014");
|
||||
Test((IRegisterInternal)register6, 7008, "DDT7008");
|
||||
Test((IRegisterInternal)register7, 7010, "DDT7010");
|
||||
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "String generation")]
|
||||
public void StringGen() {
|
||||
|
||||
var interf = Mewtocol.Ethernet("192.168.0.1");
|
||||
interf.AddRegisterCollection(new TestStringRegisters());
|
||||
var interf = Mewtocol.Ethernet("192.168.0.1")
|
||||
.WithRegisterCollections(x =>
|
||||
x.AddCollection(new TestStringRegisters())
|
||||
).Build();
|
||||
|
||||
var register1 = interf.GetRegister(nameof(TestStringRegisters.StringType));
|
||||
var register2 = interf.GetRegister(nameof(TestStringRegisters.StringType_MewString));
|
||||
var register1 = interf.GetRegister("auto_prop_register_1");
|
||||
|
||||
//test generic properties
|
||||
Test((IRegisterInternal)register1, nameof(TestStringRegisters.StringType), 7005, "DT7005");
|
||||
Test((IRegisterInternal)register2, nameof(TestStringRegisters.StringType_MewString), 7050, "DT7050");
|
||||
Test((IRegisterInternal)register1, 7005, "DT7005");
|
||||
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Byte Array generation")]
|
||||
public void ByteArrGen() {
|
||||
|
||||
var interf = Mewtocol.Ethernet("192.168.0.1")
|
||||
.WithRegisterCollections(x =>
|
||||
x.AddCollection(new TestBitwiseRegisters())
|
||||
).Build();
|
||||
|
||||
var register1 = interf.GetRegister("auto_prop_register_1");
|
||||
//var register2 = interf.GetRegister("auto_prop_register_2");
|
||||
|
||||
//test generic properties
|
||||
Test((IRegisterInternal)register1, 7000, "DT7000");
|
||||
//Test((IRegisterInternal)register2, 7001, "DT7001");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@ namespace MewtocolTests.EncapsulatedTests {
|
||||
|
||||
public class TestBoolRegisters : RegisterCollection {
|
||||
|
||||
[Register(IOType.R, memoryArea: 85, spAdress: 0xA)]
|
||||
[Register("R85A")]
|
||||
public bool RType { get; set; }
|
||||
|
||||
[Register(IOType.X, (byte)0xD)]
|
||||
[Register("XD")]
|
||||
public bool XType { get; set; }
|
||||
|
||||
[Register("R85B")]
|
||||
@@ -44,71 +44,62 @@ namespace MewtocolTests.EncapsulatedTests {
|
||||
public class Nums16Bit : RegisterCollection {
|
||||
|
||||
|
||||
[Register(899)]
|
||||
[Register("DT899")]
|
||||
public short Int16Type { get; set; }
|
||||
|
||||
[Register(342)]
|
||||
[Register("DT342")]
|
||||
public ushort UInt16Type { get; set; }
|
||||
|
||||
[Register(50)]
|
||||
[Register("DT50")]
|
||||
public CurrentState Enum16Type { get; set; }
|
||||
|
||||
[Register("DT900")]
|
||||
public short Int16Type_MewString { get; set; }
|
||||
|
||||
[Register("DT51")]
|
||||
public CurrentState Enum16Type_MewString { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class Nums32Bit : RegisterCollection {
|
||||
|
||||
[Register(7001)]
|
||||
[Register("DDT7000")]
|
||||
public int Int32Type { get; set; }
|
||||
|
||||
[Register(765)]
|
||||
[Register("DDT7002")]
|
||||
public uint UInt32Type { get; set; }
|
||||
|
||||
[Register(51)]
|
||||
[Register("DDT7004")]
|
||||
public CurrentState32 Enum32Type { get; set; }
|
||||
|
||||
[Register(7003)]
|
||||
[Register("DDT7006")]
|
||||
public float FloatType { get; set; }
|
||||
|
||||
[Register(7012)]
|
||||
[Register("DDT7006")]
|
||||
public float FloatType2 { get; set; } // this is legal, because the cast type is the same
|
||||
|
||||
//[Register("DDT7006")]
|
||||
//public int FloatType3 { get; set; } // this is not legal
|
||||
|
||||
[Register("DDT7010")]
|
||||
public TimeSpan TimeSpanType { get; set; }
|
||||
|
||||
[Register("DDT53")]
|
||||
public CurrentState32 Enum32Type_MewString { get; set; }
|
||||
[Register("DDT7008")]
|
||||
public TimeSpan TimeSpanType2 { get; set; }
|
||||
|
||||
[Register("DDT7014")]
|
||||
public TimeSpan TimeSpanType_MewString { get; set; }
|
||||
[Register("DDT7013")]
|
||||
public TimeSpan TimeSpanType3 { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class TestStringRegisters : RegisterCollection {
|
||||
|
||||
[Register(7005, 5)]
|
||||
[Register("DT7005")]
|
||||
public string? StringType { get; set; }
|
||||
|
||||
[Register("DT7050")]
|
||||
public string? StringType_MewString { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class TestBitwiseRegisters : RegisterCollection {
|
||||
|
||||
[Register(7010)]
|
||||
public BitArray TestBitRegister { get; set; }
|
||||
[Register("DT7000")]
|
||||
public BitArray BitArr16 { get; set; }
|
||||
|
||||
[Register(8010, BitCount.B32)]
|
||||
public BitArray TestBitRegister32 { get; set; }
|
||||
|
||||
[Register(1204, BitCount.B16, 9)]
|
||||
public bool BitValue { get; set; }
|
||||
|
||||
[Register(1204, BitCount.B32, 5)]
|
||||
public bool FillTest { get; set; }
|
||||
//[Register("DT7001")]
|
||||
//public BitArray BitArr32 { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace MewtocolTests
|
||||
|
||||
output.WriteLine($"Testing: {plc.PLCName}");
|
||||
|
||||
var cycleClient = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort);
|
||||
var cycleClient = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort).Build();
|
||||
|
||||
await cycleClient.ConnectAsync();
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace MewtocolTests
|
||||
|
||||
output.WriteLine($"Testing: {plc.PLCName}\n");
|
||||
|
||||
var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort);
|
||||
var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort).Build();
|
||||
|
||||
await client.ConnectAsync();
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace MewtocolTests
|
||||
|
||||
output.WriteLine($"\n\n --- Testing: {plc.PLCName} ---\n");
|
||||
|
||||
var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort);
|
||||
var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort).Build();
|
||||
|
||||
foreach (var testRW in testRegisterRW) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user