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.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using MewtocolNet;
|
||||
using MewtocolNet.Responses;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using System.Collections;
|
||||
using MewtocolNet.Logging;
|
||||
|
||||
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 () => {
|
||||
|
||||
//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");
|
||||
TestRegisters registers = new TestRegisters();
|
||||
|
||||
interf.AddRegister<short>("Cooler Status",1204);
|
||||
interf.AddRegister<string>(1101, 4);
|
||||
|
||||
interf.WithPoller();
|
||||
|
||||
interf.RegisterChanged += (o) => {
|
||||
Console.WriteLine($"DT{o.MemoryAdress} {(o.Name != null ? $"({o.Name}) " : "")}changed to {o.GetValueString()}");
|
||||
};
|
||||
//attaching the register collection and an automatic poller
|
||||
interf.WithRegisterCollection(registers).WithPoller();
|
||||
|
||||
await interf.ConnectAsync(
|
||||
(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
|
||||
var statusNum = (NRegister<short>)interf.Registers[1204];
|
||||
Console.WriteLine($"Status num is: {statusNum.Value}");
|
||||
//writing a value to the registers
|
||||
Task.Factory.StartNew(async () => {
|
||||
|
||||
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();
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.Responses;
|
||||
|
||||
namespace MewtocolNet {
|
||||
@@ -15,7 +16,6 @@ namespace MewtocolNet {
|
||||
|
||||
internal event Action PolledCycle;
|
||||
internal CancellationTokenSource cTokenAutoUpdater;
|
||||
internal bool isWriting;
|
||||
internal bool ContinousReaderRunning;
|
||||
internal bool usePoller = false;
|
||||
|
||||
@@ -30,30 +30,49 @@ namespace MewtocolNet {
|
||||
|
||||
cTokenAutoUpdater = new CancellationTokenSource();
|
||||
|
||||
Console.WriteLine("Attaching cont reader");
|
||||
Logger.Log("Poller is attaching", LogLevel.Info, this);
|
||||
|
||||
Task.Factory.StartNew(async () => {
|
||||
|
||||
var plcinf = await GetPLCInfoAsync();
|
||||
if (plcinf == null) {
|
||||
Console.WriteLine("PLC is not reachable");
|
||||
throw new Exception("PLC is not reachable");
|
||||
Logger.Log("PLC not reachable, stopping logger", LogLevel.Info, this);
|
||||
return;
|
||||
}
|
||||
if (!plcinf.OperationMode.RunMode) {
|
||||
Console.WriteLine("PLC is not running");
|
||||
throw new Exception("PLC is not running");
|
||||
|
||||
PolledCycle += MewtocolInterface_PolledCycle;
|
||||
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;
|
||||
|
||||
while (true) {
|
||||
|
||||
//dont update when currently writing a var
|
||||
if (isWriting) {
|
||||
continue;
|
||||
//do priority tasks first
|
||||
if(PriorityTasks.Count > 0) {
|
||||
|
||||
await PriorityTasks.FirstOrDefault(x => !x.IsCompleted);
|
||||
|
||||
}
|
||||
|
||||
await Task.Delay(pollingDelayMs);
|
||||
//await Task.Delay(pollingDelayMs);
|
||||
|
||||
foreach (var registerPair in Registers) {
|
||||
|
||||
var reg = registerPair.Value;
|
||||
@@ -102,7 +121,17 @@ namespace MewtocolNet {
|
||||
InvokeRegisterChanged(floatReg);
|
||||
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 readout = (await ReadStringRegister(stringReg, stationNumber)).Register.Value;
|
||||
if (lastVal != readout) {
|
||||
@@ -127,42 +156,68 @@ namespace MewtocolNet {
|
||||
|
||||
#region Register Adding
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds a PLC memory register to the watchlist <para/>
|
||||
/// The registers can be read back by attaching <see cref="WithPoller"/>
|
||||
/// </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="_length">The length of the string (Can be ignored for other types)</param>
|
||||
public void AddRegister<T> (int _address, int _length = 1) {
|
||||
/// <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 (int _address, RegisterType _type, string _name = null) {
|
||||
|
||||
Type regType = typeof(T);
|
||||
|
||||
if (regType == typeof(short)) {
|
||||
Registers.Add(_address, new NRegister<short>(_address));
|
||||
} 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");
|
||||
//as number registers
|
||||
if (_type == RegisterType.DT_short) {
|
||||
Registers.Add(_address, new NRegister<short>(_address, _name));
|
||||
return;
|
||||
}
|
||||
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="_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>
|
||||
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);
|
||||
|
||||
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)) {
|
||||
Registers.Add(_address, new NRegister<short>(_address, _name));
|
||||
Registers.Add(_address, new NRegister<short>(_address, _name, _isBitwise));
|
||||
} else if (regType == typeof(ushort)) {
|
||||
Registers.Add(_address, new NRegister<ushort>(_address, _name));
|
||||
} 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)) {
|
||||
Registers.Add(_address, new NRegister<uint>(_address, _name));
|
||||
} else if (regType == typeof(float)) {
|
||||
Registers.Add(_address, new NRegister<float>(_address, _name));
|
||||
} else if (regType == typeof(string)) {
|
||||
Registers.Add(_address, new SRegister(_address, _length, _name));
|
||||
} else if (regType == typeof(bool)) {
|
||||
Registers.Add(_address, new BRegister(_address, RegisterType.R, _name));
|
||||
} else {
|
||||
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
|
||||
$"Allowed are: short, ushort, int, uint, float and string");
|
||||
@@ -208,6 +276,21 @@ namespace MewtocolNet {
|
||||
|
||||
#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) {
|
||||
|
||||
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.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using MewtocolNet.Responses;
|
||||
using System.Collections;
|
||||
|
||||
namespace MewtocolNet {
|
||||
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();
|
||||
Byte[] bytes = ascii.GetBytes(_str.ToUpper());
|
||||
byte[] bytes = ascii.GetBytes(_str.ToUpper());
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static string BuildBCCFrame (this string asciiArr) {
|
||||
internal static string BuildBCCFrame (this string asciiArr) {
|
||||
Encoding ae = Encoding.ASCII;
|
||||
byte[] b = ae.GetBytes(asciiArr);
|
||||
byte xorTotalByte = 0;
|
||||
@@ -24,7 +35,7 @@ namespace MewtocolNet {
|
||||
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);
|
||||
if(res.Success) {
|
||||
string val = res.Groups[2].Value;
|
||||
@@ -33,7 +44,7 @@ namespace MewtocolNet {
|
||||
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);
|
||||
if (res.Success) {
|
||||
string val = res.Groups[2].Value;
|
||||
@@ -42,16 +53,46 @@ namespace MewtocolNet {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string ParseDTString (this string _onString) {
|
||||
var res = new Regex(@"\%([0-9]{2})\$RD.{8}(.*)...").Match(_onString);
|
||||
if(res.Success) {
|
||||
internal static bool? ParseRCSingleBit (this string _onString, int _blockSize = 4) {
|
||||
var res = new Regex(@"\%([0-9]{2})\$RC(.)").Match(_onString);
|
||||
if (res.Success) {
|
||||
string val = res.Groups[2].Value;
|
||||
return val.GetStringFromAsciiHex();
|
||||
return val == "1";
|
||||
}
|
||||
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();
|
||||
//06000600
|
||||
short stringSize = (short)_inString.Length;
|
||||
@@ -62,13 +103,13 @@ namespace MewtocolNet {
|
||||
//string count actual bytes
|
||||
sb.Append(sizeBytes);
|
||||
//actual string content
|
||||
sb.Append(_inString.GetAsciiHexFromString().PadRight(_stringReservedSize * 2, '0'));
|
||||
sb.Append(_inString.GetAsciiHexFromString());
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
public static string GetStringFromAsciiHex (this string input) {
|
||||
internal static string GetStringFromAsciiHex (this string input) {
|
||||
if (input.Length % 2 != 0)
|
||||
throw new ArgumentException("input not a hex string");
|
||||
byte[] bytes = new byte[input.Length / 2];
|
||||
@@ -79,19 +120,19 @@ namespace MewtocolNet {
|
||||
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);
|
||||
return bytes.ToHexString();
|
||||
}
|
||||
|
||||
public static byte[] HexStringToByteArray(this string hex) {
|
||||
internal static byte[] HexStringToByteArray(this string hex) {
|
||||
return Enumerable.Range(0, hex.Length)
|
||||
.Where(x => x % 2 == 0)
|
||||
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static string ToHexString (this byte[] arr) {
|
||||
internal static string ToHexString (this byte[] arr) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var b in arr) {
|
||||
sb.Append(b.ToString("X2"));
|
||||
@@ -99,23 +140,6 @@ namespace MewtocolNet {
|
||||
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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using MewtocolNet.Responses;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.Logging;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using MewtocolNet.Logging;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -33,14 +40,11 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// The registered data registers of the PLC
|
||||
/// </summary>
|
||||
public Dictionary<int, Register> Registers { get; set; } = new();
|
||||
|
||||
private CancellationTokenSource tokenSource;
|
||||
public Dictionary<int, Register> Registers { get; set; } = new Dictionary<int, Register>();
|
||||
|
||||
private string ip {get;set;}
|
||||
private int port {get;set;}
|
||||
private int stationNumber {get;set;}
|
||||
private int pollingDelayMs {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// The current IP of the PLC connection
|
||||
@@ -55,6 +59,7 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
public int StationNumber => stationNumber;
|
||||
|
||||
internal List<Task> PriorityTasks { get; set; } = new List<Task>();
|
||||
|
||||
#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
|
||||
@@ -99,7 +113,10 @@ namespace MewtocolNet {
|
||||
|
||||
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);
|
||||
|
||||
@@ -119,8 +136,10 @@ namespace MewtocolNet {
|
||||
|
||||
} else {
|
||||
|
||||
if (OnFailed != null)
|
||||
if (OnFailed != null) {
|
||||
OnFailed();
|
||||
Logger.Log("Initial connection failed", LogLevel.Info, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -132,9 +151,8 @@ namespace MewtocolNet {
|
||||
/// Attaches a poller to the interface that continously
|
||||
/// polls the registered data registers and writes the values to them
|
||||
/// </summary>
|
||||
public MewtocolInterface WithPoller (int pollerDelayMs = 50) {
|
||||
public MewtocolInterface WithPoller () {
|
||||
|
||||
pollingDelayMs = pollerDelayMs;
|
||||
usePoller = true;
|
||||
|
||||
return this;
|
||||
@@ -143,6 +161,258 @@ namespace MewtocolNet {
|
||||
|
||||
#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
|
||||
|
||||
@@ -158,7 +428,27 @@ namespace MewtocolNet {
|
||||
_msg += "\r";
|
||||
|
||||
//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) {
|
||||
return new CommandResult {
|
||||
@@ -195,16 +485,12 @@ namespace MewtocolNet {
|
||||
|
||||
private async Task<string> SendSingleBlock (string _blockString) {
|
||||
|
||||
if(isWriting) {
|
||||
return null;
|
||||
}
|
||||
|
||||
tokenSource = new CancellationTokenSource();
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
|
||||
using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) {
|
||||
|
||||
try {
|
||||
await client.ConnectAsync(ip, port, tokenSource.Token);
|
||||
await client.ConnectAsync(ip, port);
|
||||
} catch(SocketException) {
|
||||
return null;
|
||||
}
|
||||
@@ -213,9 +499,9 @@ namespace MewtocolNet {
|
||||
var message = _blockString.ToHexASCIIBytes();
|
||||
var messageAscii = BitConverter.ToString(message).Replace("-", " ");
|
||||
//send request
|
||||
isWriting = true;
|
||||
using (var sendStream = new MemoryStream(message)) {
|
||||
await sendStream.CopyToAsync(stream);
|
||||
Logger.Log($"OUT MSG: {_blockString}", LogLevel.Critical, this);
|
||||
//log message sent
|
||||
ASCIIEncoding enc = new ASCIIEncoding();
|
||||
string characters = enc.GetString(message);
|
||||
@@ -228,7 +514,8 @@ namespace MewtocolNet {
|
||||
response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes));
|
||||
}
|
||||
while (stream.DataAvailable);
|
||||
isWriting = false;
|
||||
sw.Stop();
|
||||
Logger.Log($"IN MSG ({(int)sw.Elapsed.TotalMilliseconds}ms): {_blockString}", LogLevel.Critical, this);
|
||||
return response.ToString();
|
||||
}
|
||||
|
||||
@@ -238,6 +525,20 @@ namespace MewtocolNet {
|
||||
|
||||
|
||||
#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 {
|
||||
|
||||
#region High level command handling
|
||||
#region PLC info getters
|
||||
|
||||
/// <summary>
|
||||
/// Gets generic information about the PLC
|
||||
@@ -49,91 +49,50 @@ namespace MewtocolNet {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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) {
|
||||
#endregion
|
||||
|
||||
//re order by contact pfx for faster querying
|
||||
_contactsToRead = _contactsToRead.OrderBy(x=>x.Prefix).ToList();
|
||||
#region Bool register reading / writing
|
||||
|
||||
//return list
|
||||
List<IBoolContact> returnContacts = new List<IBoolContact>();
|
||||
public async Task<BRegisterResult> ReadBoolRegister (BRegister _toRead, int _stationNumber = 1) {
|
||||
|
||||
//grouped by 8 each
|
||||
List<List<Contact>> nestedContacts = new List<List<Contact>>();
|
||||
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RCS{_toRead.BuildMewtocolIdent()}";
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
|
||||
//group into max 8 contacts list
|
||||
List<Contact> tempGroup = new List<Contact>();
|
||||
for (int i = 0; i < _contactsToRead.Count; i++) {
|
||||
tempGroup.Add(_contactsToRead[i]);
|
||||
//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>();
|
||||
}
|
||||
if(!result.Success) {
|
||||
return new BRegisterResult {
|
||||
Result = result,
|
||||
Register = _toRead
|
||||
};
|
||||
}
|
||||
|
||||
//make task for each group
|
||||
foreach (var group in nestedContacts) {
|
||||
//regex for getting values
|
||||
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;
|
||||
var resultBool = result.Response.ParseRCSingleBit();
|
||||
if(resultBool != null) {
|
||||
_toRead.LastValue = resultBool.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a boolen value to the given contact
|
||||
/// </summary>
|
||||
/// <param name="_contact">The contact to write</param>
|
||||
/// <param name="_value">The boolean state to write</param>
|
||||
/// <param name="_stationNumber">Station Number (optional)</param>
|
||||
/// <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;
|
||||
var finalRes = new BRegisterResult {
|
||||
Result = result,
|
||||
Register = _toRead
|
||||
};
|
||||
|
||||
return finalRes;
|
||||
|
||||
}
|
||||
|
||||
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>
|
||||
/// Reads the given numeric register from PLC
|
||||
/// </summary>
|
||||
@@ -150,26 +109,39 @@ namespace MewtocolNet {
|
||||
|
||||
if (numType == typeof(short)) {
|
||||
|
||||
var resultBytes = result.Response.ParseDTByteString(4);
|
||||
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
|
||||
var val = short.Parse(resultBytes, NumberStyles.HexNumber);
|
||||
(_toRead as NRegister<short>).LastValue = val;
|
||||
|
||||
} else if (numType == typeof(ushort)) {
|
||||
var resultBytes = result.Response.ParseDTBytes(4);
|
||||
var val = BitConverter.ToInt16(resultBytes);
|
||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
||||
|
||||
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
|
||||
var val = ushort.Parse(resultBytes, NumberStyles.HexNumber);
|
||||
(_toRead as NRegister<ushort>).LastValue = val;
|
||||
|
||||
} else if (numType == typeof(int)) {
|
||||
var resultBytes = result.Response.ParseDTBytes(8);
|
||||
var val = BitConverter.ToInt16(resultBytes);
|
||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
||||
|
||||
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
|
||||
var val = int.Parse(resultBytes, NumberStyles.HexNumber);
|
||||
(_toRead as NRegister<int>).LastValue = val;
|
||||
|
||||
} else if (numType == typeof(uint)) {
|
||||
var resultBytes = result.Response.ParseDTBytes(8);
|
||||
var val = BitConverter.ToInt16(resultBytes);
|
||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
||||
|
||||
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
|
||||
var val = uint.Parse(resultBytes, NumberStyles.HexNumber);
|
||||
(_toRead as NRegister<uint>).LastValue = val;
|
||||
|
||||
} else if (numType == typeof(float)) {
|
||||
var resultBytes = result.Response.ParseDTBytes(8);
|
||||
var val = BitConverter.ToSingle(resultBytes);
|
||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
||||
|
||||
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
|
||||
//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> {
|
||||
@@ -187,7 +159,7 @@ namespace MewtocolNet {
|
||||
/// <param name="_toWrite">The register to write</param>
|
||||
/// <param name="_stationNumber">Station number to access</param>
|
||||
/// <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;
|
||||
Type numType = typeof(T);
|
||||
@@ -201,22 +173,38 @@ namespace MewtocolNet {
|
||||
} else if (numType == typeof(uint)) {
|
||||
toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_value));
|
||||
} 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 {
|
||||
toWriteVal = null;
|
||||
}
|
||||
|
||||
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#WD{_toWrite.BuildMewtocolIdent()}{toWriteVal.ToHexString()}";
|
||||
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
|
||||
return new NRegisterResult<T> {
|
||||
Result = result,
|
||||
Register = _toWrite
|
||||
};
|
||||
return result.Success && result.Response.StartsWith($"%{ _stationNumber.ToString().PadLeft(2, '0')}#WD");
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region String register reading / writing
|
||||
|
||||
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()}";
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
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.Length > _toWrite.ReservedSize) {
|
||||
@@ -242,10 +230,7 @@ namespace MewtocolNet {
|
||||
Console.WriteLine($"reserved: {_toWrite.MemoryLength}, size: {_value.Length}");
|
||||
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
return new SRegisterResult {
|
||||
Result = result,
|
||||
Register = _toWrite
|
||||
};
|
||||
return result.Success && result.Response.StartsWith($"%{ _stationNumber.ToString().PadLeft(2, '0')}#WD");
|
||||
}
|
||||
|
||||
#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>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageId>AppLogger</PackageId>
|
||||
<Version>0.1.5</Version>
|
||||
<Authors>Felix Weiss</Authors>
|
||||
|
||||
Reference in New Issue
Block a user