mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Added ability to read registers in a sperate collection class
- multiple bugfixes
This commit is contained in:
@@ -1,44 +1,101 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using MewtocolNet;
|
using MewtocolNet;
|
||||||
using MewtocolNet.Responses;
|
using MewtocolNet.RegisterAttributes;
|
||||||
|
using System.Collections;
|
||||||
|
using MewtocolNet.Logging;
|
||||||
|
|
||||||
namespace Examples {
|
namespace Examples {
|
||||||
class Program {
|
|
||||||
static void Main(string[] args) {
|
|
||||||
|
|
||||||
Console.WriteLine("Starting test");
|
|
||||||
|
public class TestRegisters : RegisterCollectionBase {
|
||||||
|
|
||||||
|
//corresponds to a R100 boolean register in the PLC
|
||||||
|
[Register(100, RegisterType.R)]
|
||||||
|
public bool TestBool1 { get; private set; }
|
||||||
|
|
||||||
|
//corresponds to a R100 boolean register in the PLC
|
||||||
|
[Register(RegisterType.X, SpecialAddress.D)]
|
||||||
|
public bool TestBoolInputXD { get; private set; }
|
||||||
|
|
||||||
|
//corresponds to a DT1101 - DT1104 string register in the PLC with (STRING[4])
|
||||||
|
[Register(1101, 4)]
|
||||||
|
public string TestString1 { get; private set; }
|
||||||
|
|
||||||
|
//corresponds to a DT7000 16 bit int register in the PLC
|
||||||
|
[Register(7000)]
|
||||||
|
public short TestInt16 { get; private set; }
|
||||||
|
|
||||||
|
//corresponds to a DTD7001 - DTD7002 32 bit int register in the PLC
|
||||||
|
[Register(7001)]
|
||||||
|
public int TestInt32 { get; private set; }
|
||||||
|
|
||||||
|
//corresponds to a DTD7001 - DTD7002 32 bit float register in the PLC (REAL)
|
||||||
|
[Register(7003)]
|
||||||
|
public float TestFloat32 { get; private set; }
|
||||||
|
|
||||||
|
//corresponds to a DT7005 - DT7009 string register in the PLC with (STRING[5])
|
||||||
|
[Register(7005, 5)]
|
||||||
|
public string TestString2 { get; private set; }
|
||||||
|
|
||||||
|
//corresponds to a DT7010 as a 16bit word/int and parses the word as single bits
|
||||||
|
[Register(7010)]
|
||||||
|
public BitArray TestBitRegister { get; private set; }
|
||||||
|
|
||||||
|
//corresponds to a DT1204 as a 16bit word/int takes the bit at index 9 and writes it back as a boolean
|
||||||
|
[Register(1204, 9, BitCount.B16)]
|
||||||
|
public bool BitValue { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
|
||||||
|
static void Main(string[] args) {
|
||||||
|
|
||||||
Task.Factory.StartNew(async () => {
|
Task.Factory.StartNew(async () => {
|
||||||
|
|
||||||
|
//attaching the logger
|
||||||
|
Logger.LogLevel = LogLevel.Critical;
|
||||||
|
Logger.OnNewLogMessage((date, msg) => {
|
||||||
|
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}");
|
||||||
|
});
|
||||||
|
|
||||||
|
//setting up a new PLC interface and register collection
|
||||||
MewtocolInterface interf = new MewtocolInterface("10.237.191.3");
|
MewtocolInterface interf = new MewtocolInterface("10.237.191.3");
|
||||||
|
TestRegisters registers = new TestRegisters();
|
||||||
|
|
||||||
interf.AddRegister<short>("Cooler Status",1204);
|
//attaching the register collection and an automatic poller
|
||||||
interf.AddRegister<string>(1101, 4);
|
interf.WithRegisterCollection(registers).WithPoller();
|
||||||
|
|
||||||
interf.WithPoller();
|
|
||||||
|
|
||||||
interf.RegisterChanged += (o) => {
|
|
||||||
Console.WriteLine($"DT{o.MemoryAdress} {(o.Name != null ? $"({o.Name}) " : "")}changed to {o.GetValueString()}");
|
|
||||||
};
|
|
||||||
|
|
||||||
await interf.ConnectAsync(
|
await interf.ConnectAsync(
|
||||||
(plcinf) => {
|
(plcinf) => {
|
||||||
|
|
||||||
Console.WriteLine("Connected to PLC:\n" + plcinf.ToString());
|
//reading a value from the register collection
|
||||||
|
Console.WriteLine($"BitValue is: {registers.BitValue}");
|
||||||
|
|
||||||
//read back a register value
|
//writing a value to the registers
|
||||||
var statusNum = (NRegister<short>)interf.Registers[1204];
|
Task.Factory.StartNew(async () => {
|
||||||
Console.WriteLine($"Status num is: {statusNum.Value}");
|
|
||||||
|
await Task.Delay(2000);
|
||||||
|
//inverts the boolean register
|
||||||
|
interf.SetRegister(nameof(registers.TestBool1), !registers.TestBool1);
|
||||||
|
//adds 10 each time the plc connects to the PLCs INT regíster
|
||||||
|
interf.SetRegister(nameof(registers.TestInt16), (short)(registers.TestInt16 + 10));
|
||||||
|
//adds 1 each time the plc connects to the PLCs DINT regíster
|
||||||
|
interf.SetRegister(nameof(registers.TestInt32), (registers.TestInt32 + 1));
|
||||||
|
//adds 11.11 each time the plc connects to the PLCs REAL regíster
|
||||||
|
interf.SetRegister(nameof(registers.TestFloat32), (float)(registers.TestFloat32 + 11.11));
|
||||||
|
|
||||||
|
interf.SetRegister(nameof(registers.TestString2), new Random().Next(0, 99999).ToString());
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
Console.WriteLine("Failed connection");
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MewtocolNet.Logging;
|
||||||
using MewtocolNet.Responses;
|
using MewtocolNet.Responses;
|
||||||
|
|
||||||
namespace MewtocolNet {
|
namespace MewtocolNet {
|
||||||
@@ -15,7 +16,6 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
internal event Action PolledCycle;
|
internal event Action PolledCycle;
|
||||||
internal CancellationTokenSource cTokenAutoUpdater;
|
internal CancellationTokenSource cTokenAutoUpdater;
|
||||||
internal bool isWriting;
|
|
||||||
internal bool ContinousReaderRunning;
|
internal bool ContinousReaderRunning;
|
||||||
internal bool usePoller = false;
|
internal bool usePoller = false;
|
||||||
|
|
||||||
@@ -30,30 +30,49 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
cTokenAutoUpdater = new CancellationTokenSource();
|
cTokenAutoUpdater = new CancellationTokenSource();
|
||||||
|
|
||||||
Console.WriteLine("Attaching cont reader");
|
Logger.Log("Poller is attaching", LogLevel.Info, this);
|
||||||
|
|
||||||
Task.Factory.StartNew(async () => {
|
Task.Factory.StartNew(async () => {
|
||||||
|
|
||||||
var plcinf = await GetPLCInfoAsync();
|
var plcinf = await GetPLCInfoAsync();
|
||||||
if (plcinf == null) {
|
if (plcinf == null) {
|
||||||
Console.WriteLine("PLC is not reachable");
|
Logger.Log("PLC not reachable, stopping logger", LogLevel.Info, this);
|
||||||
throw new Exception("PLC is not reachable");
|
return;
|
||||||
}
|
}
|
||||||
if (!plcinf.OperationMode.RunMode) {
|
|
||||||
Console.WriteLine("PLC is not running");
|
PolledCycle += MewtocolInterface_PolledCycle;
|
||||||
throw new Exception("PLC is not running");
|
void MewtocolInterface_PolledCycle () {
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
foreach (var reg in GetAllRegisters()) {
|
||||||
|
string address = $"{reg.GetRegisterString()}{reg.GetStartingMemoryArea()}".PadRight(8, (char)32);
|
||||||
|
stringBuilder.AppendLine($"{address}{(reg.Name != null ? $" ({reg.Name})" : "")}: {reg.GetValueString()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Log($"Registers loaded are: \n" +
|
||||||
|
$"--------------------\n" +
|
||||||
|
$"{stringBuilder.ToString()}" +
|
||||||
|
$"--------------------",
|
||||||
|
LogLevel.Verbose, this);
|
||||||
|
|
||||||
|
Logger.Log("Logger did its first cycle successfully", LogLevel.Info, this);
|
||||||
|
|
||||||
|
PolledCycle -= MewtocolInterface_PolledCycle;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContinousReaderRunning = true;
|
ContinousReaderRunning = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
//dont update when currently writing a var
|
//do priority tasks first
|
||||||
if (isWriting) {
|
if(PriorityTasks.Count > 0) {
|
||||||
continue;
|
|
||||||
|
await PriorityTasks.FirstOrDefault(x => !x.IsCompleted);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(pollingDelayMs);
|
//await Task.Delay(pollingDelayMs);
|
||||||
|
|
||||||
foreach (var registerPair in Registers) {
|
foreach (var registerPair in Registers) {
|
||||||
|
|
||||||
var reg = registerPair.Value;
|
var reg = registerPair.Value;
|
||||||
@@ -102,7 +121,17 @@ namespace MewtocolNet {
|
|||||||
InvokeRegisterChanged(floatReg);
|
InvokeRegisterChanged(floatReg);
|
||||||
floatReg.TriggerNotifyChange();
|
floatReg.TriggerNotifyChange();
|
||||||
}
|
}
|
||||||
} else if (reg is SRegister stringReg) {
|
}
|
||||||
|
if (reg is BRegister boolReg) {
|
||||||
|
var lastVal = boolReg.Value;
|
||||||
|
var readout = (await ReadBoolRegister(boolReg, stationNumber)).Register.Value;
|
||||||
|
if (lastVal != readout) {
|
||||||
|
boolReg.LastValue = readout;
|
||||||
|
InvokeRegisterChanged(boolReg);
|
||||||
|
boolReg.TriggerNotifyChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reg is SRegister stringReg) {
|
||||||
var lastVal = stringReg.Value;
|
var lastVal = stringReg.Value;
|
||||||
var readout = (await ReadStringRegister(stringReg, stationNumber)).Register.Value;
|
var readout = (await ReadStringRegister(stringReg, stationNumber)).Register.Value;
|
||||||
if (lastVal != readout) {
|
if (lastVal != readout) {
|
||||||
@@ -127,42 +156,68 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#region Register Adding
|
#region Register Adding
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a PLC memory register to the watchlist <para/>
|
/// Adds a PLC memory register to the watchlist <para/>
|
||||||
/// The registers can be read back by attaching <see cref="WithPoller"/>
|
/// The registers can be read back by attaching <see cref="WithPoller"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">
|
|
||||||
/// The type of the register translated from C# to IEC 61131-3 types
|
|
||||||
/// <para>C# ------ IEC</para>
|
|
||||||
/// <para>short => INT/WORD</para>
|
|
||||||
/// <para>ushort => UINT</para>
|
|
||||||
/// <para>int => DOUBLE</para>
|
|
||||||
/// <para>uint => UDOUBLE</para>
|
|
||||||
/// <para>float => REAL</para>
|
|
||||||
/// <para>string => STRING</para>
|
|
||||||
/// </typeparam>
|
|
||||||
/// <param name="_address">The address of the register in the PLCs memory</param>
|
/// <param name="_address">The address of the register in the PLCs memory</param>
|
||||||
/// <param name="_length">The length of the string (Can be ignored for other types)</param>
|
/// <param name="_type">
|
||||||
public void AddRegister<T> (int _address, int _length = 1) {
|
/// The memory area type
|
||||||
|
/// <para>X = Physical input area (bool)</para>
|
||||||
|
/// <para>Y = Physical input area (bool)</para>
|
||||||
|
/// <para>R = Internal relay area (bool)</para>
|
||||||
|
/// <para>DT = Internal data area (short/ushort)</para>
|
||||||
|
/// <para>DDT = Internal relay area (int/uint)</para>
|
||||||
|
/// </param>
|
||||||
|
/// <param name="_name">A naming definition for QOL, doesn't effect PLC and is optional</param>
|
||||||
|
public void AddRegister (int _address, RegisterType _type, string _name = null) {
|
||||||
|
|
||||||
Type regType = typeof(T);
|
//as number registers
|
||||||
|
if (_type == RegisterType.DT_short) {
|
||||||
if (regType == typeof(short)) {
|
Registers.Add(_address, new NRegister<short>(_address, _name));
|
||||||
Registers.Add(_address, new NRegister<short>(_address));
|
return;
|
||||||
} else if (regType == typeof(ushort)) {
|
|
||||||
Registers.Add(_address, new NRegister<ushort>(_address));
|
|
||||||
} else if (regType == typeof(int)) {
|
|
||||||
Registers.Add(_address, new NRegister<int>(_address));
|
|
||||||
} else if (regType == typeof(uint)) {
|
|
||||||
Registers.Add(_address, new NRegister<uint>(_address));
|
|
||||||
} else if (regType == typeof(float)) {
|
|
||||||
Registers.Add(_address, new NRegister<float>(_address));
|
|
||||||
} else if (regType == typeof(string)) {
|
|
||||||
Registers.Add(_address, new SRegister(_address, _length));
|
|
||||||
} else {
|
|
||||||
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
|
|
||||||
$"Allowed are: short, ushort, int, uint, float and string");
|
|
||||||
}
|
}
|
||||||
|
if (_type == RegisterType.DT_ushort) {
|
||||||
|
Registers.Add(_address, new NRegister<ushort>(_address, _name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_type == RegisterType.DDT_int) {
|
||||||
|
Registers.Add(_address, new NRegister<int>(_address, _name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_type == RegisterType.DDT_uint) {
|
||||||
|
Registers.Add(_address, new NRegister<uint>(_address, _name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_type == RegisterType.DDT_float) {
|
||||||
|
Registers.Add(_address, new NRegister<float>(_address, _name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//as bool registers
|
||||||
|
Registers.Add(_address, new BRegister(_address, _type, _name));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a PLC memory register to the watchlist <para/>
|
||||||
|
/// The registers can be read back by attaching <see cref="WithPoller"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_spAddress">The special address of the register in the PLCs memory</param>
|
||||||
|
/// <param name="_type">
|
||||||
|
/// The memory area type
|
||||||
|
/// <para>X = Physical input area (bool)</para>
|
||||||
|
/// <para>Y = Physical input area (bool)</para>
|
||||||
|
/// <para>R = Internal relay area (bool)</para>
|
||||||
|
/// <para>DT = Internal data area (short/ushort)</para>
|
||||||
|
/// <para>DDT = Internal relay area (int/uint)</para>
|
||||||
|
/// </param>
|
||||||
|
/// <param name="_name">A naming definition for QOL, doesn't effect PLC and is optional</param>
|
||||||
|
public void AddRegister (SpecialAddress _spAddress, RegisterType _type, string _name = null) {
|
||||||
|
|
||||||
|
//as bool registers
|
||||||
|
Registers.Add((int)_spAddress, new BRegister(_spAddress, _type, _name));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,22 +238,35 @@ namespace MewtocolNet {
|
|||||||
/// <param name="_name">A naming definition for QOL, doesn't effect PLC and is optional</param>
|
/// <param name="_name">A naming definition for QOL, doesn't effect PLC and is optional</param>
|
||||||
/// <param name="_address">The address of the register in the PLCs memory</param>
|
/// <param name="_address">The address of the register in the PLCs memory</param>
|
||||||
/// <param name="_length">The length of the string (Can be ignored for other types)</param>
|
/// <param name="_length">The length of the string (Can be ignored for other types)</param>
|
||||||
public void AddRegister<T>(string _name, int _address, int _length = 1) {
|
public void AddRegister<T>(int _address, int _length = 1, string _name = null, bool _isBitwise = false) {
|
||||||
|
|
||||||
Type regType = typeof(T);
|
Type regType = typeof(T);
|
||||||
|
|
||||||
|
if (regType != typeof(string) && _length != 1) {
|
||||||
|
throw new NotSupportedException($"_lenght parameter only allowed for register of type string");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Registers.Any(x => x.Key == _address)) {
|
||||||
|
|
||||||
|
throw new NotSupportedException($"Cannot add a register multiple times, " +
|
||||||
|
$"make sure that all register attributes or AddRegister assignments have different adresses.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (regType == typeof(short)) {
|
if (regType == typeof(short)) {
|
||||||
Registers.Add(_address, new NRegister<short>(_address, _name));
|
Registers.Add(_address, new NRegister<short>(_address, _name, _isBitwise));
|
||||||
} else if (regType == typeof(ushort)) {
|
} else if (regType == typeof(ushort)) {
|
||||||
Registers.Add(_address, new NRegister<ushort>(_address, _name));
|
Registers.Add(_address, new NRegister<ushort>(_address, _name));
|
||||||
} else if (regType == typeof(int)) {
|
} else if (regType == typeof(int)) {
|
||||||
Registers.Add(_address, new NRegister<int>(_address, _name));
|
Registers.Add(_address, new NRegister<int>(_address, _name, _isBitwise));
|
||||||
} else if (regType == typeof(uint)) {
|
} else if (regType == typeof(uint)) {
|
||||||
Registers.Add(_address, new NRegister<uint>(_address, _name));
|
Registers.Add(_address, new NRegister<uint>(_address, _name));
|
||||||
} else if (regType == typeof(float)) {
|
} else if (regType == typeof(float)) {
|
||||||
Registers.Add(_address, new NRegister<float>(_address, _name));
|
Registers.Add(_address, new NRegister<float>(_address, _name));
|
||||||
} else if (regType == typeof(string)) {
|
} else if (regType == typeof(string)) {
|
||||||
Registers.Add(_address, new SRegister(_address, _length, _name));
|
Registers.Add(_address, new SRegister(_address, _length, _name));
|
||||||
|
} else if (regType == typeof(bool)) {
|
||||||
|
Registers.Add(_address, new BRegister(_address, RegisterType.R, _name));
|
||||||
} else {
|
} else {
|
||||||
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
|
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
|
||||||
$"Allowed are: short, ushort, int, uint, float and string");
|
$"Allowed are: short, ushort, int, uint, float and string");
|
||||||
@@ -208,6 +276,21 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Register Reading
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all added registers
|
||||||
|
/// </summary>
|
||||||
|
public List<Register> GetAllRegisters () {
|
||||||
|
|
||||||
|
return Registers.Values.ToList();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Invoking
|
||||||
|
|
||||||
internal void InvokeRegisterChanged (Register reg) {
|
internal void InvokeRegisterChanged (Register reg) {
|
||||||
|
|
||||||
RegisterChanged?.Invoke(reg);
|
RegisterChanged?.Invoke(reg);
|
||||||
@@ -220,5 +303,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
MewtocolNet/Mewtocol/Logging/Logger.cs
Normal file
43
MewtocolNet/Mewtocol/Logging/Logger.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MewtocolNet.Logging {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logging module for all PLCs
|
||||||
|
/// </summary>
|
||||||
|
public static class Logger {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the loglevel for the logger module
|
||||||
|
/// </summary>
|
||||||
|
public static LogLevel LogLevel { get; set; }
|
||||||
|
|
||||||
|
internal static Action<DateTime, string> LogInvoked;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets invoked whenever a new log message is ready
|
||||||
|
/// </summary>
|
||||||
|
public static void OnNewLogMessage (Action<DateTime, string> onMsg) {
|
||||||
|
|
||||||
|
LogInvoked += (t, m) => {
|
||||||
|
onMsg(t, m);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Log (string message, LogLevel loglevel, MewtocolInterface sender = null) {
|
||||||
|
|
||||||
|
if ((int)loglevel <= (int)LogLevel) {
|
||||||
|
if (sender == null) {
|
||||||
|
LogInvoked?.Invoke(DateTime.Now, message);
|
||||||
|
} else {
|
||||||
|
LogInvoked?.Invoke(DateTime.Now, $"[{sender.GetConnectionPortInfo()}] {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
34
MewtocolNet/Mewtocol/Logging/LoggerEnums.cs
Normal file
34
MewtocolNet/Mewtocol/Logging/LoggerEnums.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MewtocolNet.Logging {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The loglevel of the logging module
|
||||||
|
/// </summary>
|
||||||
|
public enum LogLevel {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs only errors
|
||||||
|
/// </summary>
|
||||||
|
Error = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Logs info like connection establish and loss
|
||||||
|
/// </summary>
|
||||||
|
Info = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Logs only state changes
|
||||||
|
/// </summary>
|
||||||
|
Change = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// Logs all errors, state changes, and messages
|
||||||
|
/// </summary>
|
||||||
|
Verbose = 3,
|
||||||
|
/// <summary>
|
||||||
|
/// Logs all types including network traffic
|
||||||
|
/// </summary>
|
||||||
|
Critical = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Linq;
|
|
||||||
using MewtocolNet.Responses;
|
|
||||||
|
|
||||||
namespace MewtocolNet.Events {
|
|
||||||
|
|
||||||
public class MewtocolContactListener : IDisposable {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets fired whenever a contact of the observed list changes its value
|
|
||||||
/// </summary>
|
|
||||||
public event Action<List<IBoolContact>> ContactsChangedValue;
|
|
||||||
|
|
||||||
//privates
|
|
||||||
private List<IBoolContact> lastContacts = new List<IBoolContact>();
|
|
||||||
private CancellationTokenSource cToken = new CancellationTokenSource();
|
|
||||||
|
|
||||||
public static MewtocolContactListener ListenContactChanges (MewtocolInterface _interFace, List<Contact> _observeContacts, int _refreshMS = 100, int _stationNumber = 1) {
|
|
||||||
|
|
||||||
MewtocolContactListener listener = new MewtocolContactListener();
|
|
||||||
_ = Task.Factory.StartNew( async () => {
|
|
||||||
//get contacts first time
|
|
||||||
listener.lastContacts = (List<IBoolContact>) await _interFace.ReadBoolContacts(_observeContacts, _stationNumber);
|
|
||||||
while(!listener.cToken.Token.IsCancellationRequested) {
|
|
||||||
//compare and update
|
|
||||||
var newContactData = (List<IBoolContact>) await _interFace.ReadBoolContacts(_observeContacts, _stationNumber);
|
|
||||||
var difference = newContactData.Where(p => listener.lastContacts.Any(l => p.Value != l.Value && p.Identifier == l.Identifier));
|
|
||||||
|
|
||||||
if(difference.Count() > 0) {
|
|
||||||
listener.ContactsChangedValue?.Invoke(difference.ToList());
|
|
||||||
listener.lastContacts = newContactData;
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
await Task.Delay(_refreshMS);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
cToken.Cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,19 +3,30 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using MewtocolNet.Responses;
|
using MewtocolNet.Responses;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace MewtocolNet {
|
namespace MewtocolNet {
|
||||||
public static class MewtocolHelpers {
|
public static class MewtocolHelpers {
|
||||||
public static Byte[] ToHexASCIIBytes (this string _str) {
|
|
||||||
|
/// <summary>
|
||||||
|
/// Turns a bit array into a 0 and 1 string
|
||||||
|
/// </summary>
|
||||||
|
public static string ToBitString (this BitArray arr) {
|
||||||
|
|
||||||
|
var bits = new bool[arr.Length];
|
||||||
|
arr.CopyTo(bits, 0);
|
||||||
|
return string.Join("", bits.Select(x => x ? "1" : "0"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] ToHexASCIIBytes (this string _str) {
|
||||||
ASCIIEncoding ascii = new ASCIIEncoding();
|
ASCIIEncoding ascii = new ASCIIEncoding();
|
||||||
Byte[] bytes = ascii.GetBytes(_str.ToUpper());
|
byte[] bytes = ascii.GetBytes(_str.ToUpper());
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string BuildBCCFrame (this string asciiArr) {
|
internal static string BuildBCCFrame (this string asciiArr) {
|
||||||
Encoding ae = Encoding.ASCII;
|
Encoding ae = Encoding.ASCII;
|
||||||
byte[] b = ae.GetBytes(asciiArr);
|
byte[] b = ae.GetBytes(asciiArr);
|
||||||
byte xorTotalByte = 0;
|
byte xorTotalByte = 0;
|
||||||
@@ -24,7 +35,7 @@ namespace MewtocolNet {
|
|||||||
return asciiArr.Insert(asciiArr.Length, xorTotalByte.ToString("X2"));
|
return asciiArr.Insert(asciiArr.Length, xorTotalByte.ToString("X2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] ParseDTBytes (this string _onString ,int _blockSize = 4) {
|
internal static byte[] ParseDTBytes (this string _onString ,int _blockSize = 4) {
|
||||||
var res = new Regex(@"\%([0-9]{2})\$RD(.{"+_blockSize+"})").Match(_onString);
|
var res = new Regex(@"\%([0-9]{2})\$RD(.{"+_blockSize+"})").Match(_onString);
|
||||||
if(res.Success) {
|
if(res.Success) {
|
||||||
string val = res.Groups[2].Value;
|
string val = res.Groups[2].Value;
|
||||||
@@ -33,7 +44,7 @@ namespace MewtocolNet {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ParseDTByteString (this string _onString, int _blockSize = 4) {
|
internal static string ParseDTByteString (this string _onString, int _blockSize = 4) {
|
||||||
var res = new Regex(@"\%([0-9]{2})\$RD(.{" + _blockSize + "})").Match(_onString);
|
var res = new Regex(@"\%([0-9]{2})\$RD(.{" + _blockSize + "})").Match(_onString);
|
||||||
if (res.Success) {
|
if (res.Success) {
|
||||||
string val = res.Groups[2].Value;
|
string val = res.Groups[2].Value;
|
||||||
@@ -42,16 +53,46 @@ namespace MewtocolNet {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ParseDTString (this string _onString) {
|
internal static bool? ParseRCSingleBit (this string _onString, int _blockSize = 4) {
|
||||||
var res = new Regex(@"\%([0-9]{2})\$RD.{8}(.*)...").Match(_onString);
|
var res = new Regex(@"\%([0-9]{2})\$RC(.)").Match(_onString);
|
||||||
if(res.Success) {
|
if (res.Success) {
|
||||||
string val = res.Groups[2].Value;
|
string val = res.Groups[2].Value;
|
||||||
return val.GetStringFromAsciiHex();
|
return val == "1";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string BuildDTString (this string _inString, short _stringReservedSize) {
|
internal static string ParseDTString (this string _onString) {
|
||||||
|
var res = new Regex(@"\%([0-9]{2})\$RD.{8}(.*)...").Match(_onString);
|
||||||
|
if(res.Success) {
|
||||||
|
string val = res.Groups[2].Value;
|
||||||
|
return val.GetStringFromAsciiHex().Trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string ReverseByteOrder (this string _onString) {
|
||||||
|
|
||||||
|
//split into 2 chars
|
||||||
|
var stringBytes = _onString.SplitInParts(2).ToList();
|
||||||
|
|
||||||
|
stringBytes.Reverse();
|
||||||
|
|
||||||
|
return string.Join("", stringBytes);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IEnumerable<String> SplitInParts (this string s, int partLength) {
|
||||||
|
if (s == null)
|
||||||
|
throw new ArgumentNullException(nameof(s));
|
||||||
|
if (partLength <= 0)
|
||||||
|
throw new ArgumentException("Part length has to be positive.", nameof(partLength));
|
||||||
|
|
||||||
|
for (var i = 0; i < s.Length; i += partLength)
|
||||||
|
yield return s.Substring(i, Math.Min(partLength, s.Length - i));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string BuildDTString (this string _inString, short _stringReservedSize) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
//06000600
|
//06000600
|
||||||
short stringSize = (short)_inString.Length;
|
short stringSize = (short)_inString.Length;
|
||||||
@@ -62,13 +103,13 @@ namespace MewtocolNet {
|
|||||||
//string count actual bytes
|
//string count actual bytes
|
||||||
sb.Append(sizeBytes);
|
sb.Append(sizeBytes);
|
||||||
//actual string content
|
//actual string content
|
||||||
sb.Append(_inString.GetAsciiHexFromString().PadRight(_stringReservedSize * 2, '0'));
|
sb.Append(_inString.GetAsciiHexFromString());
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static string GetStringFromAsciiHex (this string input) {
|
internal static string GetStringFromAsciiHex (this string input) {
|
||||||
if (input.Length % 2 != 0)
|
if (input.Length % 2 != 0)
|
||||||
throw new ArgumentException("input not a hex string");
|
throw new ArgumentException("input not a hex string");
|
||||||
byte[] bytes = new byte[input.Length / 2];
|
byte[] bytes = new byte[input.Length / 2];
|
||||||
@@ -79,19 +120,19 @@ namespace MewtocolNet {
|
|||||||
return Encoding.ASCII.GetString(bytes);
|
return Encoding.ASCII.GetString(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetAsciiHexFromString (this string input) {
|
internal static string GetAsciiHexFromString (this string input) {
|
||||||
var bytes = new ASCIIEncoding().GetBytes(input);
|
var bytes = new ASCIIEncoding().GetBytes(input);
|
||||||
return bytes.ToHexString();
|
return bytes.ToHexString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] HexStringToByteArray(this string hex) {
|
internal static byte[] HexStringToByteArray(this string hex) {
|
||||||
return Enumerable.Range(0, hex.Length)
|
return Enumerable.Range(0, hex.Length)
|
||||||
.Where(x => x % 2 == 0)
|
.Where(x => x % 2 == 0)
|
||||||
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
|
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToHexString (this byte[] arr) {
|
internal static string ToHexString (this byte[] arr) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
foreach (var b in arr) {
|
foreach (var b in arr) {
|
||||||
sb.Append(b.ToString("X2"));
|
sb.Append(b.ToString("X2"));
|
||||||
@@ -99,23 +140,6 @@ namespace MewtocolNet {
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToJsonString (this IEnumerable<IBoolContact> _contacts, bool formatPretty = false) {
|
|
||||||
return JsonSerializer.Serialize(_contacts, new JsonSerializerOptions {
|
|
||||||
WriteIndented = formatPretty,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) {
|
|
||||||
while (toCheck != null && toCheck != typeof(object)) {
|
|
||||||
var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
|
|
||||||
if (generic == cur) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
toCheck = toCheck.BaseType;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,14 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
using MewtocolNet.Responses;
|
using MewtocolNet.Responses;
|
||||||
|
using MewtocolNet.RegisterAttributes;
|
||||||
|
using MewtocolNet.Logging;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Reflection;
|
||||||
|
using MewtocolNet.Logging;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace MewtocolNet {
|
namespace MewtocolNet {
|
||||||
|
|
||||||
@@ -33,14 +40,11 @@ namespace MewtocolNet {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The registered data registers of the PLC
|
/// The registered data registers of the PLC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<int, Register> Registers { get; set; } = new();
|
public Dictionary<int, Register> Registers { get; set; } = new Dictionary<int, Register>();
|
||||||
|
|
||||||
private CancellationTokenSource tokenSource;
|
|
||||||
|
|
||||||
private string ip {get;set;}
|
private string ip {get;set;}
|
||||||
private int port {get;set;}
|
private int port {get;set;}
|
||||||
private int stationNumber {get;set;}
|
private int stationNumber {get;set;}
|
||||||
private int pollingDelayMs {get;set;}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current IP of the PLC connection
|
/// The current IP of the PLC connection
|
||||||
@@ -55,6 +59,7 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int StationNumber => stationNumber;
|
public int StationNumber => stationNumber;
|
||||||
|
|
||||||
|
internal List<Task> PriorityTasks { get; set; } = new List<Task>();
|
||||||
|
|
||||||
#region Initialization
|
#region Initialization
|
||||||
|
|
||||||
@@ -79,6 +84,15 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RegisterChanged += (o) => {
|
||||||
|
|
||||||
|
string address = $"{o.GetRegisterString()}{o.MemoryAdress}".PadRight(5, (char)32); ;
|
||||||
|
|
||||||
|
Logger.Log($"{address} " +
|
||||||
|
$"{(o.Name != null ? $"({o.Name}) " : "")}" +
|
||||||
|
$"changed to \"{o.GetValueString()}\"", LogLevel.Change, this);
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -99,7 +113,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
var plcinf = await GetPLCInfoAsync();
|
var plcinf = await GetPLCInfoAsync();
|
||||||
|
|
||||||
if (plcinf is not null) {
|
if (plcinf != null) {
|
||||||
|
|
||||||
|
Logger.Log("Connected", LogLevel.Info, this);
|
||||||
|
Logger.Log($"\n\n{plcinf.ToString()}\n\n", LogLevel.Verbose, this);
|
||||||
|
|
||||||
Connected?.Invoke(plcinf);
|
Connected?.Invoke(plcinf);
|
||||||
|
|
||||||
@@ -119,8 +136,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (OnFailed != null)
|
if (OnFailed != null) {
|
||||||
OnFailed();
|
OnFailed();
|
||||||
|
Logger.Log("Initial connection failed", LogLevel.Info, this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,9 +151,8 @@ namespace MewtocolNet {
|
|||||||
/// Attaches a poller to the interface that continously
|
/// Attaches a poller to the interface that continously
|
||||||
/// polls the registered data registers and writes the values to them
|
/// polls the registered data registers and writes the values to them
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MewtocolInterface WithPoller (int pollerDelayMs = 50) {
|
public MewtocolInterface WithPoller () {
|
||||||
|
|
||||||
pollingDelayMs = pollerDelayMs;
|
|
||||||
usePoller = true;
|
usePoller = true;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
@@ -143,6 +161,258 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Register Collection
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches a register collection object to
|
||||||
|
/// the interface that can be updated automatically.
|
||||||
|
/// <para/>
|
||||||
|
/// Just create a class inheriting from <see cref="RegisterCollectionBase"/>
|
||||||
|
/// and assert some propertys with the custom <see cref="RegisterAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="collection">A collection inherting the <see cref="RegisterCollectionBase"/> class</param>
|
||||||
|
public MewtocolInterface WithRegisterCollection (RegisterCollectionBase collection) {
|
||||||
|
|
||||||
|
collection.PLCInterface = this;
|
||||||
|
|
||||||
|
var props = collection.GetType().GetProperties();
|
||||||
|
|
||||||
|
foreach (var prop in props) {
|
||||||
|
|
||||||
|
var attributes = prop.GetCustomAttributes(true);
|
||||||
|
|
||||||
|
string propName = prop.Name;
|
||||||
|
foreach (var attr in attributes) {
|
||||||
|
|
||||||
|
if(attr is RegisterAttribute cAttribute) {
|
||||||
|
|
||||||
|
if (prop.PropertyType == typeof(bool) && cAttribute.AssignedBitIndex == -1) {
|
||||||
|
if (cAttribute.SpecialAddress == SpecialAddress.None) {
|
||||||
|
AddRegister(cAttribute.MemoryArea, cAttribute.RegisterType, _name: propName);
|
||||||
|
} else {
|
||||||
|
AddRegister(cAttribute.SpecialAddress, cAttribute.RegisterType, _name: propName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop.PropertyType == typeof(short)) {
|
||||||
|
AddRegister<short>(cAttribute.MemoryArea, _name: propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop.PropertyType == typeof(ushort)) {
|
||||||
|
AddRegister<ushort>(cAttribute.MemoryArea, _name: propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop.PropertyType == typeof(int)) {
|
||||||
|
AddRegister<int>(cAttribute.MemoryArea, _name: propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop.PropertyType == typeof(uint)) {
|
||||||
|
AddRegister<uint>(cAttribute.MemoryArea, _name: propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop.PropertyType == typeof(float)) {
|
||||||
|
AddRegister<float>(cAttribute.MemoryArea, _name: propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop.PropertyType == typeof(string)) {
|
||||||
|
AddRegister<string>(cAttribute.MemoryArea, cAttribute.StringLength, _name: propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//read number as bit array
|
||||||
|
if (prop.PropertyType == typeof(BitArray)) {
|
||||||
|
|
||||||
|
if(cAttribute.BitCount == BitCount.B16) {
|
||||||
|
AddRegister<short>(cAttribute.MemoryArea, _name: propName, _isBitwise: true);
|
||||||
|
} else {
|
||||||
|
AddRegister<int>(cAttribute.MemoryArea, _name: propName, _isBitwise: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//read number as bit array by invdividual properties
|
||||||
|
if (prop.PropertyType == typeof(bool) && cAttribute.AssignedBitIndex != -1) {
|
||||||
|
|
||||||
|
if (cAttribute.BitCount == BitCount.B16) {
|
||||||
|
AddRegister<short>(cAttribute.MemoryArea, _name: propName, _isBitwise: true);
|
||||||
|
} else {
|
||||||
|
AddRegister<int>(cAttribute.MemoryArea, _name: propName, _isBitwise: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterChanged += (reg) => {
|
||||||
|
|
||||||
|
var foundToUpdate = props.FirstOrDefault(x => x.Name == reg.Name);
|
||||||
|
|
||||||
|
if (foundToUpdate != null) {
|
||||||
|
|
||||||
|
var foundAttributes = foundToUpdate.GetCustomAttributes(true);
|
||||||
|
var foundAttr = foundAttributes.FirstOrDefault(x => x.GetType() == typeof(RegisterAttribute));
|
||||||
|
|
||||||
|
if (foundAttr == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var registerAttr = (RegisterAttribute)foundAttr;
|
||||||
|
|
||||||
|
//check if bit parse mode
|
||||||
|
if (registerAttr.AssignedBitIndex == -1) {
|
||||||
|
|
||||||
|
//setting back booleans
|
||||||
|
if (foundToUpdate.PropertyType == typeof(bool)) {
|
||||||
|
foundToUpdate.SetValue(collection, ((BRegister)reg).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//setting back numbers
|
||||||
|
|
||||||
|
if (foundToUpdate.PropertyType == typeof(short)) {
|
||||||
|
foundToUpdate.SetValue(collection, ((NRegister<short>)reg).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundToUpdate.PropertyType == typeof(ushort)) {
|
||||||
|
foundToUpdate.SetValue(collection, ((NRegister<ushort>)reg).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundToUpdate.PropertyType == typeof(int)) {
|
||||||
|
foundToUpdate.SetValue(collection, ((NRegister<int>)reg).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundToUpdate.PropertyType == typeof(uint)) {
|
||||||
|
foundToUpdate.SetValue(collection, ((NRegister<uint>)reg).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundToUpdate.PropertyType == typeof(float)) {
|
||||||
|
foundToUpdate.SetValue(collection, ((NRegister<float>)reg).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//setting back strings
|
||||||
|
|
||||||
|
if (foundToUpdate.PropertyType == typeof(string)) {
|
||||||
|
foundToUpdate.SetValue(collection, ((SRegister)reg).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (foundToUpdate.PropertyType == typeof(bool) && registerAttr.AssignedBitIndex >= 0) {
|
||||||
|
|
||||||
|
//setting back bit registers to individual properties
|
||||||
|
if (reg is NRegister<short> shortReg) {
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(shortReg.Value);
|
||||||
|
BitArray bitAr = new BitArray(bytes);
|
||||||
|
foundToUpdate.SetValue(collection, bitAr[registerAttr.AssignedBitIndex]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reg is NRegister<int> intReg) {
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(intReg.Value);
|
||||||
|
BitArray bitAr = new BitArray(bytes);
|
||||||
|
foundToUpdate.SetValue(collection, bitAr[registerAttr.AssignedBitIndex]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else if(foundToUpdate.PropertyType == typeof(BitArray)) {
|
||||||
|
|
||||||
|
//setting back bit registers
|
||||||
|
if (reg is NRegister<short> shortReg) {
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(shortReg.Value);
|
||||||
|
BitArray bitAr = new BitArray(bytes);
|
||||||
|
foundToUpdate.SetValue(collection, bitAr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reg is NRegister<int> intReg) {
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(intReg.Value);
|
||||||
|
BitArray bitAr = new BitArray(bytes);
|
||||||
|
foundToUpdate.SetValue(collection, bitAr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.TriggerPropertyChanged(foundToUpdate.Name);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Register Writing
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a register in the PLCs memory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="registerName">The name the register was given to or a property name from the RegisterCollection class</param>
|
||||||
|
/// <param name="value">The value to write to the register</param>
|
||||||
|
public void SetRegister (string registerName, object value) {
|
||||||
|
|
||||||
|
|
||||||
|
var foundRegister = GetAllRegisters().FirstOrDefault(x => x.Name == registerName);
|
||||||
|
|
||||||
|
if (foundRegister == null) {
|
||||||
|
throw new Exception($"Register with the name {registerName} was not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundRegister.GetType() == typeof(BRegister)) {
|
||||||
|
|
||||||
|
_ = WriteBoolRegister((BRegister)foundRegister, (bool)value, StationNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundRegister.GetType() == typeof(NRegister<short>)) {
|
||||||
|
|
||||||
|
_ = WriteNumRegister((NRegister<short>)foundRegister, (short)value, StationNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundRegister.GetType() == typeof(NRegister<ushort>)) {
|
||||||
|
|
||||||
|
_ = WriteNumRegister((NRegister<ushort>)foundRegister, (ushort)value, StationNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundRegister.GetType() == typeof(NRegister<int>)) {
|
||||||
|
|
||||||
|
_ = WriteNumRegister((NRegister<int>)foundRegister, (int)value, StationNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundRegister.GetType() == typeof(NRegister<uint>)) {
|
||||||
|
|
||||||
|
_ = WriteNumRegister((NRegister<uint>)foundRegister, (uint)value, StationNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundRegister.GetType() == typeof(NRegister<float>)) {
|
||||||
|
|
||||||
|
_ = WriteNumRegister((NRegister<float>)foundRegister, (float)value, StationNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundRegister.GetType() == typeof(SRegister)) {
|
||||||
|
|
||||||
|
_ = WriteStringRegister((SRegister)foundRegister, (string)value, StationNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Low level command handling
|
#region Low level command handling
|
||||||
|
|
||||||
@@ -158,7 +428,27 @@ namespace MewtocolNet {
|
|||||||
_msg += "\r";
|
_msg += "\r";
|
||||||
|
|
||||||
//send request
|
//send request
|
||||||
var response = await SendSingleBlock(_msg);
|
|
||||||
|
string response = null;
|
||||||
|
|
||||||
|
if(ContinousReaderRunning) {
|
||||||
|
|
||||||
|
//if the poller is active then add all messages to a qeueue
|
||||||
|
|
||||||
|
var awaittask = SendSingleBlock(_msg);
|
||||||
|
PriorityTasks.Add(awaittask);
|
||||||
|
awaittask.Wait();
|
||||||
|
|
||||||
|
PriorityTasks.Remove(awaittask);
|
||||||
|
response = awaittask.Result;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//poller not active let the user manage message timing
|
||||||
|
|
||||||
|
response = await SendSingleBlock(_msg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if(response == null) {
|
if(response == null) {
|
||||||
return new CommandResult {
|
return new CommandResult {
|
||||||
@@ -195,16 +485,12 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
private async Task<string> SendSingleBlock (string _blockString) {
|
private async Task<string> SendSingleBlock (string _blockString) {
|
||||||
|
|
||||||
if(isWriting) {
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) {
|
using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.ConnectAsync(ip, port, tokenSource.Token);
|
await client.ConnectAsync(ip, port);
|
||||||
} catch(SocketException) {
|
} catch(SocketException) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -213,9 +499,9 @@ namespace MewtocolNet {
|
|||||||
var message = _blockString.ToHexASCIIBytes();
|
var message = _blockString.ToHexASCIIBytes();
|
||||||
var messageAscii = BitConverter.ToString(message).Replace("-", " ");
|
var messageAscii = BitConverter.ToString(message).Replace("-", " ");
|
||||||
//send request
|
//send request
|
||||||
isWriting = true;
|
|
||||||
using (var sendStream = new MemoryStream(message)) {
|
using (var sendStream = new MemoryStream(message)) {
|
||||||
await sendStream.CopyToAsync(stream);
|
await sendStream.CopyToAsync(stream);
|
||||||
|
Logger.Log($"OUT MSG: {_blockString}", LogLevel.Critical, this);
|
||||||
//log message sent
|
//log message sent
|
||||||
ASCIIEncoding enc = new ASCIIEncoding();
|
ASCIIEncoding enc = new ASCIIEncoding();
|
||||||
string characters = enc.GetString(message);
|
string characters = enc.GetString(message);
|
||||||
@@ -228,7 +514,8 @@ namespace MewtocolNet {
|
|||||||
response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes));
|
response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes));
|
||||||
}
|
}
|
||||||
while (stream.DataAvailable);
|
while (stream.DataAvailable);
|
||||||
isWriting = false;
|
sw.Stop();
|
||||||
|
Logger.Log($"IN MSG ({(int)sw.Elapsed.TotalMilliseconds}ms): {_blockString}", LogLevel.Critical, this);
|
||||||
return response.ToString();
|
return response.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +525,20 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Accessing Info
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the connection info string
|
||||||
|
/// </summary>
|
||||||
|
public string GetConnectionPortInfo () {
|
||||||
|
|
||||||
|
return $"{IpAddress}:{Port}";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using MewtocolNet.Responses;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MewtocolNet {
|
|
||||||
|
|
||||||
public static class MewtocolInterfaceExtensions {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
public partial class MewtocolInterface {
|
public partial class MewtocolInterface {
|
||||||
|
|
||||||
#region High level command handling
|
#region PLC info getters
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets generic information about the PLC
|
/// Gets generic information about the PLC
|
||||||
@@ -49,91 +49,50 @@ namespace MewtocolNet {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
#endregion
|
||||||
/// Reads bool values from the plc by the given <c>Contact</c> List
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="_contactsToRead">A list of contacts</param>
|
|
||||||
/// <param name="_stationNumber">The PLCs station number</param>
|
|
||||||
/// <returns>List of IBoolContact with unique copys of the given contacts</returns>
|
|
||||||
public async Task<IEnumerable<IBoolContact>> ReadBoolContacts (List<Contact> _contactsToRead, int _stationNumber = 1) {
|
|
||||||
|
|
||||||
//re order by contact pfx for faster querying
|
#region Bool register reading / writing
|
||||||
_contactsToRead = _contactsToRead.OrderBy(x=>x.Prefix).ToList();
|
|
||||||
|
|
||||||
//return list
|
public async Task<BRegisterResult> ReadBoolRegister (BRegister _toRead, int _stationNumber = 1) {
|
||||||
List<IBoolContact> returnContacts = new List<IBoolContact>();
|
|
||||||
|
|
||||||
//grouped by 8 each
|
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RCS{_toRead.BuildMewtocolIdent()}";
|
||||||
List<List<Contact>> nestedContacts = new List<List<Contact>>();
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
|
||||||
//group into max 8 contacts list
|
if(!result.Success) {
|
||||||
List<Contact> tempGroup = new List<Contact>();
|
return new BRegisterResult {
|
||||||
for (int i = 0; i < _contactsToRead.Count; i++) {
|
Result = result,
|
||||||
tempGroup.Add(_contactsToRead[i]);
|
Register = _toRead
|
||||||
//each 8 contacts make a new list
|
};
|
||||||
if(i % 7 == 0 && i != 0 && i != _contactsToRead.Count) {
|
|
||||||
nestedContacts.Add(tempGroup);
|
|
||||||
tempGroup = new List<Contact>();
|
|
||||||
}
|
|
||||||
//if end of list and contacts cannot be broke down to 8 each group
|
|
||||||
if(i == _contactsToRead.Count - 1 && _contactsToRead.Count % 8 != 0) {
|
|
||||||
nestedContacts.Add(tempGroup);
|
|
||||||
tempGroup = new List<Contact>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//make task for each group
|
var resultBool = result.Response.ParseRCSingleBit();
|
||||||
foreach (var group in nestedContacts) {
|
if(resultBool != null) {
|
||||||
//regex for getting values
|
_toRead.LastValue = resultBool.Value;
|
||||||
StringBuilder regexString = new StringBuilder(@"\%..\$RC");
|
|
||||||
//append start %01#RCP2
|
|
||||||
StringBuilder messageString = new StringBuilder();
|
|
||||||
messageString.Append($"%{_stationNumber.ToString().PadLeft(2, '0')}#RCP");
|
|
||||||
messageString.Append($"{group.Count}");
|
|
||||||
//append each contact of group Y0000 Y0001 etc
|
|
||||||
foreach (var cont in group) {
|
|
||||||
messageString.Append(cont.BuildMewtocolIdent());
|
|
||||||
regexString.Append(@"([0-9])");
|
|
||||||
}
|
|
||||||
regexString.Append(@"(..)");
|
|
||||||
//parse the result
|
|
||||||
var result = await SendCommandAsync(messageString.ToString());
|
|
||||||
Regex regCheck = new Regex(regexString.ToString(), RegexOptions.IgnoreCase);
|
|
||||||
if(result.Success && regCheck.IsMatch(result.Response)) {
|
|
||||||
//parse result string
|
|
||||||
Match regMatch = regCheck.Match(result.Response);
|
|
||||||
// add to return list
|
|
||||||
for (int i = 0; i < group.Count; i++) {
|
|
||||||
Contact cont = group[i].ShallowCopy();
|
|
||||||
Contact toadd = cont;
|
|
||||||
if( regMatch.Groups[i + 1].Value == "1" ) {
|
|
||||||
toadd.Value = true;
|
|
||||||
} else if( regMatch.Groups[i + 1].Value == "0" ) {
|
|
||||||
toadd.Value = false;
|
|
||||||
}
|
|
||||||
returnContacts.Add(toadd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return returnContacts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var finalRes = new BRegisterResult {
|
||||||
/// Writes a boolen value to the given contact
|
Result = result,
|
||||||
/// </summary>
|
Register = _toRead
|
||||||
/// <param name="_contact">The contact to write</param>
|
};
|
||||||
/// <param name="_value">The boolean state to write</param>
|
|
||||||
/// <param name="_stationNumber">Station Number (optional)</param>
|
return finalRes;
|
||||||
/// <returns>A result struct</returns>
|
|
||||||
public async Task<CommandResult> WriteContact (Contact _contact, bool _value, int _stationNumber = 1) {
|
|
||||||
string stationNum = _stationNumber.ToString().PadLeft(2, '0');
|
|
||||||
string dataArea = _contact.BuildMewtocolIdent();
|
|
||||||
string dataString = _value ? "1" : "0";
|
|
||||||
string requeststring = $"%{stationNum}#WCS{dataArea}{dataString}";
|
|
||||||
var res = await SendCommandAsync(requeststring);
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> WriteBoolRegister (BRegister _toWrite, bool value, int _stationNumber = 1) {
|
||||||
|
|
||||||
|
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#WCS{_toWrite.BuildMewtocolIdent()}{(value ? "1" : "0")}";
|
||||||
|
|
||||||
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
|
||||||
|
return result.Success && result.Response.StartsWith($"%{ _stationNumber.ToString().PadLeft(2, '0')}#WC");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Number register reading / writing
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the given numeric register from PLC
|
/// Reads the given numeric register from PLC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -150,26 +109,39 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
if (numType == typeof(short)) {
|
if (numType == typeof(short)) {
|
||||||
|
|
||||||
var resultBytes = result.Response.ParseDTByteString(4);
|
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
|
||||||
var val = short.Parse(resultBytes, NumberStyles.HexNumber);
|
var val = short.Parse(resultBytes, NumberStyles.HexNumber);
|
||||||
(_toRead as NRegister<short>).LastValue = val;
|
(_toRead as NRegister<short>).LastValue = val;
|
||||||
|
|
||||||
} else if (numType == typeof(ushort)) {
|
} else if (numType == typeof(ushort)) {
|
||||||
var resultBytes = result.Response.ParseDTBytes(4);
|
|
||||||
var val = BitConverter.ToInt16(resultBytes);
|
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
|
||||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
var val = ushort.Parse(resultBytes, NumberStyles.HexNumber);
|
||||||
|
(_toRead as NRegister<ushort>).LastValue = val;
|
||||||
|
|
||||||
} else if (numType == typeof(int)) {
|
} else if (numType == typeof(int)) {
|
||||||
var resultBytes = result.Response.ParseDTBytes(8);
|
|
||||||
var val = BitConverter.ToInt16(resultBytes);
|
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
|
||||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
var val = int.Parse(resultBytes, NumberStyles.HexNumber);
|
||||||
|
(_toRead as NRegister<int>).LastValue = val;
|
||||||
|
|
||||||
} else if (numType == typeof(uint)) {
|
} else if (numType == typeof(uint)) {
|
||||||
var resultBytes = result.Response.ParseDTBytes(8);
|
|
||||||
var val = BitConverter.ToInt16(resultBytes);
|
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
|
||||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
var val = uint.Parse(resultBytes, NumberStyles.HexNumber);
|
||||||
|
(_toRead as NRegister<uint>).LastValue = val;
|
||||||
|
|
||||||
} else if (numType == typeof(float)) {
|
} else if (numType == typeof(float)) {
|
||||||
var resultBytes = result.Response.ParseDTBytes(8);
|
|
||||||
var val = BitConverter.ToSingle(resultBytes);
|
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
|
||||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
//convert to unsigned int first
|
||||||
|
var val = uint.Parse(resultBytes, NumberStyles.HexNumber);
|
||||||
|
|
||||||
|
byte[] floatVals = BitConverter.GetBytes(val);
|
||||||
|
float finalFloat = BitConverter.ToSingle(floatVals, 0);
|
||||||
|
|
||||||
|
(_toRead as NRegister<float>).LastValue = finalFloat;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalRes = new NRegisterResult<T> {
|
var finalRes = new NRegisterResult<T> {
|
||||||
@@ -187,7 +159,7 @@ namespace MewtocolNet {
|
|||||||
/// <param name="_toWrite">The register to write</param>
|
/// <param name="_toWrite">The register to write</param>
|
||||||
/// <param name="_stationNumber">Station number to access</param>
|
/// <param name="_stationNumber">Station number to access</param>
|
||||||
/// <returns>A result with the given NumberRegister and a result struct</returns>
|
/// <returns>A result with the given NumberRegister and a result struct</returns>
|
||||||
public async Task<NRegisterResult<T>> WriteNumRegister<T>(NRegister<T> _toWrite, T _value, int _stationNumber = 1) {
|
public async Task<bool> WriteNumRegister<T> (NRegister<T> _toWrite, T _value, int _stationNumber = 1) {
|
||||||
|
|
||||||
byte[] toWriteVal;
|
byte[] toWriteVal;
|
||||||
Type numType = typeof(T);
|
Type numType = typeof(T);
|
||||||
@@ -201,22 +173,38 @@ namespace MewtocolNet {
|
|||||||
} else if (numType == typeof(uint)) {
|
} else if (numType == typeof(uint)) {
|
||||||
toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_value));
|
toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_value));
|
||||||
} else if (numType == typeof(float)) {
|
} else if (numType == typeof(float)) {
|
||||||
toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_value));
|
|
||||||
|
var fl = _value as float?;
|
||||||
|
if (fl == null)
|
||||||
|
throw new NullReferenceException("Float cannot be null");
|
||||||
|
|
||||||
|
toWriteVal = BitConverter.GetBytes(fl.Value);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
toWriteVal = null;
|
toWriteVal = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#WD{_toWrite.BuildMewtocolIdent()}{toWriteVal.ToHexString()}";
|
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#WD{_toWrite.BuildMewtocolIdent()}{toWriteVal.ToHexString()}";
|
||||||
|
|
||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
|
|
||||||
return new NRegisterResult<T> {
|
return result.Success && result.Response.StartsWith($"%{ _stationNumber.ToString().PadLeft(2, '0')}#WD");
|
||||||
Result = result,
|
|
||||||
Register = _toWrite
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region String register reading / writing
|
||||||
|
|
||||||
public async Task<SRegisterResult> ReadStringRegister (SRegister _toRead, int _stationNumber = 1) {
|
public async Task<SRegisterResult> ReadStringRegister (SRegister _toRead, int _stationNumber = 1) {
|
||||||
|
|
||||||
|
//string is build up like this
|
||||||
|
//04 00 04 00 53 50 33 35 13
|
||||||
|
//0, 1 = reserved size
|
||||||
|
//1, 2 = current size
|
||||||
|
//3,4,5,6 = ASCII encoded chars (SP35)
|
||||||
|
//7,8 = checksum
|
||||||
|
|
||||||
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}";
|
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}";
|
||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
@@ -227,7 +215,7 @@ namespace MewtocolNet {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SRegisterResult> WriteStringRegister(SRegister _toWrite, string _value, int _stationNumber = 1) {
|
public async Task<bool> WriteStringRegister(SRegister _toWrite, string _value, int _stationNumber = 1) {
|
||||||
|
|
||||||
if (_value == null) _value = "";
|
if (_value == null) _value = "";
|
||||||
if(_value.Length > _toWrite.ReservedSize) {
|
if(_value.Length > _toWrite.ReservedSize) {
|
||||||
@@ -242,10 +230,7 @@ namespace MewtocolNet {
|
|||||||
Console.WriteLine($"reserved: {_toWrite.MemoryLength}, size: {_value.Length}");
|
Console.WriteLine($"reserved: {_toWrite.MemoryLength}, size: {_value.Length}");
|
||||||
|
|
||||||
var result = await SendCommandAsync(requeststring);
|
var result = await SendCommandAsync(requeststring);
|
||||||
return new SRegisterResult {
|
return result.Success && result.Response.StartsWith($"%{ _stationNumber.ToString().PadLeft(2, '0')}#WD");
|
||||||
Result = result,
|
|
||||||
Register = _toWrite
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MewtocolNet.Responses {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A class describing a register
|
|
||||||
/// </summary>
|
|
||||||
public abstract class Register : INotifyPropertyChanged {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets called whenever the value was changed
|
|
||||||
/// </summary>
|
|
||||||
public event Action<object> ValueChanged;
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
public int MemoryAdress { get; set; }
|
|
||||||
public int MemoryLength { get; set; }
|
|
||||||
public virtual string BuildMewtocolIdent() {
|
|
||||||
StringBuilder asciistring = new StringBuilder("D");
|
|
||||||
asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0'));
|
|
||||||
asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0'));
|
|
||||||
return asciistring.ToString();
|
|
||||||
}
|
|
||||||
protected void TriggerChangedEvnt(object changed) {
|
|
||||||
ValueChanged?.Invoke(changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TriggerNotifyChange () {
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetValueString () {
|
|
||||||
|
|
||||||
if (this is NRegister<short> shortReg) {
|
|
||||||
return shortReg.Value.ToString();
|
|
||||||
}
|
|
||||||
if (this is NRegister<ushort> ushortReg) {
|
|
||||||
return ushortReg.Value.ToString();
|
|
||||||
}
|
|
||||||
if (this is NRegister<int> intReg) {
|
|
||||||
return intReg.Value.ToString();
|
|
||||||
}
|
|
||||||
if (this is NRegister<uint> uintReg) {
|
|
||||||
return uintReg.Value.ToString();
|
|
||||||
}
|
|
||||||
if (this is NRegister<float> floatReg) {
|
|
||||||
return floatReg.Value.ToString();
|
|
||||||
}
|
|
||||||
else if (this is SRegister stringReg) {
|
|
||||||
return stringReg.Value.ToString();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Type of the register is not supported.";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a register containing a number
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the numeric value</typeparam>
|
|
||||||
public class NRegister<T> : Register {
|
|
||||||
|
|
||||||
public T NeedValue;
|
|
||||||
public T LastValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The value of the register
|
|
||||||
/// </summary>
|
|
||||||
public T Value {
|
|
||||||
get => LastValue;
|
|
||||||
set {
|
|
||||||
NeedValue = value;
|
|
||||||
TriggerChangedEvnt(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a register containing a number
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="_adress">Memory start adress max 99999</param>
|
|
||||||
/// <param name="_format">The format in which the variable is stored</param>
|
|
||||||
public NRegister(int _adress, string _name = null) {
|
|
||||||
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
|
||||||
MemoryAdress = _adress;
|
|
||||||
Name = _name;
|
|
||||||
Type numType = typeof(T);
|
|
||||||
if (numType == typeof(short)) {
|
|
||||||
MemoryLength = 0;
|
|
||||||
} else if (numType == typeof(ushort)) {
|
|
||||||
MemoryLength = 0;
|
|
||||||
} else if (numType == typeof(int)) {
|
|
||||||
MemoryLength = 1;
|
|
||||||
} else if (numType == typeof(uint)) {
|
|
||||||
MemoryLength = 1;
|
|
||||||
} else if (numType == typeof(float)) {
|
|
||||||
MemoryLength = 1;
|
|
||||||
} else {
|
|
||||||
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() {
|
|
||||||
return $"Adress: {MemoryAdress} Val: {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Result for a read/write operation
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the numeric value</typeparam>
|
|
||||||
public class NRegisterResult<T> {
|
|
||||||
public CommandResult Result { get; set; }
|
|
||||||
public NRegister<T> Register { get; set; }
|
|
||||||
|
|
||||||
public override string ToString() {
|
|
||||||
string errmsg = Result.Success ? "" : $", Error [{Result.ErrorDescription}]";
|
|
||||||
return $"Result [{Result.Success}], Register [{Register.ToString()}]{errmsg}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a register containing a string
|
|
||||||
/// </summary>
|
|
||||||
public class SRegister : Register {
|
|
||||||
|
|
||||||
private string lastVal = "";
|
|
||||||
public string Value {
|
|
||||||
|
|
||||||
get => lastVal;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public short ReservedSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a register containing a string
|
|
||||||
/// </summary>
|
|
||||||
public SRegister(int _adress, int _reservedStringSize, string _name = null) {
|
|
||||||
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
|
||||||
Name = _name;
|
|
||||||
MemoryAdress = _adress;
|
|
||||||
ReservedSize = (short)_reservedStringSize;
|
|
||||||
MemoryLength = 1 + (_reservedStringSize) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() {
|
|
||||||
return $"Adress: {MemoryAdress} Val: {Value}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string BuildMewtocolIdent() {
|
|
||||||
StringBuilder asciistring = new StringBuilder("D");
|
|
||||||
asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0'));
|
|
||||||
asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0'));
|
|
||||||
return asciistring.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetValueFromPLC (string val) {
|
|
||||||
lastVal = val;
|
|
||||||
TriggerChangedEvnt(this);
|
|
||||||
TriggerNotifyChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SRegisterResult {
|
|
||||||
public CommandResult Result { get; set; }
|
|
||||||
public SRegister Register { get; set; }
|
|
||||||
|
|
||||||
public override string ToString() {
|
|
||||||
string errmsg = Result.Success ? "" : $", Error [{Result.ErrorDescription}]";
|
|
||||||
return $"Result [{Result.Success}], Register [{Register.ToString()}]{errmsg}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
81
MewtocolNet/Mewtocol/RegisterAttributes/RegisterAttribute.cs
Normal file
81
MewtocolNet/Mewtocol/RegisterAttributes/RegisterAttribute.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MewtocolNet.RegisterAttributes {
|
||||||
|
|
||||||
|
public enum BitCount {
|
||||||
|
B16,
|
||||||
|
B32
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class RegisterAttribute : Attribute {
|
||||||
|
|
||||||
|
public int MemoryArea;
|
||||||
|
public int StringLength;
|
||||||
|
public RegisterType RegisterType;
|
||||||
|
public SpecialAddress SpecialAddress = SpecialAddress.None;
|
||||||
|
public BitCount BitCount;
|
||||||
|
public int AssignedBitIndex = -1;
|
||||||
|
|
||||||
|
|
||||||
|
public RegisterAttribute (int memoryArea, int stringLength = 1) {
|
||||||
|
|
||||||
|
MemoryArea = memoryArea;
|
||||||
|
StringLength = stringLength;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegisterAttribute (int memoryArea, RegisterType type) {
|
||||||
|
|
||||||
|
if (type.ToString().StartsWith("DT"))
|
||||||
|
throw new NotSupportedException("DT types are not supported for attribute register setups because the number type is casted automatically");
|
||||||
|
|
||||||
|
MemoryArea = memoryArea;
|
||||||
|
RegisterType = type;
|
||||||
|
SpecialAddress = SpecialAddress.None;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegisterAttribute (RegisterType type, SpecialAddress spAdress) {
|
||||||
|
|
||||||
|
if (type.ToString().StartsWith("DT"))
|
||||||
|
throw new NotSupportedException("DT types are not supported for attribute register setups because the number type is casted automatically");
|
||||||
|
|
||||||
|
RegisterType = type;
|
||||||
|
SpecialAddress = spAdress;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RegisterAttribute (int memoryArea, BitCount bitcount) {
|
||||||
|
|
||||||
|
MemoryArea = memoryArea;
|
||||||
|
StringLength = 0;
|
||||||
|
BitCount = bitcount;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegisterAttribute (int memoryArea, uint assignBit, BitCount bitcount) {
|
||||||
|
|
||||||
|
if(assignBit > 15 && bitcount == BitCount.B16) {
|
||||||
|
throw new NotSupportedException("The assignBit parameter cannot be greater than 15 in a 16 bit var");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assignBit > 31 && bitcount == BitCount.B32) {
|
||||||
|
throw new NotSupportedException("The assignBit parameter cannot be greater than 31 in a 32 bit var");
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryArea = memoryArea;
|
||||||
|
StringLength = 0;
|
||||||
|
BitCount = bitcount;
|
||||||
|
AssignedBitIndex = (int)assignBit;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MewtocolNet.RegisterAttributes {
|
||||||
|
|
||||||
|
public class RegisterCollectionBase : INotifyPropertyChanged {
|
||||||
|
|
||||||
|
public MewtocolInterface PLCInterface { get; set; }
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
internal void TriggerPropertyChanged (string propertyName = null) {
|
||||||
|
var handler = PropertyChanged;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
57
MewtocolNet/Mewtocol/RegisterEnums.cs
Normal file
57
MewtocolNet/Mewtocol/RegisterEnums.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
public enum RegisterType {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Physical input as a bool (Relay)
|
||||||
|
/// </summary>
|
||||||
|
X,
|
||||||
|
/// <summary>
|
||||||
|
/// Physical output as a bool (Relay)
|
||||||
|
/// </summary>
|
||||||
|
Y,
|
||||||
|
/// <summary>
|
||||||
|
/// Internal as a bool (Relay)
|
||||||
|
/// </summary>
|
||||||
|
R,
|
||||||
|
/// <summary>
|
||||||
|
/// Data area as a short (Register)
|
||||||
|
/// </summary>
|
||||||
|
DT_short,
|
||||||
|
/// <summary>
|
||||||
|
/// Data area as an unsigned short (Register)
|
||||||
|
/// </summary>
|
||||||
|
DT_ushort,
|
||||||
|
/// <summary>
|
||||||
|
/// Double data area as an integer (Register)
|
||||||
|
/// </summary>
|
||||||
|
DDT_int,
|
||||||
|
/// <summary>
|
||||||
|
/// Double data area as an unsigned integer (Register)
|
||||||
|
/// </summary>
|
||||||
|
DDT_uint,
|
||||||
|
/// <summary>
|
||||||
|
/// Double data area as an floating point number (Register)
|
||||||
|
/// </summary>
|
||||||
|
DDT_float,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SpecialAddress {
|
||||||
|
|
||||||
|
None,
|
||||||
|
A = -10,
|
||||||
|
B = -11,
|
||||||
|
C = -12,
|
||||||
|
D = -13,
|
||||||
|
E = -14,
|
||||||
|
F = -15,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -167,177 +167,4 @@ namespace MewtocolNet.Responses {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contact as bool contact
|
|
||||||
/// </summary>
|
|
||||||
public interface IBoolContact {
|
|
||||||
public string Name {get;set;}
|
|
||||||
public string Identifier {get;}
|
|
||||||
public bool? Value {get;set;}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A class describing a PLC contact
|
|
||||||
/// </summary>
|
|
||||||
public class Contact : IBoolContact {
|
|
||||||
public string Name {get;set;}
|
|
||||||
public PFX Prefix {get;set;}
|
|
||||||
public int Number {get;set;}
|
|
||||||
public ContactType Type {get;set;}
|
|
||||||
public string Endprefix {get;set;}
|
|
||||||
public string Asciistring {get => BuildMewtocolIdent();}
|
|
||||||
public string Identifier {get => Asciistring;}
|
|
||||||
public bool? Value {get;set;} = null;
|
|
||||||
public enum ContactType {
|
|
||||||
Unknown,
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PFX {
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
R
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new base Contact
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="_prefix">A prefix identifier eg. X,Y,R,L</param>
|
|
||||||
/// <param name="_number">The number of the PLC contact</param>
|
|
||||||
public Contact (PFX _prefix, int _number, string _name = "unknown") {
|
|
||||||
switch (_prefix) {
|
|
||||||
case PFX.X:
|
|
||||||
Type = ContactType.Input;
|
|
||||||
break;
|
|
||||||
case PFX.Y:
|
|
||||||
case PFX.R:
|
|
||||||
Type = ContactType.Output;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Prefix = _prefix;
|
|
||||||
Number = _number;
|
|
||||||
Name = _name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new base Contact
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="_prefix">A prefix identifier eg. X,Y,R,L</param>
|
|
||||||
/// <param name="_number">The number of the PLC contact</param>
|
|
||||||
public Contact (string _prefix, int _number, string _name = "unknown") {
|
|
||||||
PFX parsedPFX;
|
|
||||||
if(Enum.TryParse<PFX>(_prefix, true, out parsedPFX)) {
|
|
||||||
switch (parsedPFX) {
|
|
||||||
case PFX.X:
|
|
||||||
Type = ContactType.Input;
|
|
||||||
break;
|
|
||||||
case PFX.Y:
|
|
||||||
case PFX.R:
|
|
||||||
Type = ContactType.Output;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Prefix = parsedPFX;
|
|
||||||
Number = _number;
|
|
||||||
Name = _name;
|
|
||||||
} else {
|
|
||||||
throw new ArgumentException($"The prefix {_prefix} is no valid contact prefix");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Build contact from complete contact name
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="_contactName">Complete contact name e.g. Y1C, Y3D or X1</param>
|
|
||||||
public Contact (string _contactName, string _name = "unknown") {
|
|
||||||
|
|
||||||
string prefix = "";
|
|
||||||
int number = 0;
|
|
||||||
string endpfx = null;
|
|
||||||
|
|
||||||
Match regcheck = new Regex(@"(Y|X|R|L)([0-9]{1,3})?(.)?", RegexOptions.IgnoreCase).Match(_contactName);
|
|
||||||
if(regcheck.Success) {
|
|
||||||
|
|
||||||
/* for (int i = 0; i < regcheck.Groups.Count; i++) {
|
|
||||||
var item = regcheck.Groups[i].Value;
|
|
||||||
Console.WriteLine(item);
|
|
||||||
} */
|
|
||||||
|
|
||||||
prefix = regcheck.Groups[1].Value;
|
|
||||||
number = regcheck.Groups[2]?.Value != string.Empty ? Convert.ToInt32(regcheck.Groups[2].Value) : -1;
|
|
||||||
endpfx = regcheck.Groups[3]?.Value;
|
|
||||||
} else {
|
|
||||||
throw new ArgumentException($"The contact {_contactName} is no valid contact");
|
|
||||||
}
|
|
||||||
|
|
||||||
PFX parsedPFX;
|
|
||||||
if(Enum.TryParse<PFX>(prefix, true, out parsedPFX)) {
|
|
||||||
switch (parsedPFX) {
|
|
||||||
case PFX.X:
|
|
||||||
Type = ContactType.Input;
|
|
||||||
break;
|
|
||||||
case PFX.Y:
|
|
||||||
case PFX.R:
|
|
||||||
Type = ContactType.Output;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Prefix = parsedPFX;
|
|
||||||
Number = number;
|
|
||||||
Endprefix = endpfx;
|
|
||||||
Name = _name;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new ArgumentException($"The prefix {prefix} is no valid contact prefix");
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine(BuildMewtocolIdent());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Builds the mewtocol ascii contact identifier
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The identifier e.g. Y0001 or Y000A or X001C</returns>
|
|
||||||
public string BuildMewtocolIdent () {
|
|
||||||
string contactstring = "";
|
|
||||||
if(Endprefix == null) {
|
|
||||||
contactstring += Prefix;
|
|
||||||
contactstring += Number.ToString().PadLeft(4, '0');
|
|
||||||
} else {
|
|
||||||
contactstring += Prefix;
|
|
||||||
if(Number == -1) {
|
|
||||||
contactstring += "000" + Endprefix;
|
|
||||||
} else {
|
|
||||||
contactstring += (Number.ToString() + Endprefix).PadLeft(4, '0');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(string.IsNullOrEmpty(contactstring)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return contactstring;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts the class to a generic json compatible object
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public object ToGenericObject () {
|
|
||||||
return new {
|
|
||||||
Name = this.Name,
|
|
||||||
Identifier = this.Asciistring,
|
|
||||||
Value = this.Value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a copy of the contact
|
|
||||||
/// </summary>
|
|
||||||
public Contact ShallowCopy() {
|
|
||||||
return (Contact) this.MemberwiseClone();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
82
MewtocolNet/Mewtocol/Subregisters/BRegister.cs
Normal file
82
MewtocolNet/Mewtocol/Subregisters/BRegister.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using MewtocolNet;
|
||||||
|
|
||||||
|
namespace MewtocolNet.Responses {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a register containing a boolean
|
||||||
|
/// </summary>
|
||||||
|
public class BRegister : Register {
|
||||||
|
|
||||||
|
internal RegisterType RegType { get; set; }
|
||||||
|
internal SpecialAddress SpecialAddress { get; set; }
|
||||||
|
|
||||||
|
public bool NeedValue;
|
||||||
|
public bool LastValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value of the register
|
||||||
|
/// </summary>
|
||||||
|
public bool Value {
|
||||||
|
get => LastValue;
|
||||||
|
set {
|
||||||
|
NeedValue = value;
|
||||||
|
TriggerChangedEvnt(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a register containing a number
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_address">Memory start adress max 99999</param>
|
||||||
|
/// <param name="_type">Type of boolean register</param>
|
||||||
|
/// <param name="_name">Name of the register</param>
|
||||||
|
public BRegister (int _address, RegisterType _type = RegisterType.R, string _name = null) {
|
||||||
|
|
||||||
|
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
||||||
|
MemoryAdress = _address;
|
||||||
|
Name = _name;
|
||||||
|
|
||||||
|
RegType = _type;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a register containing a number
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_address">Memory start adress max 99999</param>
|
||||||
|
/// <param name="_type">Type of boolean register</param>
|
||||||
|
/// <param name="_name">Name of the register</param>
|
||||||
|
public BRegister (SpecialAddress _address, RegisterType _type = RegisterType.R, string _name = null) {
|
||||||
|
|
||||||
|
if (_address == SpecialAddress.None)
|
||||||
|
throw new NotSupportedException("Special adress cant be none");
|
||||||
|
|
||||||
|
SpecialAddress = _address;
|
||||||
|
Name = _name;
|
||||||
|
|
||||||
|
RegType = _type;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string BuildMewtocolIdent () {
|
||||||
|
|
||||||
|
//build area code from register type
|
||||||
|
StringBuilder asciistring = new StringBuilder(RegType.ToString());
|
||||||
|
if(SpecialAddress == SpecialAddress.None) {
|
||||||
|
asciistring.Append(MemoryAdress.ToString().PadLeft(4, '0'));
|
||||||
|
} else {
|
||||||
|
asciistring.Append(SpecialAddress.ToString().PadLeft(4, '0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return asciistring.ToString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return $"Adress: {MemoryAdress} Val: {Value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
MewtocolNet/Mewtocol/Subregisters/BRegisterResult.cs
Normal file
13
MewtocolNet/Mewtocol/Subregisters/BRegisterResult.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace MewtocolNet.Responses {
|
||||||
|
public class BRegisterResult {
|
||||||
|
|
||||||
|
public CommandResult Result { get; set; }
|
||||||
|
public BRegister Register { get; set; }
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
string errmsg = Result.Success ? "" : $", Error [{Result.ErrorDescription}]";
|
||||||
|
return $"Result [{Result.Success}], Register [{Register.ToString()}]{errmsg}";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
61
MewtocolNet/Mewtocol/Subregisters/NRegister.cs
Normal file
61
MewtocolNet/Mewtocol/Subregisters/NRegister.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MewtocolNet.Responses {
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a register containing a number
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the numeric value</typeparam>
|
||||||
|
public class NRegister<T> : Register {
|
||||||
|
|
||||||
|
public T NeedValue;
|
||||||
|
public T LastValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value of the register
|
||||||
|
/// </summary>
|
||||||
|
public T Value {
|
||||||
|
get => LastValue;
|
||||||
|
set {
|
||||||
|
NeedValue = value;
|
||||||
|
TriggerChangedEvnt(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a register containing a number
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_adress">Memory start adress max 99999</param>
|
||||||
|
/// <param name="_format">The format in which the variable is stored</param>
|
||||||
|
public NRegister(int _adress, string _name = null, bool isBitwise = false) {
|
||||||
|
|
||||||
|
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
||||||
|
MemoryAdress = _adress;
|
||||||
|
Name = _name;
|
||||||
|
Type numType = typeof(T);
|
||||||
|
if (numType == typeof(short)) {
|
||||||
|
MemoryLength = 0;
|
||||||
|
} else if (numType == typeof(ushort)) {
|
||||||
|
MemoryLength = 0;
|
||||||
|
} else if (numType == typeof(int)) {
|
||||||
|
MemoryLength = 1;
|
||||||
|
} else if (numType == typeof(uint)) {
|
||||||
|
MemoryLength = 1;
|
||||||
|
} else if (numType == typeof(float)) {
|
||||||
|
MemoryLength = 1;
|
||||||
|
} else {
|
||||||
|
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
|
||||||
|
}
|
||||||
|
|
||||||
|
isUsedBitwise = isBitwise;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return $"Adress: {MemoryAdress} Val: {Value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
19
MewtocolNet/Mewtocol/Subregisters/NRegisterResult.cs
Normal file
19
MewtocolNet/Mewtocol/Subregisters/NRegisterResult.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace MewtocolNet.Responses {
|
||||||
|
/// <summary>
|
||||||
|
/// Result for a read/write operation
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the numeric value</typeparam>
|
||||||
|
public class NRegisterResult<T> {
|
||||||
|
public CommandResult Result { get; set; }
|
||||||
|
public NRegister<T> Register { get; set; }
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
string errmsg = Result.Success ? "" : $", Error [{Result.ErrorDescription}]";
|
||||||
|
return $"Result [{Result.Success}], Register [{Register.ToString()}]{errmsg}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
153
MewtocolNet/Mewtocol/Subregisters/Register.cs
Normal file
153
MewtocolNet/Mewtocol/Subregisters/Register.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MewtocolNet.Responses {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A class describing a register
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Register : INotifyPropertyChanged {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets called whenever the value was changed
|
||||||
|
/// </summary>
|
||||||
|
public event Action<object> ValueChanged;
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int MemoryAdress { get; set; }
|
||||||
|
public int MemoryLength { get; set; }
|
||||||
|
internal bool isUsedBitwise { get; set; }
|
||||||
|
|
||||||
|
public virtual string BuildMewtocolIdent() {
|
||||||
|
|
||||||
|
StringBuilder asciistring = new StringBuilder("D");
|
||||||
|
asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0'));
|
||||||
|
asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0'));
|
||||||
|
return asciistring.ToString();
|
||||||
|
|
||||||
|
}
|
||||||
|
internal void TriggerChangedEvnt(object changed) {
|
||||||
|
ValueChanged?.Invoke(changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TriggerNotifyChange () {
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the starting memory are either numeric or A,B,C,D etc for special areas like inputs
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetStartingMemoryArea () {
|
||||||
|
|
||||||
|
if (this is BRegister bReg && bReg.SpecialAddress != SpecialAddress.None) {
|
||||||
|
return bReg.SpecialAddress.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.MemoryAdress.ToString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current value in the adress as a string
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetValueString () {
|
||||||
|
|
||||||
|
if (this is NRegister<short> shortReg) {
|
||||||
|
return $"{shortReg.Value}{(isUsedBitwise ? $" [{shortReg.GetBitwise().ToBitString()}]" : "")}";
|
||||||
|
}
|
||||||
|
if (this is NRegister<ushort> ushortReg) {
|
||||||
|
return ushortReg.Value.ToString();
|
||||||
|
}
|
||||||
|
if (this is NRegister<int> intReg) {
|
||||||
|
return $"{intReg.Value}{(isUsedBitwise ? $" [{intReg.GetBitwise().ToBitString()}]" : "")}";
|
||||||
|
}
|
||||||
|
if (this is NRegister<uint> uintReg) {
|
||||||
|
return uintReg.Value.ToString();
|
||||||
|
}
|
||||||
|
if (this is NRegister<float> floatReg) {
|
||||||
|
return floatReg.Value.ToString();
|
||||||
|
}
|
||||||
|
if (this is BRegister boolReg) {
|
||||||
|
return boolReg.Value.ToString();
|
||||||
|
}
|
||||||
|
if (this is SRegister stringReg) {
|
||||||
|
return stringReg.Value.ToString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Type of the register is not supported.";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the register bitwise if its a 16 or 32 bit int
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A bitarray</returns>
|
||||||
|
public BitArray GetBitwise () {
|
||||||
|
|
||||||
|
if (this is NRegister<short> shortReg) {
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(shortReg.Value);
|
||||||
|
BitArray bitAr = new BitArray(bytes);
|
||||||
|
return bitAr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this is NRegister<int> intReg) {
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(intReg.Value);
|
||||||
|
BitArray bitAr = new BitArray(bytes);
|
||||||
|
return bitAr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetRegisterString () {
|
||||||
|
|
||||||
|
if (this is NRegister<short> shortReg) {
|
||||||
|
return "DT";
|
||||||
|
}
|
||||||
|
if (this is NRegister<ushort> ushortReg) {
|
||||||
|
return "DT";
|
||||||
|
}
|
||||||
|
if (this is NRegister<int> intReg) {
|
||||||
|
return "DDT";
|
||||||
|
}
|
||||||
|
if (this is NRegister<uint> uintReg) {
|
||||||
|
return "DDT";
|
||||||
|
}
|
||||||
|
if (this is NRegister<float> floatReg) {
|
||||||
|
return "DDT";
|
||||||
|
}
|
||||||
|
if (this is BRegister boolReg) {
|
||||||
|
return boolReg.RegType.ToString();
|
||||||
|
}
|
||||||
|
if (this is SRegister stringReg) {
|
||||||
|
return "DT";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Type of the register is not supported.";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetRegisterPLCName () {
|
||||||
|
|
||||||
|
return $"{GetRegisterString()}{MemoryAdress}";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
58
MewtocolNet/Mewtocol/Subregisters/SRegister.cs
Normal file
58
MewtocolNet/Mewtocol/Subregisters/SRegister.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MewtocolNet.Responses {
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a register containing a string
|
||||||
|
/// </summary>
|
||||||
|
public class SRegister : Register {
|
||||||
|
|
||||||
|
private string lastVal = "";
|
||||||
|
public string Value {
|
||||||
|
|
||||||
|
get => lastVal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public short ReservedSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a register containing a string
|
||||||
|
/// </summary>
|
||||||
|
public SRegister(int _adress, int _reservedStringSize, string _name = null) {
|
||||||
|
|
||||||
|
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
||||||
|
Name = _name;
|
||||||
|
MemoryAdress = _adress;
|
||||||
|
ReservedSize = (short)_reservedStringSize;
|
||||||
|
|
||||||
|
//calc mem length
|
||||||
|
var wordsize = (double)_reservedStringSize / 2;
|
||||||
|
if (wordsize % 2 != 0) {
|
||||||
|
wordsize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryLength = (int)Math.Round(wordsize + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return $"Adress: {MemoryAdress} Val: {Value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string BuildMewtocolIdent() {
|
||||||
|
StringBuilder asciistring = new StringBuilder("D");
|
||||||
|
asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0'));
|
||||||
|
asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0'));
|
||||||
|
return asciistring.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValueFromPLC (string val) {
|
||||||
|
lastVal = val;
|
||||||
|
TriggerChangedEvnt(this);
|
||||||
|
TriggerNotifyChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
MewtocolNet/Mewtocol/Subregisters/SRegisterResult.cs
Normal file
15
MewtocolNet/Mewtocol/Subregisters/SRegisterResult.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace MewtocolNet.Responses {
|
||||||
|
public class SRegisterResult {
|
||||||
|
public CommandResult Result { get; set; }
|
||||||
|
public SRegister Register { get; set; }
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
string errmsg = Result.Success ? "" : $", Error [{Result.ErrorDescription}]";
|
||||||
|
return $"Result [{Result.Success}], Register [{Register.ToString()}]{errmsg}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<PackageId>AppLogger</PackageId>
|
<PackageId>AppLogger</PackageId>
|
||||||
<Version>0.1.5</Version>
|
<Version>0.1.5</Version>
|
||||||
<Authors>Felix Weiss</Authors>
|
<Authors>Felix Weiss</Authors>
|
||||||
|
|||||||
Reference in New Issue
Block a user