mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Change register list source
- add poller levels - add new register builder pattern
This commit is contained in:
@@ -17,6 +17,12 @@ namespace MewtocolNet.Exceptions {
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
||||
|
||||
internal static MewtocolException NotConnectedSend () {
|
||||
|
||||
return new MewtocolException($"Can not send a message to the PLC if it isn't connected");
|
||||
|
||||
}
|
||||
|
||||
internal static MewtocolException DupeRegister (IRegisterInternal register) {
|
||||
|
||||
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}");
|
||||
@@ -31,7 +37,7 @@ namespace MewtocolNet.Exceptions {
|
||||
|
||||
internal static MewtocolException OverlappingRegister (IRegisterInternal registerA, IRegisterInternal registerB) {
|
||||
|
||||
throw new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " +
|
||||
return new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " +
|
||||
$"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}");
|
||||
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace MewtocolNet {
|
||||
if (_onString == null)
|
||||
return null;
|
||||
|
||||
var res = new Regex(@"\%([0-9]{2})\$RD(.{" + _blockSize + "})").Match(_onString);
|
||||
var res = new Regex(@"\%([0-9a-fA-F]{2})\$RD(.{" + _blockSize + "})").Match(_onString);
|
||||
if (res.Success) {
|
||||
string val = res.Groups[2].Value;
|
||||
return val;
|
||||
@@ -75,7 +75,7 @@ namespace MewtocolNet {
|
||||
|
||||
_onString = _onString.Replace("\r", "");
|
||||
|
||||
var res = new Regex(@"\%([0-9]{2})\$RC(.)").Match(_onString);
|
||||
var res = new Regex(@"\%([0-9a-fA-F]{2})\$RC(.)").Match(_onString);
|
||||
if (res.Success) {
|
||||
string val = res.Groups[2].Value;
|
||||
return val == "1";
|
||||
@@ -91,7 +91,7 @@ namespace MewtocolNet {
|
||||
|
||||
_onString = _onString.Replace("\r", "");
|
||||
|
||||
var res = new Regex(@"\%([0-9]{2})\$RC(?<bits>(?:0|1){0,8})(..)").Match(_onString);
|
||||
var res = new Regex(@"\%([0-9a-fA-F]{2})\$RC(?<bits>(?:0|1){0,8})(..)").Match(_onString);
|
||||
if (res.Success) {
|
||||
|
||||
string val = res.Groups["bits"].Value;
|
||||
@@ -121,7 +121,7 @@ namespace MewtocolNet {
|
||||
|
||||
_onString = _onString.Replace("\r", "");
|
||||
|
||||
var res = new Regex(@"\%([0-9]{2})\$RD(?<data>.*)(?<csum>..)").Match(_onString);
|
||||
var res = new Regex(@"\%([0-9a-fA-F]{2})\$RD(?<data>.*)(?<csum>..)").Match(_onString);
|
||||
if (res.Success) {
|
||||
|
||||
string val = res.Groups["data"].Value;
|
||||
@@ -245,16 +245,20 @@ namespace MewtocolNet {
|
||||
|
||||
bool valCompare = reg1.RegisterType == compare.RegisterType &&
|
||||
reg1.MemoryAddress == compare.MemoryAddress &&
|
||||
reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
|
||||
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
|
||||
|
||||
return valCompare;
|
||||
|
||||
}
|
||||
|
||||
internal static bool CompareIsDuplicateNonCast (this BaseRegister reg1, BaseRegister compare) {
|
||||
internal static bool CompareIsDuplicateNonCast (this BaseRegister reg1, BaseRegister compare, bool ingnoreByteRegisters = true) {
|
||||
|
||||
if (ingnoreByteRegisters && (compare.GetType() == typeof(BytesRegister) || reg1.GetType() == typeof(BytesRegister))) return false;
|
||||
|
||||
bool valCompare = reg1.GetType() != compare.GetType() &&
|
||||
reg1.MemoryAddress == compare.MemoryAddress &&
|
||||
reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
|
||||
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
|
||||
|
||||
return valCompare;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
@@ -8,7 +10,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// Provides a interface for Panasonic PLCs
|
||||
/// </summary>
|
||||
public interface IPlc : IDisposable {
|
||||
public interface IPlc : IDisposable, INotifyPropertyChanged {
|
||||
|
||||
/// <summary>
|
||||
/// The current connection state of the interface
|
||||
@@ -35,11 +37,6 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
int QueuedMessages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The registered data registers of the PLC
|
||||
/// </summary>
|
||||
IEnumerable<IRegister> Registers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Generic information about the connected PLC
|
||||
/// </summary>
|
||||
@@ -60,13 +57,24 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
int ConnectTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides an anonymous interface for register reading and writing without memory management
|
||||
/// </summary>
|
||||
RBuild Register { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to establish a connection with the device asynchronously
|
||||
/// </summary>
|
||||
Task ConnectAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the devive from its current connection
|
||||
/// Disconnects the device from its current plc connection
|
||||
/// and awaits the end of all asociated tasks
|
||||
/// </summary>
|
||||
Task DisconnectAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the device from its current plc connection
|
||||
/// </summary>
|
||||
void Disconnect();
|
||||
|
||||
@@ -97,23 +105,13 @@ namespace MewtocolNet {
|
||||
/// useful if you want to use a custom update frequency
|
||||
/// </summary>
|
||||
/// <returns>The number of inidvidual mewtocol commands sent</returns>
|
||||
Task<int> RunPollerCylceManual();
|
||||
Task<int> RunPollerCylceManualAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connection info string
|
||||
/// </summary>
|
||||
string GetConnectionInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a register to the plc
|
||||
/// </summary>
|
||||
void AddRegister(BaseRegister register);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a register to the plc
|
||||
/// </summary>
|
||||
void AddRegister(IRegister register);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a register from the plc by name
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.SetupClasses;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -14,15 +15,17 @@ namespace MewtocolNet {
|
||||
/// Builder helper for mewtocol interfaces
|
||||
/// </summary>
|
||||
public static class Mewtocol {
|
||||
|
||||
|
||||
#region Build Order 1
|
||||
|
||||
/// <summary>
|
||||
/// Builds a ethernet based Mewtocol Interface
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="station">Plc station number</param>
|
||||
/// <param name="station">Plc station number 0xEE for direct communication</param>
|
||||
/// <returns></returns>
|
||||
public static PostInit<IPlcEthernet> Ethernet (string ip, int port = 9094, int station = 1) {
|
||||
public static PostInit<IPlcEthernet> Ethernet (string ip, int port = 9094, int station = 0xEE) {
|
||||
|
||||
var instance = new MewtocolInterfaceTcp();
|
||||
instance.ConfigureConnection(ip, port, station);
|
||||
@@ -37,9 +40,9 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="station">Plc station number</param>
|
||||
/// <param name="station">Plc station number 0xEE for direct communication</param>
|
||||
/// <returns></returns>
|
||||
public static PostInit<IPlcEthernet> Ethernet(IPAddress ip, int port = 9094, int station = 1) {
|
||||
public static PostInit<IPlcEthernet> Ethernet(IPAddress ip, int port = 9094, int station = 0xEE) {
|
||||
|
||||
var instance = new MewtocolInterfaceTcp();
|
||||
instance.ConfigureConnection(ip, port, station);
|
||||
@@ -52,14 +55,14 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// Builds a serial port based Mewtocol Interface
|
||||
/// </summary>
|
||||
/// <param name="portName"></param>
|
||||
/// <param name="baudRate"></param>
|
||||
/// <param name="dataBits"></param>
|
||||
/// <param name="parity"></param>
|
||||
/// <param name="stopBits"></param>
|
||||
/// <param name="station"></param>
|
||||
/// <param name="portName">System port name</param>
|
||||
/// <param name="baudRate">Baud rate of the plc toolport</param>
|
||||
/// <param name="dataBits">DataBits of the plc toolport</param>
|
||||
/// <param name="parity">Parity rate of the plc toolport</param>
|
||||
/// <param name="stopBits">Stop bits of the plc toolport</param>
|
||||
/// <param name="station">Plc station number 0xEE for direct communication</param>
|
||||
/// <returns></returns>
|
||||
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) {
|
||||
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 = 0xEE) {
|
||||
|
||||
TestPortName(portName);
|
||||
|
||||
@@ -75,9 +78,9 @@ namespace MewtocolNet {
|
||||
/// Builds a serial mewtocol interface that finds the correct settings for the given port name automatically
|
||||
/// </summary>
|
||||
/// <param name="portName"></param>
|
||||
/// <param name="station"></param>
|
||||
/// <param name="station">Plc station number 0xEE for direct communication</param>
|
||||
/// <returns></returns>
|
||||
public static PostInit<IPlcSerial> SerialAuto (string portName, int station = 1) {
|
||||
public static PostInit<IPlcSerial> SerialAuto (string portName, int station = 0xEE) {
|
||||
|
||||
TestPortName(portName);
|
||||
|
||||
@@ -99,6 +102,10 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Build Order 2
|
||||
|
||||
public class MemoryManagerSettings {
|
||||
|
||||
/// <summary>
|
||||
@@ -125,6 +132,80 @@ namespace MewtocolNet {
|
||||
/// </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>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the poll level for the given key
|
||||
/// </summary>
|
||||
/// <param name="level">The level to reference</param>
|
||||
/// <param name="interval">Delay between poll requests</param>
|
||||
public PollLevelConfigurator SetLevel (int level, TimeSpan interval) {
|
||||
|
||||
if(level <= 1)
|
||||
throw new NotSupportedException($"The poll level {level} is not configurable");
|
||||
|
||||
if (!levelConfigs.ContainsKey(level)) {
|
||||
levelConfigs.Add(level, new PollLevelConfig {
|
||||
delay = interval,
|
||||
});
|
||||
} else {
|
||||
throw new NotSupportedException("Can't set poll levels multiple times");
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public PollLevelConfigurator SetLevel(int level, int skipNth) {
|
||||
|
||||
if (level <= 1)
|
||||
throw new NotSupportedException($"The poll level {level} is not configurable");
|
||||
|
||||
if (!levelConfigs.ContainsKey(level)) {
|
||||
levelConfigs.Add(level, new PollLevelConfig {
|
||||
skipNth = skipNth,
|
||||
});
|
||||
} else {
|
||||
throw new NotSupportedException("Can't set poll levels multiple times");
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class RegCollector {
|
||||
|
||||
internal List<RegisterCollection> collections = new List<RegisterCollection>();
|
||||
|
||||
public RegCollector AddCollection(RegisterCollection collection) {
|
||||
|
||||
collections.Add(collection);
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public RegCollector AddCollection<T>() where T : RegisterCollection {
|
||||
|
||||
var instance = (RegisterCollection)Activator.CreateInstance(typeof(T));
|
||||
|
||||
collections.Add(instance);
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class PostInit<T> {
|
||||
@@ -162,6 +243,25 @@ namespace MewtocolNet {
|
||||
|
||||
imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance;
|
||||
imew.memoryManager.maxRegistersPerGroup = res.MaxRegistersPerGroup;
|
||||
imew.memoryManager.allowByteRegDupes = res.AllowByteRegisterDupes;
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A builder for poll custom levels
|
||||
/// </summary>
|
||||
public PostInit<T> WithCustomPollLevels (Action<PollLevelConfigurator> levels) {
|
||||
|
||||
var res = new PollLevelConfigurator();
|
||||
levels.Invoke(res);
|
||||
|
||||
if (intf is MewtocolInterface imew) {
|
||||
|
||||
imew.memoryManager.pollLevelConfigs = res.levelConfigs;
|
||||
|
||||
}
|
||||
|
||||
@@ -172,9 +272,9 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// A builder for attaching register collections
|
||||
/// </summary>
|
||||
public EndInit<T> WithRegisterCollections(Action<RegisterCollectionCollector> collector) {
|
||||
public EndInit<T> WithRegisterCollections(Action<RegCollector> collector) {
|
||||
|
||||
var res = new RegisterCollectionCollector();
|
||||
var res = new RegCollector();
|
||||
collector.Invoke(res);
|
||||
|
||||
if (intf is MewtocolInterface imew) {
|
||||
@@ -194,6 +294,10 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BuildLevel 3
|
||||
|
||||
public class EndInit<T> {
|
||||
|
||||
internal PostInit<T> postInit;
|
||||
@@ -205,6 +309,8 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ using MewtocolNet.UnderlyingRegisters;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
public abstract partial class MewtocolInterface : IPlc, INotifyPropertyChanged, IDisposable {
|
||||
public abstract partial class MewtocolInterface : IPlc {
|
||||
|
||||
#region Private fields
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace MewtocolNet {
|
||||
private PLCInfo plcInfo;
|
||||
private protected int stationNumber;
|
||||
|
||||
private protected int RecBufferSize = 128;
|
||||
private protected int bytesTotalCountedUpstream = 0;
|
||||
private protected int bytesTotalCountedDownstream = 0;
|
||||
private protected int cycleTimeMs = 25;
|
||||
@@ -35,7 +36,6 @@ namespace MewtocolNet {
|
||||
private protected int bytesPerSecondDownstream = 0;
|
||||
|
||||
private protected AsyncQueue queue = new AsyncQueue();
|
||||
private protected int RecBufferSize = 128;
|
||||
private protected Stopwatch speedStopwatchUpstr;
|
||||
private protected Stopwatch speedStopwatchDownstr;
|
||||
private protected Task firstPollTask = new Task(() => { });
|
||||
@@ -52,9 +52,6 @@ namespace MewtocolNet {
|
||||
internal bool usePoller = false;
|
||||
internal MemoryAreaManager memoryManager;
|
||||
|
||||
internal List<BaseRegister> RegistersUnderlying { get; private set; } = new List<BaseRegister>();
|
||||
internal IEnumerable<IRegisterInternal> RegistersInternal => RegistersUnderlying.Cast<IRegisterInternal>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Read Only Properties / Fields
|
||||
@@ -95,9 +92,6 @@ namespace MewtocolNet {
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IRegister> Registers => RegistersUnderlying.Cast<IRegister>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int StationNumber => stationNumber;
|
||||
|
||||
@@ -173,18 +167,26 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task ConnectAsync() => throw new NotImplementedException();
|
||||
public virtual Task ConnectAsync() => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task AwaitFirstDataCycleAsync() => await firstPollTask;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task DisconnectAsync () {
|
||||
|
||||
await pollCycleTask;
|
||||
|
||||
Disconnect();
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Disconnect() {
|
||||
|
||||
if (!IsConnected) return;
|
||||
|
||||
if(pollCycleTask != null && !pollCycleTask.IsCompleted)
|
||||
pollCycleTask.Wait();
|
||||
if (!pollCycleTask.IsCompleted) pollCycleTask.Wait();
|
||||
|
||||
OnMajorSocketExceptionWhileConnected();
|
||||
|
||||
@@ -194,7 +196,9 @@ namespace MewtocolNet {
|
||||
public void Dispose() {
|
||||
|
||||
if (Disposed) return;
|
||||
|
||||
Disconnect();
|
||||
|
||||
//GC.SuppressFinalize(this);
|
||||
Disposed = true;
|
||||
|
||||
@@ -329,7 +333,7 @@ namespace MewtocolNet {
|
||||
|
||||
SetDownstreamStopWatchStart();
|
||||
|
||||
byte[] buffer = new byte[128];
|
||||
byte[] buffer = new byte[RecBufferSize];
|
||||
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
|
||||
CalcDownstreamSpeed(bytesRead);
|
||||
|
||||
@@ -3,6 +3,7 @@ using MewtocolNet.Logging;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.SetupClasses;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.Collections;
|
||||
@@ -24,14 +25,18 @@ namespace MewtocolNet {
|
||||
|
||||
internal Task pollCycleTask;
|
||||
|
||||
private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
|
||||
|
||||
internal IEnumerable<BaseRegister> RegistersInternal => GetAllRegistersInternal();
|
||||
|
||||
public IEnumerable<IRegister> Registers => GetAllRegisters();
|
||||
|
||||
/// <summary>
|
||||
/// True if the poller is actvice (can be paused)
|
||||
/// </summary>
|
||||
public bool PollerActive => !pollerTaskStopped;
|
||||
|
||||
/// <summary>
|
||||
/// Current poller cycle duration
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public int PollerCycleDurationMs {
|
||||
get => pollerCycleDurationMs;
|
||||
private set {
|
||||
@@ -40,7 +45,8 @@ namespace MewtocolNet {
|
||||
}
|
||||
}
|
||||
|
||||
private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
|
||||
/// <inheritdoc/>
|
||||
public RBuild Register => new RBuild(this);
|
||||
|
||||
#region Register Polling
|
||||
|
||||
@@ -74,11 +80,11 @@ 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> RunPollerCylceManualAsync() {
|
||||
|
||||
if (!pollerTaskStopped)
|
||||
throw new NotSupportedException($"The poller is already running, " +
|
||||
$"please make sure there is no polling active before calling {nameof(RunPollerCylceManual)}");
|
||||
$"please make sure there is no polling active before calling {nameof(RunPollerCylceManualAsync)}");
|
||||
|
||||
tcpMessagesSentThisCycle = 0;
|
||||
|
||||
@@ -139,69 +145,46 @@ namespace MewtocolNet {
|
||||
private async Task UpdateRCPRegisters() {
|
||||
|
||||
//build booleans
|
||||
var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister))
|
||||
.Select(x => (BoolRegister)x)
|
||||
.ToArray();
|
||||
//var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister))
|
||||
// .Select(x => (BoolRegister)x)
|
||||
// .ToArray();
|
||||
|
||||
//one frame can only read 8 registers at a time
|
||||
int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8);
|
||||
int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : rcpList.Length % 8;
|
||||
////one frame can only read 8 registers at a time
|
||||
//int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8);
|
||||
//int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : rcpList.Length % 8;
|
||||
|
||||
for (int i = 0; i < rcpFrameCount; i++) {
|
||||
//for (int i = 0; i < rcpFrameCount; i++) {
|
||||
|
||||
int toReadRegistersCount = 8;
|
||||
// int toReadRegistersCount = 8;
|
||||
|
||||
if (i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder;
|
||||
// if (i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder;
|
||||
|
||||
var rcpString = new StringBuilder($"%{GetStationNumber()}#RCP{toReadRegistersCount}");
|
||||
// var rcpString = new StringBuilder($"%{GetStationNumber()}#RCP{toReadRegistersCount}");
|
||||
|
||||
for (int j = 0; j < toReadRegistersCount; j++) {
|
||||
// for (int j = 0; j < toReadRegistersCount; j++) {
|
||||
|
||||
BoolRegister register = rcpList[i + j];
|
||||
rcpString.Append(register.BuildMewtocolQuery());
|
||||
// BoolRegister register = rcpList[i + j];
|
||||
// rcpString.Append(register.BuildMewtocolQuery());
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
string rcpRequest = rcpString.ToString();
|
||||
var result = await SendCommandAsync(rcpRequest);
|
||||
if (!result.Success) return;
|
||||
// string rcpRequest = rcpString.ToString();
|
||||
// var result = await SendCommandAsync(rcpRequest);
|
||||
// if (!result.Success) return;
|
||||
|
||||
var resultBitArray = result.Response.ParseRCMultiBit();
|
||||
// var resultBitArray = result.Response.ParseRCMultiBit();
|
||||
|
||||
for (int k = 0; k < resultBitArray.Length; k++) {
|
||||
// for (int k = 0; k < resultBitArray.Length; k++) {
|
||||
|
||||
var register = rcpList[i + k];
|
||||
// var register = rcpList[i + k];
|
||||
|
||||
if ((bool)register.Value != resultBitArray[k]) {
|
||||
register.SetValueFromPLC(resultBitArray[k]);
|
||||
}
|
||||
// if ((bool)register.Value != resultBitArray[k]) {
|
||||
// register.SetValueFromPLC(resultBitArray[k]);
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task UpdateDTRegisters() {
|
||||
|
||||
foreach (var reg in RegistersUnderlying) {
|
||||
|
||||
var type = reg.GetType();
|
||||
|
||||
if (reg.RegisterType.IsNumericDTDDT() || reg.RegisterType == RegisterType.DT_BYTE_RANGE) {
|
||||
|
||||
var lastVal = reg.Value;
|
||||
var rwReg = (IRegisterInternal)reg;
|
||||
var readout = await rwReg.ReadAsync();
|
||||
if (readout == null) return;
|
||||
|
||||
if (lastVal != readout) {
|
||||
rwReg.SetValueFromPLC(readout);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
@@ -217,7 +200,7 @@ namespace MewtocolNet {
|
||||
if (registerCollections.Count != 0)
|
||||
throw new NotSupportedException("Register collections can only be build once");
|
||||
|
||||
List<RegisterBuildInfo> buildInfos = new List<RegisterBuildInfo>();
|
||||
var regBuild = RBuild.Factory;
|
||||
|
||||
foreach (var collection in collections) {
|
||||
|
||||
@@ -234,18 +217,24 @@ namespace MewtocolNet {
|
||||
|
||||
if (attr is RegisterAttribute cAttribute) {
|
||||
|
||||
var pollFreqAttr = (PollLevelAttribute)attributes.FirstOrDefault(x => x.GetType() == typeof(PollLevelAttribute));
|
||||
|
||||
if (!prop.PropertyType.IsAllowedPlcCastingType()) {
|
||||
throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})");
|
||||
}
|
||||
|
||||
var dotnetType = prop.PropertyType;
|
||||
int pollLevel = 1;
|
||||
|
||||
buildInfos.Add(new RegisterBuildInfo {
|
||||
mewAddress = cAttribute.MewAddress,
|
||||
dotnetCastType = dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType,
|
||||
collectionTarget = collection,
|
||||
boundPropTarget = prop,
|
||||
});
|
||||
if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel;
|
||||
|
||||
//add builder item
|
||||
regBuild
|
||||
.Address(cAttribute.MewAddress)
|
||||
.AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType)
|
||||
.PollLevel(pollLevel)
|
||||
.RegCollection(collection)
|
||||
.BoundProp(prop);
|
||||
|
||||
}
|
||||
|
||||
@@ -265,7 +254,9 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
AddRegisters(buildInfos);
|
||||
var assembler = new RegisterAssembler(this);
|
||||
var registers = assembler.Assemble(regBuild);
|
||||
AddRegisters(registers.ToArray());
|
||||
|
||||
}
|
||||
|
||||
@@ -291,56 +282,13 @@ namespace MewtocolNet {
|
||||
|
||||
#region Register Adding
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddRegister (IRegister register) => AddRegister(register as BaseRegister);
|
||||
internal void AddRegisters (params BaseRegister[] registers) {
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddRegister (BaseRegister register) {
|
||||
|
||||
if (CheckDuplicateRegister(register))
|
||||
throw MewtocolException.DupeRegister(register);
|
||||
|
||||
if (CheckDuplicateNameRegister(register))
|
||||
throw MewtocolException.DupeNameRegister(register);
|
||||
|
||||
if (CheckOverlappingRegister(register, out var regB))
|
||||
throw MewtocolException.OverlappingRegister(register, regB);
|
||||
|
||||
register.attachedInterface = this;
|
||||
RegistersUnderlying.Add(register);
|
||||
InsertRegistersToMemoryStack(registers.ToList());
|
||||
|
||||
}
|
||||
|
||||
// Used for internal property based register building
|
||||
internal void AddRegisters (List<RegisterBuildInfo> buildInfos) {
|
||||
|
||||
//build all from attribute
|
||||
List<BaseRegister> registers = new List<BaseRegister>();
|
||||
|
||||
foreach (var buildInfo in buildInfos) {
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
internal void InsertRegistersToMemoryStack (List<BaseRegister> registers) {
|
||||
|
||||
//order by address
|
||||
registers = registers.OrderBy(x => x.GetSpecialAddress()).ToList();
|
||||
@@ -353,12 +301,7 @@ namespace MewtocolNet {
|
||||
reg.name = $"auto_prop_register_{j + 1}";
|
||||
|
||||
//link the memory area to the register
|
||||
if (memoryManager.LinkRegister(reg)) {
|
||||
|
||||
RegistersUnderlying.Add(reg);
|
||||
j++;
|
||||
|
||||
}
|
||||
if (memoryManager.LinkRegister(reg)) j++;
|
||||
|
||||
}
|
||||
|
||||
@@ -423,16 +366,22 @@ namespace MewtocolNet {
|
||||
#region Register accessing
|
||||
|
||||
/// <inheritdoc/>>
|
||||
public IRegister GetRegister(string name) {
|
||||
public IRegister GetRegister (string name) {
|
||||
|
||||
return RegistersUnderlying.FirstOrDefault(x => x.Name == name);
|
||||
return RegistersInternal.FirstOrDefault(x => x.Name == name);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IRegister> GetAllRegisters () {
|
||||
|
||||
return RegistersUnderlying.Cast<IRegister>();
|
||||
return memoryManager.GetAllRegisters().Cast<IRegister>();
|
||||
|
||||
}
|
||||
|
||||
internal IEnumerable<BaseRegister> GetAllRegistersInternal () {
|
||||
|
||||
return memoryManager.GetAllRegisters();
|
||||
|
||||
}
|
||||
|
||||
@@ -442,9 +391,11 @@ namespace MewtocolNet {
|
||||
|
||||
private protected void ClearRegisterVals() {
|
||||
|
||||
for (int i = 0; i < RegistersUnderlying.Count; i++) {
|
||||
var internals = RegistersInternal.ToList();
|
||||
|
||||
var reg = (IRegisterInternal)RegistersUnderlying[i];
|
||||
for (int i = 0; i < internals.Count; i++) {
|
||||
|
||||
var reg = (IRegisterInternal)internals[i];
|
||||
reg.ClearValue();
|
||||
|
||||
}
|
||||
@@ -453,7 +404,7 @@ namespace MewtocolNet {
|
||||
|
||||
internal void PropertyRegisterWasSet(string propName, object value) {
|
||||
|
||||
_ = SetRegisterAsync(GetRegister(propName), value);
|
||||
throw new NotImplementedException();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ namespace MewtocolNet {
|
||||
|
||||
#region Raw register reading / writing
|
||||
|
||||
[Obsolete]
|
||||
internal async Task<byte[]> ReadRawRegisterAsync (IRegisterInternal _toRead) {
|
||||
|
||||
var toreadType = _toRead.GetType();
|
||||
@@ -237,6 +238,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
internal async Task<bool> WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) {
|
||||
|
||||
var toWriteType = _toWrite.GetType();
|
||||
@@ -254,36 +256,6 @@ namespace MewtocolNet {
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
return result.Success;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Register reading / writing
|
||||
|
||||
internal async Task<bool> SetRegisterAsync (IRegister register, object value) {
|
||||
|
||||
var internalReg = (IRegisterInternal)register;
|
||||
|
||||
return await internalReg.WriteAsync(value);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reading / Writing Plc program
|
||||
|
||||
public async Task GetSystemRegister () {
|
||||
|
||||
//the "." means CR or \r
|
||||
|
||||
await SendCommandAsync("%EE#RT");
|
||||
|
||||
//then get plc status extended? gets polled all time
|
||||
// %EE#EX00RT00
|
||||
await SendCommandAsync("%EE#EX00RT00");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -292,8 +264,12 @@ namespace MewtocolNet {
|
||||
|
||||
internal string GetStationNumber() {
|
||||
|
||||
return StationNumber.ToString().PadLeft(2, '0');
|
||||
if (StationNumber != 0xEE && StationNumber > 99)
|
||||
throw new NotSupportedException("Station number was greater 99");
|
||||
|
||||
if(StationNumber == 0xEE) return "EE";
|
||||
|
||||
return StationNumber.ToString().PadLeft(2, '0');
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ConfigureConnection (string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 1) {
|
||||
public void ConfigureConnection (string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 0xEE) {
|
||||
|
||||
PortName = _portName;
|
||||
SerialBaudRate = _baudRate;
|
||||
@@ -90,6 +90,9 @@ namespace MewtocolNet {
|
||||
SerialStopBits = _stopBits;
|
||||
stationNumber = _station;
|
||||
|
||||
if (stationNumber != 0xEE && stationNumber > 99)
|
||||
throw new NotSupportedException("Station number can't be greater than 99");
|
||||
|
||||
OnSerialPropsChanged();
|
||||
Disconnect();
|
||||
|
||||
|
||||
@@ -32,11 +32,14 @@ namespace MewtocolNet {
|
||||
#region TCP connection state handling
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ConfigureConnection (string ip, int port = 9094, int station = 1) {
|
||||
public void ConfigureConnection (string ip, int port = 9094, int station = 0xEE) {
|
||||
|
||||
if (!IPAddress.TryParse(ip, out ipAddr))
|
||||
throw new MewtocolException($"The ip: {ip} is no valid ip address");
|
||||
|
||||
|
||||
if (stationNumber != 0xEE && stationNumber > 99)
|
||||
throw new NotSupportedException("Station number can't be greater than 99");
|
||||
|
||||
Port = port;
|
||||
stationNumber = station;
|
||||
|
||||
@@ -45,10 +48,14 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 1) {
|
||||
public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 0xEE) {
|
||||
|
||||
ipAddr = ip;
|
||||
Port = port;
|
||||
|
||||
if (stationNumber != 0xEE && stationNumber > 99)
|
||||
throw new NotSupportedException("Station number can't be greater than 99");
|
||||
|
||||
stationNumber = station;
|
||||
|
||||
Disconnect();
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
21
MewtocolNet/RegisterAttributes/PollLevelAttribute.cs
Normal file
21
MewtocolNet/RegisterAttributes/PollLevelAttribute.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace MewtocolNet.RegisterAttributes {
|
||||
|
||||
/// <summary>
|
||||
/// Defines the poll level of the register
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class PollLevelAttribute : Attribute {
|
||||
|
||||
internal int pollLevel;
|
||||
|
||||
public PollLevelAttribute(int level) {
|
||||
|
||||
pollLevel = level;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
public static class BuilderStepExtensions {
|
||||
|
||||
public static BuilderStep<T> AsType<T> (this BuilderStepBase baseStep) {
|
||||
|
||||
if (!typeof(T).IsAllowedPlcCastingType()) {
|
||||
|
||||
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting");
|
||||
|
||||
}
|
||||
|
||||
var castStp = new BuilderStep<T>();
|
||||
|
||||
if (baseStep.SpecialAddress != null) castStp.SpecialAddress = baseStep.SpecialAddress;
|
||||
|
||||
castStp.Name = baseStep.Name;
|
||||
castStp.RegType = baseStep.RegType;
|
||||
castStp.MemAddress = baseStep.MemAddress;
|
||||
castStp.MemByteSize = baseStep.MemByteSize;
|
||||
castStp.dotnetVarType = typeof(T);
|
||||
castStp.plcVarType = null;
|
||||
castStp.wasCasted = true;
|
||||
|
||||
return castStp;
|
||||
|
||||
}
|
||||
|
||||
public static BuilderStep AsType (this BuilderStepBase baseStep, Type type) {
|
||||
|
||||
if (!type.IsAllowedPlcCastingType()) {
|
||||
|
||||
throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting");
|
||||
|
||||
}
|
||||
|
||||
var castStp = new BuilderStep();
|
||||
|
||||
if (baseStep.SpecialAddress != null) castStp.SpecialAddress = baseStep.SpecialAddress;
|
||||
|
||||
castStp.Name = baseStep.Name;
|
||||
castStp.RegType = baseStep.RegType;
|
||||
castStp.MemAddress = baseStep.MemAddress;
|
||||
castStp.MemByteSize = baseStep.MemByteSize;
|
||||
castStp.dotnetVarType = type;
|
||||
castStp.plcVarType = null;
|
||||
castStp.wasCasted = true;
|
||||
|
||||
return castStp;
|
||||
|
||||
}
|
||||
|
||||
public static IRegister Build (this BuilderStepBase step) {
|
||||
|
||||
//if no casting method in builder was called => autocast the type from the RegisterType
|
||||
if (!step.wasCasted && step.MemByteSize == null) step.AutoType();
|
||||
|
||||
//fallbacks if no casting builder was given
|
||||
BuilderStepBase.GetFallbackDotnetType(step);
|
||||
|
||||
BaseRegister builtReg;
|
||||
|
||||
var bInfo = new RegisterBuildInfo {
|
||||
|
||||
name = step.Name,
|
||||
specialAddress = step.SpecialAddress,
|
||||
memoryAddress = step.MemAddress,
|
||||
memorySizeBytes = step.MemByteSize,
|
||||
memorySizeBits = step.MemBitSize,
|
||||
registerType = step.RegType,
|
||||
dotnetCastType = step.dotnetVarType,
|
||||
|
||||
};
|
||||
|
||||
builtReg = bInfo.Build();
|
||||
|
||||
BuilderStepBase.AddToRegisterList(step, builtReg);
|
||||
|
||||
return builtReg;
|
||||
|
||||
}
|
||||
|
||||
public static IRegister Build<T>(this BuilderStep<T> step) {
|
||||
|
||||
//fallbacks if no casting builder was given
|
||||
BuilderStepBase.GetFallbackDotnetType(step);
|
||||
|
||||
BaseRegister builtReg;
|
||||
|
||||
var bInfo = new RegisterBuildInfo {
|
||||
|
||||
name = step.Name,
|
||||
specialAddress = step.SpecialAddress,
|
||||
memoryAddress = step.MemAddress,
|
||||
memorySizeBytes = step.MemByteSize,
|
||||
memorySizeBits = step.MemBitSize,
|
||||
registerType = step.RegType,
|
||||
dotnetCastType = step.dotnetVarType,
|
||||
|
||||
};
|
||||
|
||||
if (step.dotnetVarType.IsEnum) {
|
||||
|
||||
builtReg = bInfo.Build();
|
||||
|
||||
} else {
|
||||
|
||||
builtReg = bInfo.Build();
|
||||
|
||||
}
|
||||
|
||||
BuilderStepBase.AddToRegisterList(step, builtReg);
|
||||
|
||||
return builtReg;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public abstract class BuilderStepBase {
|
||||
|
||||
internal MewtocolInterface forInterface;
|
||||
|
||||
internal bool wasCasted = false;
|
||||
|
||||
internal string OriginalInput;
|
||||
|
||||
internal string Name;
|
||||
internal RegisterType RegType;
|
||||
|
||||
internal uint MemAddress;
|
||||
|
||||
internal uint? MemByteSize;
|
||||
internal ushort? MemBitSize;
|
||||
internal byte? SpecialAddress;
|
||||
|
||||
internal PlcVarType? plcVarType;
|
||||
internal Type dotnetVarType;
|
||||
|
||||
internal BuilderStepBase AutoType() {
|
||||
|
||||
switch (RegType) {
|
||||
case RegisterType.X:
|
||||
case RegisterType.Y:
|
||||
case RegisterType.R:
|
||||
dotnetVarType = typeof(bool);
|
||||
break;
|
||||
case RegisterType.DT:
|
||||
dotnetVarType = typeof(short);
|
||||
break;
|
||||
case RegisterType.DDT:
|
||||
dotnetVarType = typeof(int);
|
||||
break;
|
||||
case RegisterType.DT_BYTE_RANGE:
|
||||
dotnetVarType = typeof(string);
|
||||
break;
|
||||
}
|
||||
|
||||
plcVarType = null;
|
||||
|
||||
wasCasted = true;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
internal static void GetFallbackDotnetType (BuilderStepBase step) {
|
||||
|
||||
bool isBoolean = step.RegType.IsBoolean();
|
||||
bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null;
|
||||
|
||||
if (isTypeNotDefined && step.RegType == RegisterType.DT) {
|
||||
|
||||
step.dotnetVarType = typeof(short);
|
||||
|
||||
}
|
||||
if (isTypeNotDefined && step.RegType == RegisterType.DDT) {
|
||||
|
||||
step.dotnetVarType = typeof(int);
|
||||
|
||||
} else if (isTypeNotDefined && isBoolean) {
|
||||
|
||||
step.dotnetVarType = typeof(bool);
|
||||
|
||||
} else if (isTypeNotDefined && step.RegType == RegisterType.DT_BYTE_RANGE) {
|
||||
|
||||
step.dotnetVarType = typeof(string);
|
||||
|
||||
}
|
||||
|
||||
if (step.plcVarType != null) {
|
||||
|
||||
step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static void AddToRegisterList(BuilderStepBase step, BaseRegister instance) {
|
||||
|
||||
if (step.forInterface == null) return;
|
||||
|
||||
step.forInterface.AddRegister(instance);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class BuilderStep<T> : BuilderStepBase { }
|
||||
|
||||
public class BuilderStep : BuilderStepBase {
|
||||
|
||||
public BuilderStep AsPlcType (PlcVarType varType) {
|
||||
|
||||
dotnetVarType = null;
|
||||
plcVarType = varType;
|
||||
|
||||
wasCasted = true;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public BuilderStep AsBytes (uint byteLength) {
|
||||
|
||||
if (RegType != RegisterType.DT) {
|
||||
|
||||
throw new NotSupportedException($"Cant use the {nameof(AsBytes)} converter on a non {nameof(RegisterType.DT)} register");
|
||||
|
||||
}
|
||||
|
||||
MemByteSize = byteLength;
|
||||
dotnetVarType = typeof(byte[]);
|
||||
plcVarType = null;
|
||||
|
||||
wasCasted = true;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public BuilderStep AsBits(ushort bitCount = 16) {
|
||||
|
||||
if (RegType != RegisterType.DT) {
|
||||
|
||||
throw new NotSupportedException($"Cant use the {nameof(AsBits)} converter on a non {nameof(RegisterType.DT)} register");
|
||||
|
||||
}
|
||||
|
||||
MemBitSize = bitCount;
|
||||
dotnetVarType = typeof(BitArray);
|
||||
plcVarType = null;
|
||||
|
||||
wasCasted = true;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
internal struct ParseResult {
|
||||
|
||||
public ParseResultState state;
|
||||
|
||||
public string hardFailReason;
|
||||
|
||||
public BuilderStep stepData;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
609
MewtocolNet/RegisterBuilding/RBuild.cs
Normal file
609
MewtocolNet/RegisterBuilding/RBuild.cs
Normal file
@@ -0,0 +1,609 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.UnderlyingRegisters;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
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,
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains useful tools for register creation
|
||||
/// </summary>
|
||||
public class RBuild {
|
||||
|
||||
private MewtocolInterface attachedPLC;
|
||||
|
||||
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
|
||||
|
||||
//methods to test the input string on
|
||||
private static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
|
||||
|
||||
(x) => TryBuildBoolean(x),
|
||||
(x) => TryBuildNumericBased(x),
|
||||
(x) => TryBuildByteRangeBased(x),
|
||||
|
||||
};
|
||||
|
||||
internal class SData {
|
||||
|
||||
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 pollLevel = 1;
|
||||
|
||||
internal RegisterCollection regCollection;
|
||||
internal PropertyInfo boundProperty;
|
||||
|
||||
}
|
||||
|
||||
public class SBase {
|
||||
|
||||
public SBase() { }
|
||||
|
||||
internal SBase(SData data) {
|
||||
Data = data;
|
||||
}
|
||||
|
||||
internal SData Data { get; set; }
|
||||
|
||||
}
|
||||
|
||||
internal struct ParseResult {
|
||||
|
||||
public ParseResultState state;
|
||||
|
||||
public string hardFailReason;
|
||||
|
||||
public SData stepData;
|
||||
|
||||
}
|
||||
|
||||
//bool registers
|
||||
private static ParseResult TryBuildBoolean(string plcAddrName) {
|
||||
|
||||
//regex to find special register values
|
||||
var patternBool = new Regex(@"(?<prefix>X|Y|R)(?<area>[0-9]{0,3})(?<special>(?:[0-9]|[A-F]){1})?");
|
||||
|
||||
var match = patternBool.Match(plcAddrName);
|
||||
|
||||
if (!match.Success)
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedSoft
|
||||
};
|
||||
|
||||
string prefix = match.Groups["prefix"].Value;
|
||||
string area = match.Groups["area"].Value;
|
||||
string special = match.Groups["special"].Value;
|
||||
|
||||
IOType regType;
|
||||
uint areaAdd = 0;
|
||||
byte specialAdd = 0x0;
|
||||
|
||||
//try cast the prefix
|
||||
if (!Enum.TryParse(prefix, out regType)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for boolean registers"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//special address not given
|
||||
if (string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) {
|
||||
|
||||
var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt);
|
||||
|
||||
if (isAreaInt && areaInt >= 0 && areaInt <= 9) {
|
||||
|
||||
//area address is actually meant as special address but 0-9
|
||||
specialAdd = (byte)areaInt;
|
||||
areaAdd = 0;
|
||||
|
||||
|
||||
} else if (isAreaInt && areaInt > 9) {
|
||||
|
||||
//area adress is meant to be the actual area address
|
||||
areaAdd = areaInt;
|
||||
specialAdd = 0;
|
||||
|
||||
} else {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 1",
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
//special address parsed as hex num
|
||||
if (!byte.TryParse(special, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out specialAdd)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 2",
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.Success,
|
||||
stepData = new SData {
|
||||
regType = (RegisterType)(int)regType,
|
||||
memAddress = areaAdd,
|
||||
specialAddress = specialAdd,
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// one to two word registers
|
||||
private static ParseResult TryBuildNumericBased(string plcAddrName) {
|
||||
|
||||
var patternByte = new Regex(@"^(?<prefix>DT|DDT)(?<area>[0-9]{1,5})$");
|
||||
|
||||
var match = patternByte.Match(plcAddrName);
|
||||
|
||||
if (!match.Success)
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedSoft
|
||||
};
|
||||
|
||||
string prefix = match.Groups["prefix"].Value;
|
||||
string area = match.Groups["area"].Value;
|
||||
|
||||
RegisterType regType;
|
||||
uint areaAdd = 0;
|
||||
|
||||
//try cast the prefix
|
||||
if (!Enum.TryParse(prefix, out regType)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for numeric registers"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.Success,
|
||||
stepData = new SData {
|
||||
regType = regType,
|
||||
memAddress = areaAdd,
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// one to two word registers
|
||||
private static ParseResult TryBuildByteRangeBased(string plcAddrName) {
|
||||
|
||||
var split = plcAddrName.Split('-');
|
||||
|
||||
if (split.Length > 2)
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'"
|
||||
};
|
||||
|
||||
uint[] addresses = new uint[2];
|
||||
|
||||
for (int i = 0; i < split.Length; i++) {
|
||||
|
||||
string addr = split[i];
|
||||
var patternByte = new Regex(@"(?<prefix>DT|DDT)(?<area>[0-9]{1,5})");
|
||||
|
||||
var match = patternByte.Match(addr);
|
||||
|
||||
if (!match.Success)
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedSoft
|
||||
};
|
||||
|
||||
string prefix = match.Groups["prefix"].Value;
|
||||
string area = match.Groups["area"].Value;
|
||||
|
||||
RegisterType regType;
|
||||
uint areaAdd = 0;
|
||||
|
||||
//try cast the prefix
|
||||
if (!Enum.TryParse(prefix, out regType) || regType != RegisterType.DT) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
addresses[i] = areaAdd;
|
||||
|
||||
}
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.Success,
|
||||
stepData = new SData {
|
||||
regType = RegisterType.DT_BYTE_RANGE,
|
||||
dotnetVarType = typeof(byte[]),
|
||||
memAddress = addresses[0],
|
||||
byteSize = (addresses[1] - addresses[0] + 1) * 2
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public SAddress Address (string plcAddrName, string name = null) {
|
||||
|
||||
foreach (var method in parseMethods) {
|
||||
|
||||
var res = method.Invoke(plcAddrName);
|
||||
|
||||
if(res.state == ParseResultState.Success) {
|
||||
|
||||
if (!string.IsNullOrEmpty(name)) res.stepData.name = name;
|
||||
|
||||
res.stepData.originalParseStr = plcAddrName;
|
||||
|
||||
unfinishedList.Add(res.stepData);
|
||||
|
||||
return new SAddress {
|
||||
Data = res.stepData
|
||||
};
|
||||
|
||||
} else if(res.state == ParseResultState.FailedHard) {
|
||||
|
||||
throw new Exception(res.hardFailReason);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new Exception("Wrong input format");
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type determination stage
|
||||
|
||||
public class SAddress : SBase {
|
||||
|
||||
public TempRegister<T> AsType<T> () {
|
||||
|
||||
if (!typeof(T).IsAllowedPlcCastingType()) {
|
||||
|
||||
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting");
|
||||
|
||||
}
|
||||
|
||||
Data.dotnetVarType = typeof(T);
|
||||
|
||||
return new TempRegister<T>(Data);
|
||||
|
||||
}
|
||||
|
||||
public TempRegister AsType (Type type) {
|
||||
|
||||
if (!type.IsAllowedPlcCastingType()) {
|
||||
|
||||
throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting");
|
||||
|
||||
}
|
||||
|
||||
Data.dotnetVarType = type;
|
||||
|
||||
return new TempRegister(Data);
|
||||
|
||||
}
|
||||
|
||||
public TempRegister AsBytes (uint byteLength) {
|
||||
|
||||
if (Data.regType != RegisterType.DT) {
|
||||
|
||||
throw new NotSupportedException($"Cant use the {nameof(AsBytes)} converter on a non {nameof(RegisterType.DT)} register");
|
||||
|
||||
}
|
||||
|
||||
Data.byteSize = byteLength;
|
||||
Data.dotnetVarType = typeof(byte[]);
|
||||
|
||||
return new TempRegister(Data);
|
||||
|
||||
}
|
||||
|
||||
public TempRegister AsBits (ushort bitCount = 16) {
|
||||
|
||||
if (Data.regType != RegisterType.DT) {
|
||||
|
||||
throw new NotSupportedException($"Cant use the {nameof(AsBits)} converter on a non {nameof(RegisterType.DT)} register");
|
||||
|
||||
}
|
||||
|
||||
Data.bitSize = bitCount;
|
||||
Data.dotnetVarType = typeof(BitArray);
|
||||
|
||||
return new TempRegister(Data);
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return new TempRegister(Data);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Options stage
|
||||
|
||||
public class TempRegister<T> : SBase {
|
||||
|
||||
internal TempRegister(SData data) : base(data) {}
|
||||
|
||||
public TempRegister<T> PollLevel (int level) {
|
||||
|
||||
Data.pollLevel = level;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public async Task WriteToAsync (T value) => throw new NotImplementedException();
|
||||
|
||||
}
|
||||
|
||||
public class TempRegister : SBase {
|
||||
|
||||
internal TempRegister(SData data) : base(data) { }
|
||||
|
||||
public TempRegister PollLevel (int level) {
|
||||
|
||||
Data.pollLevel = level;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
internal TempRegister RegCollection (RegisterCollection col) {
|
||||
|
||||
Data.regCollection = col;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
internal TempRegister BoundProp (PropertyInfo prop) {
|
||||
|
||||
Data.boundProperty = prop;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public async Task WriteToAsync (object value) => throw new NotImplementedException();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
internal class RegisterAssembler {
|
||||
|
||||
internal RegisterCollection collectionTarget;
|
||||
|
||||
internal MewtocolInterface onInterface;
|
||||
|
||||
internal RegisterAssembler (MewtocolInterface interf) {
|
||||
|
||||
onInterface = interf;
|
||||
|
||||
}
|
||||
|
||||
internal List<BaseRegister> Assemble (RBuild rBuildData) {
|
||||
|
||||
List<BaseRegister> generatedInstances = new List<BaseRegister>();
|
||||
|
||||
foreach (var data in rBuildData.unfinishedList) {
|
||||
|
||||
//parse all others where the type is known
|
||||
Type registerClassType = data.dotnetVarType.GetDefaultRegisterHoldingType();
|
||||
|
||||
BaseRegister generatedInstance = null;
|
||||
|
||||
if (data.dotnetVarType.IsEnum) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as numeric register with enum target
|
||||
|
||||
var underlying = Enum.GetUnderlyingType(data.dotnetVarType);
|
||||
var enuSize = Marshal.SizeOf(underlying);
|
||||
|
||||
if (enuSize > 4)
|
||||
throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported");
|
||||
|
||||
Type myParameterizedSomeClass = typeof(NumberRegister<>).MakeGenericType(data.dotnetVarType);
|
||||
ConstructorInfo constr = myParameterizedSomeClass.GetConstructor(new Type[] { typeof(uint), typeof(string) });
|
||||
|
||||
var parameters = new object[] { data.memAddress, data.name };
|
||||
var instance = (BaseRegister)constr.Invoke(parameters);
|
||||
|
||||
generatedInstance = instance;
|
||||
|
||||
} else if (registerClassType.IsGenericType) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as numeric register
|
||||
|
||||
//create a new bregister instance
|
||||
var flags = BindingFlags.Public | 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);
|
||||
instance.pollLevel = data.pollLevel;
|
||||
|
||||
generatedInstance = instance;
|
||||
|
||||
} else if (registerClassType == typeof(BytesRegister) && data.byteSize != null) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as byte range register
|
||||
|
||||
BytesRegister instance = new BytesRegister(data.memAddress, (uint)data.byteSize, data.name);
|
||||
|
||||
generatedInstance = instance;
|
||||
|
||||
} else if (registerClassType == typeof(BytesRegister) && data.bitSize != null) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as bit range register
|
||||
|
||||
BytesRegister instance = new BytesRegister(data.memAddress, (ushort)data.bitSize, data.name);
|
||||
|
||||
generatedInstance = instance;
|
||||
|
||||
} else if (registerClassType == typeof(StringRegister)) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as byte range register
|
||||
var instance = (BaseRegister)new StringRegister(data.memAddress, data.name);
|
||||
|
||||
generatedInstance = instance;
|
||||
|
||||
} else if (data.regType.IsBoolean()) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as boolean register
|
||||
|
||||
var io = (IOType)(int)data.regType;
|
||||
var spAddr = data.specialAddress;
|
||||
var areaAddr = data.memAddress;
|
||||
|
||||
var instance = new BoolRegister(io, spAddr, areaAddr, data.name);
|
||||
|
||||
generatedInstance = instance;
|
||||
|
||||
}
|
||||
|
||||
//finalize set for every
|
||||
|
||||
if(generatedInstance == null)
|
||||
throw new MewtocolException("Failed to build register");
|
||||
|
||||
if (collectionTarget != null)
|
||||
generatedInstance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
generatedInstance.attachedInterface = onInterface;
|
||||
|
||||
generatedInstance.pollLevel = data.pollLevel;
|
||||
|
||||
generatedInstances.Add(generatedInstance);
|
||||
|
||||
}
|
||||
|
||||
return generatedInstances;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
/// <summary>
|
||||
/// Contains useful tools for register creation
|
||||
/// </summary>
|
||||
public class RegBuilder {
|
||||
|
||||
internal MewtocolInterface forInterface = null;
|
||||
|
||||
//methods to test the input string on
|
||||
private static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
|
||||
|
||||
(x) => TryBuildBoolean(x),
|
||||
(x) => TryBuildNumericBased(x),
|
||||
(x) => TryBuildByteRangeBased(x),
|
||||
|
||||
};
|
||||
|
||||
public static RegBuilder ForInterface (IPlc interf) {
|
||||
|
||||
var rb = new RegBuilder();
|
||||
rb.forInterface = interf as MewtocolInterface;
|
||||
return rb;
|
||||
|
||||
}
|
||||
|
||||
public static RegBuilder Factory { get; private set; } = new RegBuilder();
|
||||
|
||||
|
||||
public BuilderStep FromPlcRegName (string plcAddrName, string name = null) {
|
||||
|
||||
foreach (var method in parseMethods) {
|
||||
|
||||
var res = method.Invoke(plcAddrName);
|
||||
|
||||
if(res.state == ParseResultState.Success) {
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
res.stepData.Name = name;
|
||||
|
||||
res.stepData.OriginalInput = plcAddrName;
|
||||
res.stepData.forInterface = forInterface;
|
||||
|
||||
return res.stepData;
|
||||
|
||||
} else if(res.state == ParseResultState.FailedHard) {
|
||||
|
||||
throw new Exception(res.hardFailReason);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new Exception("Wrong input format");
|
||||
|
||||
}
|
||||
|
||||
//bool registers
|
||||
private static ParseResult TryBuildBoolean (string plcAddrName) {
|
||||
|
||||
//regex to find special register values
|
||||
var patternBool = new Regex(@"(?<prefix>X|Y|R)(?<area>[0-9]{0,3})(?<special>(?:[0-9]|[A-F]){1})?");
|
||||
|
||||
var match = patternBool.Match(plcAddrName);
|
||||
|
||||
if (!match.Success)
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedSoft
|
||||
};
|
||||
|
||||
string prefix = match.Groups["prefix"].Value;
|
||||
string area = match.Groups["area"].Value;
|
||||
string special = match.Groups["special"].Value;
|
||||
|
||||
IOType regType;
|
||||
uint areaAdd = 0;
|
||||
byte specialAdd = 0x0;
|
||||
|
||||
//try cast the prefix
|
||||
if(!Enum.TryParse(prefix, out regType)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for boolean registers"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if(!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd) ) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//special address not given
|
||||
if(string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) {
|
||||
|
||||
var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt);
|
||||
|
||||
if (isAreaInt && areaInt >= 0 && areaInt <= 9) {
|
||||
|
||||
//area address is actually meant as special address but 0-9
|
||||
specialAdd = (byte)areaInt;
|
||||
areaAdd = 0;
|
||||
|
||||
|
||||
} else if (isAreaInt && areaInt > 9) {
|
||||
|
||||
//area adress is meant to be the actual area address
|
||||
areaAdd = areaInt;
|
||||
specialAdd = 0;
|
||||
|
||||
} else {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 1",
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
//special address parsed as hex num
|
||||
if (!byte.TryParse(special, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out specialAdd)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 2",
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.Success,
|
||||
stepData = new BuilderStep {
|
||||
RegType = (RegisterType)(int)regType,
|
||||
MemAddress = areaAdd,
|
||||
SpecialAddress = specialAdd,
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// one to two word registers
|
||||
private static ParseResult TryBuildNumericBased (string plcAddrName) {
|
||||
|
||||
var patternByte = new Regex(@"^(?<prefix>DT|DDT)(?<area>[0-9]{1,5})$");
|
||||
|
||||
var match = patternByte.Match(plcAddrName);
|
||||
|
||||
if (!match.Success)
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedSoft
|
||||
};
|
||||
|
||||
string prefix = match.Groups["prefix"].Value;
|
||||
string area = match.Groups["area"].Value;
|
||||
|
||||
RegisterType regType;
|
||||
uint areaAdd = 0;
|
||||
|
||||
//try cast the prefix
|
||||
if (!Enum.TryParse(prefix, out regType)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for numeric registers"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.Success,
|
||||
stepData = new BuilderStep {
|
||||
RegType = regType,
|
||||
MemAddress = areaAdd,
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// one to two word registers
|
||||
private static ParseResult TryBuildByteRangeBased (string plcAddrName) {
|
||||
|
||||
var split = plcAddrName.Split('-');
|
||||
|
||||
if(split.Length > 2)
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'"
|
||||
};
|
||||
|
||||
uint[] addresses = new uint[2];
|
||||
|
||||
for (int i = 0; i < split.Length; i++) {
|
||||
|
||||
string addr = split[i];
|
||||
var patternByte = new Regex(@"(?<prefix>DT|DDT)(?<area>[0-9]{1,5})");
|
||||
|
||||
var match = patternByte.Match(addr);
|
||||
|
||||
if (!match.Success)
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedSoft
|
||||
};
|
||||
|
||||
string prefix = match.Groups["prefix"].Value;
|
||||
string area = match.Groups["area"].Value;
|
||||
|
||||
RegisterType regType;
|
||||
uint areaAdd = 0;
|
||||
|
||||
//try cast the prefix
|
||||
if (!Enum.TryParse(prefix, out regType) || regType != RegisterType.DT) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.FailedHard,
|
||||
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
addresses[i] = areaAdd;
|
||||
|
||||
}
|
||||
|
||||
return new ParseResult {
|
||||
state = ParseResultState.Success,
|
||||
stepData = new BuilderStep {
|
||||
RegType = RegisterType.DT_BYTE_RANGE,
|
||||
dotnetVarType = typeof(byte[]),
|
||||
MemAddress = addresses[0],
|
||||
MemByteSize = (addresses[1] - addresses[0] + 1) * 2,
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
28
MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs
Normal file
28
MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
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);
|
||||
|
||||
var assembler = new RegisterAssembler((MewtocolInterface)plc);
|
||||
var registers = assembler.Assemble(regBuilder);
|
||||
|
||||
var interf = (MewtocolInterface)plc;
|
||||
|
||||
interf.AddRegisters(registers.ToArray());
|
||||
|
||||
return plc;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Data.Common;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
internal struct RegisterBuildInfo {
|
||||
|
||||
internal string mewAddress;
|
||||
|
||||
internal string name;
|
||||
internal uint memoryAddress;
|
||||
|
||||
internal uint? memorySizeBytes;
|
||||
internal ushort? memorySizeBits;
|
||||
internal byte? specialAddress;
|
||||
|
||||
internal RegisterType? registerType;
|
||||
|
||||
internal Type dotnetCastType;
|
||||
|
||||
internal RegisterCollection collectionTarget;
|
||||
internal PropertyInfo boundPropTarget;
|
||||
|
||||
internal BaseRegister BuildForCollectionAttribute () {
|
||||
|
||||
return (BaseRegister)RegBuilder.Factory.FromPlcRegName(mewAddress, name).AsType(dotnetCastType).Build();
|
||||
|
||||
}
|
||||
|
||||
internal BaseRegister Build () {
|
||||
|
||||
//parse enums
|
||||
if (dotnetCastType.IsEnum) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as numeric register with enum target
|
||||
|
||||
var underlying = Enum.GetUnderlyingType(dotnetCastType);
|
||||
var enuSize = Marshal.SizeOf(underlying);
|
||||
|
||||
if (enuSize > 4)
|
||||
throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported");
|
||||
|
||||
Type myParameterizedSomeClass = typeof(NumberRegister<>).MakeGenericType(dotnetCastType);
|
||||
ConstructorInfo constr = myParameterizedSomeClass.GetConstructor(new Type[] { typeof(uint), typeof(string) });
|
||||
|
||||
var parameters = new object[] { memoryAddress, name };
|
||||
var instance = (BaseRegister)constr.Invoke(parameters);
|
||||
|
||||
if (collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
//parse all others where the type is known
|
||||
RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault();
|
||||
Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType();
|
||||
|
||||
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);
|
||||
|
||||
bool isNormalNumericResiter = regType.IsNumericDTDDT() && !isBytesArrRegister && !isBytesBitsRegister && !isStringRegister;
|
||||
|
||||
if (isNormalNumericResiter) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as numeric register
|
||||
|
||||
//create a new bregister instance
|
||||
var flags = BindingFlags.Public | BindingFlags.Instance;
|
||||
|
||||
//int _adress, Type _enumType = null, string _name = null
|
||||
var parameters = new object[] { memoryAddress, name };
|
||||
var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null);
|
||||
|
||||
if(collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
instance = new BytesRegister(memoryAddress, memorySizeBits.Value, name);
|
||||
} else {
|
||||
instance = new BytesRegister(memoryAddress, 16, name);
|
||||
}
|
||||
|
||||
if (collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
if (isStringRegister) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as byte range register
|
||||
var instance = (BaseRegister)new StringRegister(memoryAddress, name);
|
||||
|
||||
if (collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
if (isBoolRegister) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as boolean register
|
||||
|
||||
var io = (IOType)(int)regType;
|
||||
var spAddr = specialAddress;
|
||||
var areaAddr = memoryAddress;
|
||||
|
||||
var instance = new BoolRegister(io, spAddr.Value, areaAddr, name);
|
||||
|
||||
if (collectionTarget != null)
|
||||
instance.WithRegisterCollection(collectionTarget);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
throw new Exception("Failed to build register");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,10 @@ namespace MewtocolNet.Registers {
|
||||
internal object lastValue = null;
|
||||
internal string name;
|
||||
internal uint memoryAddress;
|
||||
internal int pollLevel = 0;
|
||||
|
||||
internal uint successfulReads = 0;
|
||||
internal uint successfulWrites = 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RegisterCollection ContainedCollection => containedCollection;
|
||||
@@ -82,6 +86,10 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
public virtual Task<bool> WriteAsync(object data) => throw new NotImplementedException();
|
||||
|
||||
internal virtual Task<bool> WriteToAnonymousAsync (object value) => throw new NotImplementedException();
|
||||
|
||||
internal virtual Task<object> ReadFromAnonymousAsync () => throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Default accessors
|
||||
@@ -129,6 +137,16 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void AddSuccessRead () {
|
||||
if (successfulReads == uint.MaxValue) successfulReads = 0;
|
||||
else successfulReads++;
|
||||
}
|
||||
|
||||
protected virtual void AddSuccessWrite () {
|
||||
if (successfulWrites == uint.MaxValue) successfulWrites = 0;
|
||||
else successfulWrites++;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
|
||||
var sb = new StringBuilder();
|
||||
@@ -148,9 +166,10 @@ namespace MewtocolNet.Registers {
|
||||
sb.AppendLine($"MewName: {GetMewName()}");
|
||||
sb.AppendLine($"Name: {Name ?? "Not named"}");
|
||||
sb.AppendLine($"Value: {GetValueString()}");
|
||||
sb.AppendLine($"Perf. Reads: {successfulReads}, Writes: {successfulWrites}");
|
||||
sb.AppendLine($"Register Type: {RegisterType}");
|
||||
sb.AppendLine($"Address: {GetRegisterWordRangeString()}");
|
||||
if(GetSpecialAddress() != null) sb.AppendLine($"SPAddress: {GetSpecialAddress()}");
|
||||
if(GetSpecialAddress() != null) sb.AppendLine($"SPAddress: {GetSpecialAddress():X1}");
|
||||
if (GetType().IsGenericType) sb.AppendLine($"Type: NumberRegister<{GetType().GenericTypeArguments[0]}>");
|
||||
else sb.AppendLine($"Type: {GetType()}");
|
||||
if(containedCollection != null) sb.AppendLine($"In collection: {containedCollection.GetType()}");
|
||||
|
||||
@@ -112,9 +112,6 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ClearValue() => SetValueFromPLC(false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetMewName() {
|
||||
|
||||
|
||||
@@ -117,9 +117,6 @@ namespace MewtocolNet.Registers {
|
||||
/// <inheritdoc/>
|
||||
public override string GetRegisterString() => "DT";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ClearValue() => SetValueFromPLC(null);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override uint GetRegisterAddressLen() => AddressLength;
|
||||
|
||||
@@ -139,6 +136,8 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
internal override object SetValueFromBytes(byte[] bytes) {
|
||||
|
||||
AddSuccessRead();
|
||||
|
||||
object parsed;
|
||||
if (ReservedBitSize != null) {
|
||||
parsed = PlcValueParser.Parse<BitArray>(this, bytes);
|
||||
@@ -166,6 +165,7 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
|
||||
if (res) {
|
||||
AddSuccessWrite();
|
||||
SetValueFromPLC(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static MewtocolNet.RegisterBuilding.RBuild;
|
||||
|
||||
namespace MewtocolNet.Registers {
|
||||
|
||||
@@ -67,7 +68,8 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
if (lastValue?.ToString() != val?.ToString()) {
|
||||
|
||||
lastValue = (T)val;
|
||||
if (val != null) lastValue = (T)val;
|
||||
else lastValue = null;
|
||||
|
||||
TriggerNotifyChange();
|
||||
attachedInterface.InvokeRegisterChanged(this);
|
||||
@@ -122,9 +124,6 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ClearValue() => SetValueFromPLC(default(T));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2);
|
||||
|
||||
@@ -144,6 +143,8 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
internal override object SetValueFromBytes(byte[] bytes) {
|
||||
|
||||
AddSuccessRead();
|
||||
|
||||
var parsed = PlcValueParser.Parse<T>(this, bytes);
|
||||
SetValueFromPLC(parsed);
|
||||
return parsed;
|
||||
@@ -159,6 +160,7 @@ namespace MewtocolNet.Registers {
|
||||
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
|
||||
|
||||
if (res) {
|
||||
AddSuccessWrite();
|
||||
SetValueFromPLC(data);
|
||||
}
|
||||
|
||||
@@ -166,29 +168,25 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the register bitwise if its a 16 or 32 bit int
|
||||
/// </summary>
|
||||
/// <returns>A bitarray</returns>
|
||||
public BitArray GetBitwise() {
|
||||
internal override async Task<bool> WriteToAnonymousAsync (object value) {
|
||||
|
||||
if (this is NumberRegister<short> shortReg) {
|
||||
if (!attachedInterface.IsConnected)
|
||||
throw MewtocolException.NotConnectedSend();
|
||||
|
||||
var bytes = BitConverter.GetBytes((short)Value);
|
||||
BitArray bitAr = new BitArray(bytes);
|
||||
return bitAr;
|
||||
var encoded = PlcValueParser.Encode(this, (T)value);
|
||||
return await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (this is NumberRegister<int> intReg) {
|
||||
internal override async Task<object> ReadFromAnonymousAsync () {
|
||||
|
||||
var bytes = BitConverter.GetBytes((int)Value);
|
||||
BitArray bitAr = new BitArray(bytes);
|
||||
return bitAr;
|
||||
if (!attachedInterface.IsConnected)
|
||||
throw MewtocolException.NotConnectedSend();
|
||||
|
||||
}
|
||||
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
|
||||
if (res == null) return null;
|
||||
|
||||
return null;
|
||||
return PlcValueParser.Parse<T>(this, res);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -69,9 +69,6 @@ namespace MewtocolNet.Registers {
|
||||
/// <inheritdoc/>
|
||||
public override string GetRegisterString() => "DT";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ClearValue() => SetValueFromPLC("");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize);
|
||||
|
||||
|
||||
18
MewtocolNet/SetupClasses/PollLevelConfig.cs
Normal file
18
MewtocolNet/SetupClasses/PollLevelConfig.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace MewtocolNet.SetupClasses {
|
||||
|
||||
internal class PollLevelConfig {
|
||||
|
||||
internal TimeSpan? delay;
|
||||
|
||||
internal int? skipNth;
|
||||
|
||||
internal Stopwatch timeFromLastRead;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,6 +47,20 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
public void UpdateAreaRegisterValues() {
|
||||
|
||||
foreach (var register in this.linkedRegisters) {
|
||||
|
||||
var regStart = register.MemoryAddress;
|
||||
var addLen = (int)register.GetRegisterAddressLen();
|
||||
|
||||
var bytes = this.GetUnderlyingBytes(regStart, addLen);
|
||||
register.SetValueFromBytes(bytes);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> ReadRegisterAsync (BaseRegister reg) {
|
||||
|
||||
return await RequestByteReadAsync(reg.MemoryAddress, reg.MemoryAddress + reg.GetRegisterAddressLen() - 1);
|
||||
@@ -125,6 +139,8 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
int copyOffset = (int)((addStart - addressStart) * 2);
|
||||
bytes.CopyTo(underlyingBytes, copyOffset);
|
||||
|
||||
UpdateAreaRegisterValues();
|
||||
|
||||
}
|
||||
|
||||
private string GetMewtocolIdent () {
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes);
|
||||
|
||||
void UpdateAreaRegisterValues();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolNet.SetupClasses;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -11,22 +13,16 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
internal int maxOptimizationDistance = 8;
|
||||
internal int maxRegistersPerGroup = -1;
|
||||
internal bool allowByteRegDupes;
|
||||
|
||||
private int wrAreaSize;
|
||||
private int dtAreaSize;
|
||||
|
||||
internal MewtocolInterface mewInterface;
|
||||
internal List<PollLevel> pollLevels;
|
||||
internal Dictionary<int, PollLevelConfig> pollLevelConfigs = new Dictionary<int, PollLevelConfig>();
|
||||
|
||||
// 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;
|
||||
private uint pollIteration = 0;
|
||||
|
||||
internal MemoryAreaManager (MewtocolInterface mewIf, int wrSize = 512, int dtSize = 32_765) {
|
||||
|
||||
@@ -38,22 +34,25 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
// 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);
|
||||
wrAreaSize = wrSize;
|
||||
dtAreaSize = dtSize;
|
||||
pollLevels = new List<PollLevel> {
|
||||
new PollLevel(wrSize, dtSize) {
|
||||
level = 1,
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
internal bool LinkRegister (BaseRegister reg) {
|
||||
|
||||
TestPollLevelExistence(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);
|
||||
return AddWRArea(reg);
|
||||
case RegisterType.DT:
|
||||
case RegisterType.DDT:
|
||||
case RegisterType.DT_BYTE_RANGE:
|
||||
@@ -64,7 +63,48 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
private bool AddWRArea (BaseRegister insertReg, List<WRArea> collection) {
|
||||
private void TestPollLevelExistence (BaseRegister reg) {
|
||||
|
||||
if(!pollLevelConfigs.ContainsKey(1)) {
|
||||
pollLevelConfigs.Add(1, new PollLevelConfig {
|
||||
skipNth = 1,
|
||||
});
|
||||
}
|
||||
|
||||
if(!pollLevels.Any(x => x.level == reg.pollLevel)) {
|
||||
|
||||
pollLevels.Add(new PollLevel(wrAreaSize, dtAreaSize) {
|
||||
level = reg.pollLevel,
|
||||
});
|
||||
|
||||
//add config if it was not made at setup
|
||||
if(!pollLevelConfigs.ContainsKey(reg.pollLevel)) {
|
||||
pollLevelConfigs.Add(reg.pollLevel, new PollLevelConfig {
|
||||
skipNth = reg.pollLevel,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool AddWRArea (BaseRegister insertReg) {
|
||||
|
||||
var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel);
|
||||
|
||||
List<WRArea> collection = null;
|
||||
|
||||
switch (insertReg.RegisterType) {
|
||||
case RegisterType.X:
|
||||
collection = pollLevelFound.externalRelayInAreas;
|
||||
break;
|
||||
case RegisterType.Y:
|
||||
collection = pollLevelFound.externalRelayOutAreas;
|
||||
break;
|
||||
case RegisterType.R:
|
||||
collection = pollLevelFound.internalRelayAreas;
|
||||
break;
|
||||
}
|
||||
|
||||
WRArea area = collection.FirstOrDefault(x => x.AddressStart == insertReg.MemoryAddress);
|
||||
|
||||
@@ -114,6 +154,9 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
DTArea targetArea = null;
|
||||
|
||||
var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel);
|
||||
var dataAreas = pollLevelFound.dataAreas;
|
||||
|
||||
foreach (var dtArea in dataAreas) {
|
||||
|
||||
bool matchingAddress = regInsAddStart >= dtArea.AddressStart &&
|
||||
@@ -123,9 +166,10 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
if (matchingAddress) {
|
||||
|
||||
//check if the area has registers linked that are overlapping (not matching)
|
||||
var foundDupe = dtArea.linkedRegisters.FirstOrDefault(x => x.CompareIsDuplicateNonCast(insertReg));
|
||||
var foundDupe = dtArea.linkedRegisters
|
||||
.FirstOrDefault(x => x.CompareIsDuplicateNonCast(insertReg, allowByteRegDupes));
|
||||
|
||||
if(foundDupe != null) {
|
||||
if (foundDupe != null) {
|
||||
throw new NotSupportedException(
|
||||
message: $"Can't have registers of different types at the same referenced plc address: " +
|
||||
$"{insertReg.PLCAddressName} ({insertReg.GetType()}) <=> " +
|
||||
@@ -211,34 +255,60 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
internal async Task PollAllAreasAsync () {
|
||||
|
||||
foreach (var dtArea in dataAreas) {
|
||||
foreach (var pollLevel in pollLevels) {
|
||||
|
||||
//set the whole memory area at once
|
||||
var res = await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
foreach (var register in dtArea.linkedRegisters) {
|
||||
//determine to skip poll levels, first iteration is always polled
|
||||
if(pollIteration > 0 && pollLevel.level > 1) {
|
||||
|
||||
var regStart = register.MemoryAddress;
|
||||
var addLen = (int)register.GetRegisterAddressLen();
|
||||
var lvlConfig = pollLevelConfigs[pollLevel.level];
|
||||
var skipIterations = lvlConfig.skipNth;
|
||||
var skipDelay = lvlConfig.delay;
|
||||
|
||||
var bytes = dtArea.GetUnderlyingBytes(regStart, addLen);
|
||||
register.SetValueFromBytes(bytes);
|
||||
if (skipIterations != null && pollIteration % skipIterations.Value != 0) {
|
||||
|
||||
//count delayed poll skips
|
||||
continue;
|
||||
|
||||
} else if(skipDelay != null) {
|
||||
|
||||
//time delayed poll skips
|
||||
|
||||
if(lvlConfig.timeFromLastRead.Elapsed < skipDelay.Value) {
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//set stopwatch for levels
|
||||
if(pollLevelConfigs.ContainsKey(pollLevel.level)) {
|
||||
|
||||
pollLevelConfigs[pollLevel.level].timeFromLastRead = Stopwatch.StartNew();
|
||||
|
||||
}
|
||||
|
||||
//update registers in poll level
|
||||
foreach (var dtArea in pollLevel.dataAreas) {
|
||||
|
||||
//set the whole memory area at once
|
||||
await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
|
||||
|
||||
}
|
||||
|
||||
pollLevel.lastReadTimeMs = (int)sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
if(pollIteration == uint.MaxValue) {
|
||||
pollIteration = uint.MinValue;
|
||||
} else {
|
||||
pollIteration++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -246,63 +316,77 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("---- DT Area ----");
|
||||
foreach (var pollLevel in pollLevels) {
|
||||
|
||||
sb.AppendLine($"Optimization distance: {maxOptimizationDistance}");
|
||||
|
||||
foreach (var area in dataAreas) {
|
||||
sb.AppendLine($"==== Poll lvl {pollLevel.level} ====");
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"=> {area} = {area.underlyingBytes.Length} bytes");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(string.Join("\n", area.underlyingBytes.ToHexString(" ").SplitInParts(3 * 8)));
|
||||
if (pollLevelConfigs[pollLevel.level].delay != null) {
|
||||
sb.AppendLine($"Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms");
|
||||
} else {
|
||||
sb.AppendLine($"Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations");
|
||||
}
|
||||
sb.AppendLine($"Level read time: {pollLevel.lastReadTimeMs}ms");
|
||||
sb.AppendLine($"Optimization distance: {maxOptimizationDistance}");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
sb.AppendLine($"---- DT Area ----");
|
||||
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
foreach (var area in pollLevel.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 ----");
|
||||
|
||||
sb.AppendLine("---- WR X Area ----");
|
||||
foreach (var area in pollLevel.externalRelayInAreas) {
|
||||
|
||||
foreach (var area in externalRelayInAreas) {
|
||||
sb.AppendLine(area.ToString());
|
||||
|
||||
sb.AppendLine(area.ToString());
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
sb.AppendLine($"---- WR Y Area ---");
|
||||
|
||||
sb.AppendLine("---- WR Y Area ----");
|
||||
foreach (var area in pollLevel.externalRelayOutAreas) {
|
||||
|
||||
foreach (var area in externalRelayOutAreas) {
|
||||
sb.AppendLine(area.ToString());
|
||||
|
||||
sb.AppendLine(area.ToString());
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
sb.AppendLine($"---- WR R Area ----");
|
||||
|
||||
sb.AppendLine("---- WR R Area ----");
|
||||
foreach (var area in pollLevel.internalRelayAreas) {
|
||||
|
||||
foreach (var area in internalRelayAreas) {
|
||||
sb.AppendLine(area.ToString());
|
||||
|
||||
sb.AppendLine(area.ToString());
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
|
||||
foreach (var reg in area.linkedRegisters) {
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
|
||||
sb.AppendLine($"{reg.ToString(true)}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -312,6 +396,22 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
internal IEnumerable<BaseRegister> GetAllRegisters () {
|
||||
|
||||
List<BaseRegister> registers = new List<BaseRegister>();
|
||||
|
||||
foreach (var lvl in pollLevels) {
|
||||
|
||||
registers.AddRange(lvl.dataAreas.SelectMany(x => x.linkedRegisters));
|
||||
registers.AddRange(lvl.internalRelayAreas.SelectMany(x => x.linkedRegisters));
|
||||
registers.AddRange(lvl.externalRelayInAreas.SelectMany(x => x.linkedRegisters));
|
||||
registers.AddRange(lvl.externalRelayOutAreas.SelectMany(x => x.linkedRegisters));
|
||||
|
||||
}
|
||||
|
||||
return registers;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
36
MewtocolNet/UnderlyingRegisters/PollLevel.cs
Normal file
36
MewtocolNet/UnderlyingRegisters/PollLevel.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
internal class PollLevel {
|
||||
|
||||
internal int lastReadTimeMs = 0;
|
||||
|
||||
internal PollLevel (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 int level;
|
||||
|
||||
// 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,12 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
mewInterface = mewIf;
|
||||
|
||||
}
|
||||
|
||||
public void UpdateAreaRegisterValues () {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public byte[] GetUnderlyingBytes(BaseRegister reg) {
|
||||
|
||||
Reference in New Issue
Block a user