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.SerializationInfo info,
|
||||||
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
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) {
|
internal static MewtocolException DupeRegister (IRegisterInternal register) {
|
||||||
|
|
||||||
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}");
|
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) {
|
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()}");
|
$"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace MewtocolNet {
|
|||||||
if (_onString == null)
|
if (_onString == null)
|
||||||
return 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) {
|
if (res.Success) {
|
||||||
string val = res.Groups[2].Value;
|
string val = res.Groups[2].Value;
|
||||||
return val;
|
return val;
|
||||||
@@ -75,7 +75,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
_onString = _onString.Replace("\r", "");
|
_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) {
|
if (res.Success) {
|
||||||
string val = res.Groups[2].Value;
|
string val = res.Groups[2].Value;
|
||||||
return val == "1";
|
return val == "1";
|
||||||
@@ -91,7 +91,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
_onString = _onString.Replace("\r", "");
|
_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) {
|
if (res.Success) {
|
||||||
|
|
||||||
string val = res.Groups["bits"].Value;
|
string val = res.Groups["bits"].Value;
|
||||||
@@ -121,7 +121,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
_onString = _onString.Replace("\r", "");
|
_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) {
|
if (res.Success) {
|
||||||
|
|
||||||
string val = res.Groups["data"].Value;
|
string val = res.Groups["data"].Value;
|
||||||
@@ -245,16 +245,20 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
bool valCompare = reg1.RegisterType == compare.RegisterType &&
|
bool valCompare = reg1.RegisterType == compare.RegisterType &&
|
||||||
reg1.MemoryAddress == compare.MemoryAddress &&
|
reg1.MemoryAddress == compare.MemoryAddress &&
|
||||||
|
reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
|
||||||
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
|
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
|
||||||
|
|
||||||
return valCompare;
|
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() &&
|
bool valCompare = reg1.GetType() != compare.GetType() &&
|
||||||
reg1.MemoryAddress == compare.MemoryAddress &&
|
reg1.MemoryAddress == compare.MemoryAddress &&
|
||||||
|
reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
|
||||||
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
|
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
|
||||||
|
|
||||||
return valCompare;
|
return valCompare;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using MewtocolNet.Registers;
|
using MewtocolNet.RegisterBuilding;
|
||||||
|
using MewtocolNet.Registers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MewtocolNet {
|
namespace MewtocolNet {
|
||||||
@@ -8,7 +10,7 @@ namespace MewtocolNet {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a interface for Panasonic PLCs
|
/// Provides a interface for Panasonic PLCs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPlc : IDisposable {
|
public interface IPlc : IDisposable, INotifyPropertyChanged {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current connection state of the interface
|
/// The current connection state of the interface
|
||||||
@@ -35,11 +37,6 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
int QueuedMessages { get; }
|
int QueuedMessages { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The registered data registers of the PLC
|
|
||||||
/// </summary>
|
|
||||||
IEnumerable<IRegister> Registers { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic information about the connected PLC
|
/// Generic information about the connected PLC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -60,13 +57,24 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
int ConnectTimeout { get; set; }
|
int ConnectTimeout { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an anonymous interface for register reading and writing without memory management
|
||||||
|
/// </summary>
|
||||||
|
RBuild Register { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to establish a connection with the device asynchronously
|
/// Tries to establish a connection with the device asynchronously
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task ConnectAsync();
|
Task ConnectAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
void Disconnect();
|
void Disconnect();
|
||||||
|
|
||||||
@@ -97,23 +105,13 @@ namespace MewtocolNet {
|
|||||||
/// useful if you want to use a custom update frequency
|
/// useful if you want to use a custom update frequency
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The number of inidvidual mewtocol commands sent</returns>
|
/// <returns>The number of inidvidual mewtocol commands sent</returns>
|
||||||
Task<int> RunPollerCylceManual();
|
Task<int> RunPollerCylceManualAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the connection info string
|
/// Gets the connection info string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string GetConnectionInfo();
|
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>
|
/// <summary>
|
||||||
/// Gets a register from the plc by name
|
/// Gets a register from the plc by name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using MewtocolNet.Exceptions;
|
using MewtocolNet.Exceptions;
|
||||||
using MewtocolNet.RegisterAttributes;
|
using MewtocolNet.RegisterAttributes;
|
||||||
|
using MewtocolNet.SetupClasses;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@@ -15,14 +16,16 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class Mewtocol {
|
public static class Mewtocol {
|
||||||
|
|
||||||
|
#region Build Order 1
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a ethernet based Mewtocol Interface
|
/// Builds a ethernet based Mewtocol Interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ip"></param>
|
/// <param name="ip"></param>
|
||||||
/// <param name="port"></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>
|
/// <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();
|
var instance = new MewtocolInterfaceTcp();
|
||||||
instance.ConfigureConnection(ip, port, station);
|
instance.ConfigureConnection(ip, port, station);
|
||||||
@@ -37,9 +40,9 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ip"></param>
|
/// <param name="ip"></param>
|
||||||
/// <param name="port"></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>
|
/// <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();
|
var instance = new MewtocolInterfaceTcp();
|
||||||
instance.ConfigureConnection(ip, port, station);
|
instance.ConfigureConnection(ip, port, station);
|
||||||
@@ -52,14 +55,14 @@ namespace MewtocolNet {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a serial port based Mewtocol Interface
|
/// Builds a serial port based Mewtocol Interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="portName"></param>
|
/// <param name="portName">System port name</param>
|
||||||
/// <param name="baudRate"></param>
|
/// <param name="baudRate">Baud rate of the plc toolport</param>
|
||||||
/// <param name="dataBits"></param>
|
/// <param name="dataBits">DataBits of the plc toolport</param>
|
||||||
/// <param name="parity"></param>
|
/// <param name="parity">Parity rate of the plc toolport</param>
|
||||||
/// <param name="stopBits"></param>
|
/// <param name="stopBits">Stop bits of the plc toolport</param>
|
||||||
/// <param name="station"></param>
|
/// <param name="station">Plc station number 0xEE for direct communication</param>
|
||||||
/// <returns></returns>
|
/// <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);
|
TestPortName(portName);
|
||||||
|
|
||||||
@@ -75,9 +78,9 @@ namespace MewtocolNet {
|
|||||||
/// Builds a serial mewtocol interface that finds the correct settings for the given port name automatically
|
/// Builds a serial mewtocol interface that finds the correct settings for the given port name automatically
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="portName"></param>
|
/// <param name="portName"></param>
|
||||||
/// <param name="station"></param>
|
/// <param name="station">Plc station number 0xEE for direct communication</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static PostInit<IPlcSerial> SerialAuto (string portName, int station = 1) {
|
public static PostInit<IPlcSerial> SerialAuto (string portName, int station = 0xEE) {
|
||||||
|
|
||||||
TestPortName(portName);
|
TestPortName(portName);
|
||||||
|
|
||||||
@@ -99,6 +102,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Build Order 2
|
||||||
|
|
||||||
public class MemoryManagerSettings {
|
public class MemoryManagerSettings {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -125,6 +132,80 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxRegistersPerGroup { get; set; } = -1;
|
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> {
|
public class PostInit<T> {
|
||||||
@@ -162,6 +243,25 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance;
|
imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance;
|
||||||
imew.memoryManager.maxRegistersPerGroup = res.MaxRegistersPerGroup;
|
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>
|
/// <summary>
|
||||||
/// A builder for attaching register collections
|
/// A builder for attaching register collections
|
||||||
/// </summary>
|
/// </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);
|
collector.Invoke(res);
|
||||||
|
|
||||||
if (intf is MewtocolInterface imew) {
|
if (intf is MewtocolInterface imew) {
|
||||||
@@ -194,6 +294,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region BuildLevel 3
|
||||||
|
|
||||||
public class EndInit<T> {
|
public class EndInit<T> {
|
||||||
|
|
||||||
internal PostInit<T> postInit;
|
internal PostInit<T> postInit;
|
||||||
@@ -205,6 +309,8 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ using MewtocolNet.UnderlyingRegisters;
|
|||||||
|
|
||||||
namespace MewtocolNet {
|
namespace MewtocolNet {
|
||||||
|
|
||||||
public abstract partial class MewtocolInterface : IPlc, INotifyPropertyChanged, IDisposable {
|
public abstract partial class MewtocolInterface : IPlc {
|
||||||
|
|
||||||
#region Private fields
|
#region Private fields
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ namespace MewtocolNet {
|
|||||||
private PLCInfo plcInfo;
|
private PLCInfo plcInfo;
|
||||||
private protected int stationNumber;
|
private protected int stationNumber;
|
||||||
|
|
||||||
|
private protected int RecBufferSize = 128;
|
||||||
private protected int bytesTotalCountedUpstream = 0;
|
private protected int bytesTotalCountedUpstream = 0;
|
||||||
private protected int bytesTotalCountedDownstream = 0;
|
private protected int bytesTotalCountedDownstream = 0;
|
||||||
private protected int cycleTimeMs = 25;
|
private protected int cycleTimeMs = 25;
|
||||||
@@ -35,7 +36,6 @@ namespace MewtocolNet {
|
|||||||
private protected int bytesPerSecondDownstream = 0;
|
private protected int bytesPerSecondDownstream = 0;
|
||||||
|
|
||||||
private protected AsyncQueue queue = new AsyncQueue();
|
private protected AsyncQueue queue = new AsyncQueue();
|
||||||
private protected int RecBufferSize = 128;
|
|
||||||
private protected Stopwatch speedStopwatchUpstr;
|
private protected Stopwatch speedStopwatchUpstr;
|
||||||
private protected Stopwatch speedStopwatchDownstr;
|
private protected Stopwatch speedStopwatchDownstr;
|
||||||
private protected Task firstPollTask = new Task(() => { });
|
private protected Task firstPollTask = new Task(() => { });
|
||||||
@@ -52,9 +52,6 @@ namespace MewtocolNet {
|
|||||||
internal bool usePoller = false;
|
internal bool usePoller = false;
|
||||||
internal MemoryAreaManager memoryManager;
|
internal MemoryAreaManager memoryManager;
|
||||||
|
|
||||||
internal List<BaseRegister> RegistersUnderlying { get; private set; } = new List<BaseRegister>();
|
|
||||||
internal IEnumerable<IRegisterInternal> RegistersInternal => RegistersUnderlying.Cast<IRegisterInternal>();
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Public Read Only Properties / Fields
|
#region Public Read Only Properties / Fields
|
||||||
@@ -95,9 +92,6 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public IEnumerable<IRegister> Registers => RegistersUnderlying.Cast<IRegister>();
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int StationNumber => stationNumber;
|
public int StationNumber => stationNumber;
|
||||||
|
|
||||||
@@ -173,18 +167,26 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async Task ConnectAsync() => throw new NotImplementedException();
|
public virtual Task ConnectAsync() => throw new NotImplementedException();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task AwaitFirstDataCycleAsync() => await firstPollTask;
|
public async Task AwaitFirstDataCycleAsync() => await firstPollTask;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task DisconnectAsync () {
|
||||||
|
|
||||||
|
await pollCycleTask;
|
||||||
|
|
||||||
|
Disconnect();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Disconnect() {
|
public void Disconnect() {
|
||||||
|
|
||||||
if (!IsConnected) return;
|
if (!IsConnected) return;
|
||||||
|
|
||||||
if(pollCycleTask != null && !pollCycleTask.IsCompleted)
|
if (!pollCycleTask.IsCompleted) pollCycleTask.Wait();
|
||||||
pollCycleTask.Wait();
|
|
||||||
|
|
||||||
OnMajorSocketExceptionWhileConnected();
|
OnMajorSocketExceptionWhileConnected();
|
||||||
|
|
||||||
@@ -194,7 +196,9 @@ namespace MewtocolNet {
|
|||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
|
|
||||||
if (Disposed) return;
|
if (Disposed) return;
|
||||||
|
|
||||||
Disconnect();
|
Disconnect();
|
||||||
|
|
||||||
//GC.SuppressFinalize(this);
|
//GC.SuppressFinalize(this);
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
|
|
||||||
@@ -329,7 +333,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
SetDownstreamStopWatchStart();
|
SetDownstreamStopWatchStart();
|
||||||
|
|
||||||
byte[] buffer = new byte[128];
|
byte[] buffer = new byte[RecBufferSize];
|
||||||
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
|
||||||
CalcDownstreamSpeed(bytesRead);
|
CalcDownstreamSpeed(bytesRead);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using MewtocolNet.Logging;
|
|||||||
using MewtocolNet.RegisterAttributes;
|
using MewtocolNet.RegisterAttributes;
|
||||||
using MewtocolNet.RegisterBuilding;
|
using MewtocolNet.RegisterBuilding;
|
||||||
using MewtocolNet.Registers;
|
using MewtocolNet.Registers;
|
||||||
|
using MewtocolNet.SetupClasses;
|
||||||
using MewtocolNet.UnderlyingRegisters;
|
using MewtocolNet.UnderlyingRegisters;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
@@ -24,14 +25,18 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
internal Task pollCycleTask;
|
internal Task pollCycleTask;
|
||||||
|
|
||||||
|
private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
|
||||||
|
|
||||||
|
internal IEnumerable<BaseRegister> RegistersInternal => GetAllRegistersInternal();
|
||||||
|
|
||||||
|
public IEnumerable<IRegister> Registers => GetAllRegisters();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the poller is actvice (can be paused)
|
/// True if the poller is actvice (can be paused)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool PollerActive => !pollerTaskStopped;
|
public bool PollerActive => !pollerTaskStopped;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Current poller cycle duration
|
|
||||||
/// </summary>
|
|
||||||
public int PollerCycleDurationMs {
|
public int PollerCycleDurationMs {
|
||||||
get => pollerCycleDurationMs;
|
get => pollerCycleDurationMs;
|
||||||
private set {
|
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
|
#region Register Polling
|
||||||
|
|
||||||
@@ -74,11 +80,11 @@ namespace MewtocolNet {
|
|||||||
/// useful if you want to use a custom update frequency
|
/// useful if you want to use a custom update frequency
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The number of inidvidual mewtocol commands sent</returns>
|
/// <returns>The number of inidvidual mewtocol commands sent</returns>
|
||||||
public async Task<int> RunPollerCylceManual() {
|
public async Task<int> RunPollerCylceManualAsync() {
|
||||||
|
|
||||||
if (!pollerTaskStopped)
|
if (!pollerTaskStopped)
|
||||||
throw new NotSupportedException($"The poller is already running, " +
|
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;
|
tcpMessagesSentThisCycle = 0;
|
||||||
|
|
||||||
@@ -139,69 +145,46 @@ namespace MewtocolNet {
|
|||||||
private async Task UpdateRCPRegisters() {
|
private async Task UpdateRCPRegisters() {
|
||||||
|
|
||||||
//build booleans
|
//build booleans
|
||||||
var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister))
|
//var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister))
|
||||||
.Select(x => (BoolRegister)x)
|
// .Select(x => (BoolRegister)x)
|
||||||
.ToArray();
|
// .ToArray();
|
||||||
|
|
||||||
//one frame can only read 8 registers at a time
|
////one frame can only read 8 registers at a time
|
||||||
int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8);
|
//int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8);
|
||||||
int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : 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];
|
// BoolRegister register = rcpList[i + j];
|
||||||
rcpString.Append(register.BuildMewtocolQuery());
|
// rcpString.Append(register.BuildMewtocolQuery());
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
string rcpRequest = rcpString.ToString();
|
// string rcpRequest = rcpString.ToString();
|
||||||
var result = await SendCommandAsync(rcpRequest);
|
// var result = await SendCommandAsync(rcpRequest);
|
||||||
if (!result.Success) return;
|
// 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]) {
|
// if ((bool)register.Value != resultBitArray[k]) {
|
||||||
register.SetValueFromPLC(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)
|
if (registerCollections.Count != 0)
|
||||||
throw new NotSupportedException("Register collections can only be build once");
|
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) {
|
foreach (var collection in collections) {
|
||||||
|
|
||||||
@@ -234,18 +217,24 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
if (attr is RegisterAttribute cAttribute) {
|
if (attr is RegisterAttribute cAttribute) {
|
||||||
|
|
||||||
|
var pollFreqAttr = (PollLevelAttribute)attributes.FirstOrDefault(x => x.GetType() == typeof(PollLevelAttribute));
|
||||||
|
|
||||||
if (!prop.PropertyType.IsAllowedPlcCastingType()) {
|
if (!prop.PropertyType.IsAllowedPlcCastingType()) {
|
||||||
throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})");
|
throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})");
|
||||||
}
|
}
|
||||||
|
|
||||||
var dotnetType = prop.PropertyType;
|
var dotnetType = prop.PropertyType;
|
||||||
|
int pollLevel = 1;
|
||||||
|
|
||||||
buildInfos.Add(new RegisterBuildInfo {
|
if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel;
|
||||||
mewAddress = cAttribute.MewAddress,
|
|
||||||
dotnetCastType = dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType,
|
//add builder item
|
||||||
collectionTarget = collection,
|
regBuild
|
||||||
boundPropTarget = prop,
|
.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
|
#region Register Adding
|
||||||
|
|
||||||
/// <inheritdoc/>
|
internal void AddRegisters (params BaseRegister[] registers) {
|
||||||
public void AddRegister (IRegister register) => AddRegister(register as BaseRegister);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
InsertRegistersToMemoryStack(registers.ToList());
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for internal property based register building
|
internal void InsertRegistersToMemoryStack (List<BaseRegister> registers) {
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//order by address
|
//order by address
|
||||||
registers = registers.OrderBy(x => x.GetSpecialAddress()).ToList();
|
registers = registers.OrderBy(x => x.GetSpecialAddress()).ToList();
|
||||||
@@ -353,12 +301,7 @@ namespace MewtocolNet {
|
|||||||
reg.name = $"auto_prop_register_{j + 1}";
|
reg.name = $"auto_prop_register_{j + 1}";
|
||||||
|
|
||||||
//link the memory area to the register
|
//link the memory area to the register
|
||||||
if (memoryManager.LinkRegister(reg)) {
|
if (memoryManager.LinkRegister(reg)) j++;
|
||||||
|
|
||||||
RegistersUnderlying.Add(reg);
|
|
||||||
j++;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,16 +366,22 @@ namespace MewtocolNet {
|
|||||||
#region Register accessing
|
#region Register accessing
|
||||||
|
|
||||||
/// <inheritdoc/>>
|
/// <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/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<IRegister> GetAllRegisters () {
|
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() {
|
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();
|
reg.ClearValue();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -453,7 +404,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
internal void PropertyRegisterWasSet(string propName, object value) {
|
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
|
#region Raw register reading / writing
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
internal async Task<byte[]> ReadRawRegisterAsync (IRegisterInternal _toRead) {
|
internal async Task<byte[]> ReadRawRegisterAsync (IRegisterInternal _toRead) {
|
||||||
|
|
||||||
var toreadType = _toRead.GetType();
|
var toreadType = _toRead.GetType();
|
||||||
@@ -237,6 +238,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
internal async Task<bool> WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) {
|
internal async Task<bool> WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) {
|
||||||
|
|
||||||
var toWriteType = _toWrite.GetType();
|
var toWriteType = _toWrite.GetType();
|
||||||
@@ -254,36 +256,6 @@ namespace MewtocolNet {
|
|||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
return result.Success;
|
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
|
#endregion
|
||||||
@@ -292,8 +264,12 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
internal string GetStationNumber() {
|
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/>
|
/// <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;
|
PortName = _portName;
|
||||||
SerialBaudRate = _baudRate;
|
SerialBaudRate = _baudRate;
|
||||||
@@ -90,6 +90,9 @@ namespace MewtocolNet {
|
|||||||
SerialStopBits = _stopBits;
|
SerialStopBits = _stopBits;
|
||||||
stationNumber = _station;
|
stationNumber = _station;
|
||||||
|
|
||||||
|
if (stationNumber != 0xEE && stationNumber > 99)
|
||||||
|
throw new NotSupportedException("Station number can't be greater than 99");
|
||||||
|
|
||||||
OnSerialPropsChanged();
|
OnSerialPropsChanged();
|
||||||
Disconnect();
|
Disconnect();
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,14 @@ namespace MewtocolNet {
|
|||||||
#region TCP connection state handling
|
#region TCP connection state handling
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <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))
|
if (!IPAddress.TryParse(ip, out ipAddr))
|
||||||
throw new MewtocolException($"The ip: {ip} is no valid ip address");
|
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;
|
Port = port;
|
||||||
stationNumber = station;
|
stationNumber = station;
|
||||||
|
|
||||||
@@ -45,10 +48,14 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <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;
|
ipAddr = ip;
|
||||||
Port = port;
|
Port = port;
|
||||||
|
|
||||||
|
if (stationNumber != 0xEE && stationNumber > 99)
|
||||||
|
throw new NotSupportedException("Station number can't be greater than 99");
|
||||||
|
|
||||||
stationNumber = station;
|
stationNumber = station;
|
||||||
|
|
||||||
Disconnect();
|
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 object lastValue = null;
|
||||||
internal string name;
|
internal string name;
|
||||||
internal uint memoryAddress;
|
internal uint memoryAddress;
|
||||||
|
internal int pollLevel = 0;
|
||||||
|
|
||||||
|
internal uint successfulReads = 0;
|
||||||
|
internal uint successfulWrites = 0;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public RegisterCollection ContainedCollection => containedCollection;
|
public RegisterCollection ContainedCollection => containedCollection;
|
||||||
@@ -82,6 +86,10 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
public virtual Task<bool> WriteAsync(object data) => throw new NotImplementedException();
|
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
|
#endregion
|
||||||
|
|
||||||
#region Default accessors
|
#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() {
|
public override string ToString() {
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
@@ -148,9 +166,10 @@ namespace MewtocolNet.Registers {
|
|||||||
sb.AppendLine($"MewName: {GetMewName()}");
|
sb.AppendLine($"MewName: {GetMewName()}");
|
||||||
sb.AppendLine($"Name: {Name ?? "Not named"}");
|
sb.AppendLine($"Name: {Name ?? "Not named"}");
|
||||||
sb.AppendLine($"Value: {GetValueString()}");
|
sb.AppendLine($"Value: {GetValueString()}");
|
||||||
|
sb.AppendLine($"Perf. Reads: {successfulReads}, Writes: {successfulWrites}");
|
||||||
sb.AppendLine($"Register Type: {RegisterType}");
|
sb.AppendLine($"Register Type: {RegisterType}");
|
||||||
sb.AppendLine($"Address: {GetRegisterWordRangeString()}");
|
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]}>");
|
if (GetType().IsGenericType) sb.AppendLine($"Type: NumberRegister<{GetType().GenericTypeArguments[0]}>");
|
||||||
else sb.AppendLine($"Type: {GetType()}");
|
else sb.AppendLine($"Type: {GetType()}");
|
||||||
if(containedCollection != null) sb.AppendLine($"In collection: {containedCollection.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/>
|
/// <inheritdoc/>
|
||||||
public override string GetMewName() {
|
public override string GetMewName() {
|
||||||
|
|
||||||
|
|||||||
@@ -117,9 +117,6 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string GetRegisterString() => "DT";
|
public override string GetRegisterString() => "DT";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void ClearValue() => SetValueFromPLC(null);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override uint GetRegisterAddressLen() => AddressLength;
|
public override uint GetRegisterAddressLen() => AddressLength;
|
||||||
|
|
||||||
@@ -139,6 +136,8 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
internal override object SetValueFromBytes(byte[] bytes) {
|
internal override object SetValueFromBytes(byte[] bytes) {
|
||||||
|
|
||||||
|
AddSuccessRead();
|
||||||
|
|
||||||
object parsed;
|
object parsed;
|
||||||
if (ReservedBitSize != null) {
|
if (ReservedBitSize != null) {
|
||||||
parsed = PlcValueParser.Parse<BitArray>(this, bytes);
|
parsed = PlcValueParser.Parse<BitArray>(this, bytes);
|
||||||
@@ -166,6 +165,7 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
|
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
|
||||||
if (res) {
|
if (res) {
|
||||||
|
AddSuccessWrite();
|
||||||
SetValueFromPLC(data);
|
SetValueFromPLC(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.ComponentModel;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static MewtocolNet.RegisterBuilding.RBuild;
|
||||||
|
|
||||||
namespace MewtocolNet.Registers {
|
namespace MewtocolNet.Registers {
|
||||||
|
|
||||||
@@ -67,7 +68,8 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
if (lastValue?.ToString() != val?.ToString()) {
|
if (lastValue?.ToString() != val?.ToString()) {
|
||||||
|
|
||||||
lastValue = (T)val;
|
if (val != null) lastValue = (T)val;
|
||||||
|
else lastValue = null;
|
||||||
|
|
||||||
TriggerNotifyChange();
|
TriggerNotifyChange();
|
||||||
attachedInterface.InvokeRegisterChanged(this);
|
attachedInterface.InvokeRegisterChanged(this);
|
||||||
@@ -122,9 +124,6 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void ClearValue() => SetValueFromPLC(default(T));
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2);
|
public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2);
|
||||||
|
|
||||||
@@ -144,6 +143,8 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
internal override object SetValueFromBytes(byte[] bytes) {
|
internal override object SetValueFromBytes(byte[] bytes) {
|
||||||
|
|
||||||
|
AddSuccessRead();
|
||||||
|
|
||||||
var parsed = PlcValueParser.Parse<T>(this, bytes);
|
var parsed = PlcValueParser.Parse<T>(this, bytes);
|
||||||
SetValueFromPLC(parsed);
|
SetValueFromPLC(parsed);
|
||||||
return parsed;
|
return parsed;
|
||||||
@@ -159,6 +160,7 @@ namespace MewtocolNet.Registers {
|
|||||||
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
|
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
|
AddSuccessWrite();
|
||||||
SetValueFromPLC(data);
|
SetValueFromPLC(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,29 +168,25 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal override async Task<bool> WriteToAnonymousAsync (object value) {
|
||||||
/// Gets the register bitwise if its a 16 or 32 bit int
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A bitarray</returns>
|
|
||||||
public BitArray GetBitwise() {
|
|
||||||
|
|
||||||
if (this is NumberRegister<short> shortReg) {
|
if (!attachedInterface.IsConnected)
|
||||||
|
throw MewtocolException.NotConnectedSend();
|
||||||
|
|
||||||
var bytes = BitConverter.GetBytes((short)Value);
|
var encoded = PlcValueParser.Encode(this, (T)value);
|
||||||
BitArray bitAr = new BitArray(bytes);
|
return await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
|
||||||
return bitAr;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is NumberRegister<int> intReg) {
|
internal override async Task<object> ReadFromAnonymousAsync () {
|
||||||
|
|
||||||
var bytes = BitConverter.GetBytes((int)Value);
|
if (!attachedInterface.IsConnected)
|
||||||
BitArray bitAr = new BitArray(bytes);
|
throw MewtocolException.NotConnectedSend();
|
||||||
return bitAr;
|
|
||||||
|
|
||||||
}
|
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/>
|
/// <inheritdoc/>
|
||||||
public override string GetRegisterString() => "DT";
|
public override string GetRegisterString() => "DT";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void ClearValue() => SetValueFromPLC("");
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize);
|
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) {
|
public async Task<bool> ReadRegisterAsync (BaseRegister reg) {
|
||||||
|
|
||||||
return await RequestByteReadAsync(reg.MemoryAddress, reg.MemoryAddress + reg.GetRegisterAddressLen() - 1);
|
return await RequestByteReadAsync(reg.MemoryAddress, reg.MemoryAddress + reg.GetRegisterAddressLen() - 1);
|
||||||
@@ -125,6 +139,8 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
int copyOffset = (int)((addStart - addressStart) * 2);
|
int copyOffset = (int)((addStart - addressStart) * 2);
|
||||||
bytes.CopyTo(underlyingBytes, copyOffset);
|
bytes.CopyTo(underlyingBytes, copyOffset);
|
||||||
|
|
||||||
|
UpdateAreaRegisterValues();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetMewtocolIdent () {
|
private string GetMewtocolIdent () {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes);
|
Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes);
|
||||||
|
|
||||||
|
void UpdateAreaRegisterValues();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using MewtocolNet.Registers;
|
using MewtocolNet.Registers;
|
||||||
|
using MewtocolNet.SetupClasses;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -11,22 +13,16 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
internal int maxOptimizationDistance = 8;
|
internal int maxOptimizationDistance = 8;
|
||||||
internal int maxRegistersPerGroup = -1;
|
internal int maxRegistersPerGroup = -1;
|
||||||
|
internal bool allowByteRegDupes;
|
||||||
|
|
||||||
|
private int wrAreaSize;
|
||||||
|
private int dtAreaSize;
|
||||||
|
|
||||||
internal MewtocolInterface mewInterface;
|
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"
|
private uint pollIteration = 0;
|
||||||
|
|
||||||
//X WR
|
|
||||||
internal List<WRArea> externalRelayInAreas;
|
|
||||||
|
|
||||||
//Y WR
|
|
||||||
internal List<WRArea> externalRelayOutAreas;
|
|
||||||
|
|
||||||
//R WR
|
|
||||||
internal List<WRArea> internalRelayAreas;
|
|
||||||
|
|
||||||
//DT
|
|
||||||
internal List<DTArea> dataAreas;
|
|
||||||
|
|
||||||
internal MemoryAreaManager (MewtocolInterface mewIf, int wrSize = 512, int dtSize = 32_765) {
|
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
|
// Later on pass memory area sizes here
|
||||||
internal void Setup (int wrSize, int dtSize) {
|
internal void Setup (int wrSize, int dtSize) {
|
||||||
|
|
||||||
externalRelayInAreas = new List<WRArea>(wrSize * 16);
|
wrAreaSize = wrSize;
|
||||||
externalRelayOutAreas = new List<WRArea>(wrSize * 16);
|
dtAreaSize = dtSize;
|
||||||
internalRelayAreas = new List<WRArea>(wrSize * 16);
|
pollLevels = new List<PollLevel> {
|
||||||
dataAreas = new List<DTArea>(dtSize);
|
new PollLevel(wrSize, dtSize) {
|
||||||
|
level = 1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool LinkRegister (BaseRegister reg) {
|
internal bool LinkRegister (BaseRegister reg) {
|
||||||
|
|
||||||
|
TestPollLevelExistence(reg);
|
||||||
|
|
||||||
switch (reg.RegisterType) {
|
switch (reg.RegisterType) {
|
||||||
case RegisterType.X:
|
case RegisterType.X:
|
||||||
return AddWRArea(reg, externalRelayInAreas);
|
|
||||||
case RegisterType.Y:
|
case RegisterType.Y:
|
||||||
return AddWRArea(reg, externalRelayOutAreas);
|
|
||||||
case RegisterType.R:
|
case RegisterType.R:
|
||||||
return AddWRArea(reg, internalRelayAreas);
|
return AddWRArea(reg);
|
||||||
case RegisterType.DT:
|
case RegisterType.DT:
|
||||||
case RegisterType.DDT:
|
case RegisterType.DDT:
|
||||||
case RegisterType.DT_BYTE_RANGE:
|
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);
|
WRArea area = collection.FirstOrDefault(x => x.AddressStart == insertReg.MemoryAddress);
|
||||||
|
|
||||||
@@ -114,6 +154,9 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
DTArea targetArea = null;
|
DTArea targetArea = null;
|
||||||
|
|
||||||
|
var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel);
|
||||||
|
var dataAreas = pollLevelFound.dataAreas;
|
||||||
|
|
||||||
foreach (var dtArea in dataAreas) {
|
foreach (var dtArea in dataAreas) {
|
||||||
|
|
||||||
bool matchingAddress = regInsAddStart >= dtArea.AddressStart &&
|
bool matchingAddress = regInsAddStart >= dtArea.AddressStart &&
|
||||||
@@ -123,9 +166,10 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
if (matchingAddress) {
|
if (matchingAddress) {
|
||||||
|
|
||||||
//check if the area has registers linked that are overlapping (not matching)
|
//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(
|
throw new NotSupportedException(
|
||||||
message: $"Can't have registers of different types at the same referenced plc address: " +
|
message: $"Can't have registers of different types at the same referenced plc address: " +
|
||||||
$"{insertReg.PLCAddressName} ({insertReg.GetType()}) <=> " +
|
$"{insertReg.PLCAddressName} ({insertReg.GetType()}) <=> " +
|
||||||
@@ -211,34 +255,60 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
internal async Task PollAllAreasAsync () {
|
internal async Task PollAllAreasAsync () {
|
||||||
|
|
||||||
foreach (var dtArea in dataAreas) {
|
foreach (var pollLevel in pollLevels) {
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
//determine to skip poll levels, first iteration is always polled
|
||||||
|
if(pollIteration > 0 && pollLevel.level > 1) {
|
||||||
|
|
||||||
|
var lvlConfig = pollLevelConfigs[pollLevel.level];
|
||||||
|
var skipIterations = lvlConfig.skipNth;
|
||||||
|
var skipDelay = lvlConfig.delay;
|
||||||
|
|
||||||
|
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
|
//set the whole memory area at once
|
||||||
var res = await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
|
await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
|
||||||
|
|
||||||
foreach (var register in dtArea.linkedRegisters) {
|
|
||||||
|
|
||||||
var regStart = register.MemoryAddress;
|
|
||||||
var addLen = (int)register.GetRegisterAddressLen();
|
|
||||||
|
|
||||||
var bytes = dtArea.GetUnderlyingBytes(regStart, addLen);
|
|
||||||
register.SetValueFromBytes(bytes);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
pollLevel.lastReadTimeMs = (int)sw.Elapsed.TotalMilliseconds;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Merge () {
|
if(pollIteration == uint.MaxValue) {
|
||||||
|
pollIteration = uint.MinValue;
|
||||||
//merge gaps that the algorithm didn't catch be rerunning the register attachment
|
} else {
|
||||||
|
pollIteration++;
|
||||||
var allDataAreaRegisters = dataAreas.SelectMany(x => x.linkedRegisters).ToList();
|
}
|
||||||
dataAreas = new List<DTArea>(allDataAreaRegisters.Capacity);
|
|
||||||
|
|
||||||
foreach (var reg in allDataAreaRegisters)
|
|
||||||
AddDTArea(reg);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,11 +316,23 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
sb.AppendLine("---- DT Area ----");
|
foreach (var pollLevel in pollLevels) {
|
||||||
|
|
||||||
|
sb.AppendLine($"==== Poll lvl {pollLevel.level} ====");
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
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($"Optimization distance: {maxOptimizationDistance}");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
foreach (var area in dataAreas) {
|
sb.AppendLine($"---- DT Area ----");
|
||||||
|
|
||||||
|
foreach (var area in pollLevel.dataAreas) {
|
||||||
|
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine($"=> {area} = {area.underlyingBytes.Length} bytes");
|
sb.AppendLine($"=> {area} = {area.underlyingBytes.Length} bytes");
|
||||||
@@ -266,9 +348,9 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine("---- WR X Area ----");
|
sb.AppendLine($"---- WR X Area ----");
|
||||||
|
|
||||||
foreach (var area in externalRelayInAreas) {
|
foreach (var area in pollLevel.externalRelayInAreas) {
|
||||||
|
|
||||||
sb.AppendLine(area.ToString());
|
sb.AppendLine(area.ToString());
|
||||||
|
|
||||||
@@ -280,9 +362,9 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine("---- WR Y Area ----");
|
sb.AppendLine($"---- WR Y Area ---");
|
||||||
|
|
||||||
foreach (var area in externalRelayOutAreas) {
|
foreach (var area in pollLevel.externalRelayOutAreas) {
|
||||||
|
|
||||||
sb.AppendLine(area.ToString());
|
sb.AppendLine(area.ToString());
|
||||||
|
|
||||||
@@ -294,9 +376,9 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine("---- WR R Area ----");
|
sb.AppendLine($"---- WR R Area ----");
|
||||||
|
|
||||||
foreach (var area in internalRelayAreas) {
|
foreach (var area in pollLevel.internalRelayAreas) {
|
||||||
|
|
||||||
sb.AppendLine(area.ToString());
|
sb.AppendLine(area.ToString());
|
||||||
|
|
||||||
@@ -308,10 +390,28 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
mewInterface = mewIf;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateAreaRegisterValues () {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetUnderlyingBytes(BaseRegister reg) {
|
public byte[] GetUnderlyingBytes(BaseRegister reg) {
|
||||||
|
|||||||
Reference in New Issue
Block a user