mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Projektdateien hinzufügen.
This commit is contained in:
140
MewtocolNet/Mewtocol/DynamicInterface.cs
Normal file
140
MewtocolNet/Mewtocol/DynamicInterface.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MewtocolNet.Responses;
|
||||
|
||||
namespace MewtocolNet {
|
||||
public partial class MewtocolInterface {
|
||||
|
||||
public event Action<Register> RegisterChanged;
|
||||
|
||||
internal CancellationTokenSource cTokenAutoUpdater;
|
||||
protected internal bool isWriting = false;
|
||||
|
||||
|
||||
public bool ContinousReaderRunning { get; set; }
|
||||
public List<Register> Registers { get; set; } = new List<Register>();
|
||||
public List<Contact> Contacts { get; set; } = new List<Contact>();
|
||||
|
||||
/// <summary>
|
||||
/// Trys to connect to the PLC by the IP given in the constructor
|
||||
/// </summary>
|
||||
/// <param name="OnConnected">Gets called when a connection with a PLC was established</param>
|
||||
/// <param name="OnFailed">Gets called when an error or timeout during connection occurs</param>
|
||||
/// <returns></returns>
|
||||
public async Task<MewtocolInterface> ConnectAsync (Action<PLCInfo> OnConnected = null, Action OnFailed = null) {
|
||||
|
||||
var plcinf = await GetPLCInfoAsync();
|
||||
|
||||
if(plcinf is not null) {
|
||||
|
||||
if(OnConnected != null) OnConnected(plcinf);
|
||||
|
||||
} else {
|
||||
|
||||
if(OnFailed != null) OnFailed();
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
#region Register Adding
|
||||
|
||||
/// <summary>
|
||||
/// Adds a PLC memory register to the watchlist <para/>
|
||||
/// The registers can be read back by attaching <see cref="MewtocolInterfaceExtensions.AttachContinousReader(Task{MewtocolInterface}, int)"/>
|
||||
/// <para/>
|
||||
/// to the end of a <see cref="MewtocolInterface.ConnectAsync(Action{PLCInfo}, Action)"/> method
|
||||
/// </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) {
|
||||
|
||||
Type regType = typeof(T);
|
||||
|
||||
if (regType == typeof(short)) {
|
||||
Registers.Add(new NRegister<short>(_address));
|
||||
} else if (regType == typeof(ushort)) {
|
||||
Registers.Add(new NRegister<ushort>(_address));
|
||||
} else if (regType == typeof(int)) {
|
||||
Registers.Add(new NRegister<int>(_address));
|
||||
} else if (regType == typeof(uint)) {
|
||||
Registers.Add(new NRegister<uint>(_address));
|
||||
} else if (regType == typeof(float)) {
|
||||
Registers.Add(new NRegister<float>(_address));
|
||||
} else if (regType == typeof(string)) {
|
||||
Registers.Add(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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a PLC memory register to the watchlist <para/>
|
||||
/// The registers can be read back by attaching <see cref="MewtocolInterfaceExtensions.AttachContinousReader(Task{MewtocolInterface}, int)"/>
|
||||
/// <para/>
|
||||
/// to the end of a <see cref="MewtocolInterface.ConnectAsync(Action{PLCInfo}, Action)"/> method
|
||||
/// </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="_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) {
|
||||
|
||||
Type regType = typeof(T);
|
||||
|
||||
if (regType == typeof(short)) {
|
||||
Registers.Add(new NRegister<short>(_address, _name));
|
||||
} else if (regType == typeof(ushort)) {
|
||||
Registers.Add(new NRegister<ushort>(_address, _name));
|
||||
} else if (regType == typeof(int)) {
|
||||
Registers.Add(new NRegister<int>(_address, _name));
|
||||
} else if (regType == typeof(uint)) {
|
||||
Registers.Add(new NRegister<uint>(_address, _name));
|
||||
} else if (regType == typeof(float)) {
|
||||
Registers.Add(new NRegister<float>(_address, _name));
|
||||
} else if (regType == typeof(string)) {
|
||||
Registers.Add(new SRegister(_address, _length, _name));
|
||||
} else {
|
||||
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
|
||||
$"Allowed are: short, ushort, int, uint, float and string");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal void InvokeRegisterChanged (Register reg) {
|
||||
|
||||
RegisterChanged?.Invoke (reg);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
46
MewtocolNet/Mewtocol/LinkedLists.cs
Normal file
46
MewtocolNet/Mewtocol/LinkedLists.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MewtocolNet.Links {
|
||||
|
||||
public class LinkedData {
|
||||
|
||||
public static Dictionary<int, string> ErrorCodes = new System.Collections.Generic.Dictionary<int, string> {
|
||||
|
||||
{21, "NACK error"},
|
||||
{22, "WACK error"},
|
||||
{23, "Station number overlap"},
|
||||
{24, "Transmission error"},
|
||||
{25, "Hardware error"},
|
||||
{26, "Station number setting error"},
|
||||
{27, "Frame over error"},
|
||||
{28, "No response error"},
|
||||
{29, "Buffer close error"},
|
||||
{30, "Timeout error"},
|
||||
{32, "Transmission impossible"},
|
||||
{33, "Communication stop"},
|
||||
{36, "No local station"},
|
||||
{38, "Other com error"},
|
||||
{40, "BCC error"},
|
||||
{41, "Format error"},
|
||||
{42, "Not supported error"},
|
||||
{43, "Procedure error"},
|
||||
{50, "Link setting error"},
|
||||
{51, "Simultanious operation error"},
|
||||
{52, "Sending disable error"},
|
||||
{53, "Busy error"},
|
||||
{60, "Paramter error"},
|
||||
{61, "Data error"},
|
||||
{62, "Registration error"},
|
||||
{63, "Mode error"},
|
||||
{66, "Adress error"},
|
||||
{67, "No data error"},
|
||||
{72, "Timeout"},
|
||||
{73, "Timeout"},
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
48
MewtocolNet/Mewtocol/MewtocolEvents.cs
Normal file
48
MewtocolNet/Mewtocol/MewtocolEvents.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
121
MewtocolNet/Mewtocol/MewtocolHelpers.cs
Normal file
121
MewtocolNet/Mewtocol/MewtocolHelpers.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
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;
|
||||
|
||||
namespace MewtocolNet {
|
||||
public static class MewtocolHelpers {
|
||||
public static Byte[] ToHexASCIIBytes (this string _str) {
|
||||
ASCIIEncoding ascii = new ASCIIEncoding();
|
||||
Byte[] bytes = ascii.GetBytes(_str.ToUpper());
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static string BuildBCCFrame (this string asciiArr) {
|
||||
Encoding ae = Encoding.ASCII;
|
||||
byte[] b = ae.GetBytes(asciiArr);
|
||||
byte xorTotalByte = 0;
|
||||
for(int i = 0; i < b.Length; i++)
|
||||
xorTotalByte ^= b[i];
|
||||
return asciiArr.Insert(asciiArr.Length, xorTotalByte.ToString("X2"));
|
||||
}
|
||||
|
||||
public 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;
|
||||
return val.HexStringToByteArray();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public 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;
|
||||
return val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public 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();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string BuildDTString (this string _inString, short _stringReservedSize) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
//06000600
|
||||
short stringSize = (short)_inString.Length;
|
||||
var sizeBytes = BitConverter.GetBytes(stringSize).ToHexString();
|
||||
var reservedSizeBytes = BitConverter.GetBytes(_stringReservedSize).ToHexString();
|
||||
//reserved string count bytes
|
||||
sb.Append(reservedSizeBytes);
|
||||
//string count actual bytes
|
||||
sb.Append(sizeBytes);
|
||||
//actual string content
|
||||
sb.Append(_inString.GetAsciiHexFromString().PadRight(_stringReservedSize * 2, '0'));
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
public 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];
|
||||
for (int i = 0; i < input.Length; i += 2) {
|
||||
String hex = input.Substring(i, 2);
|
||||
bytes[i/2] = Convert.ToByte(hex, 16);
|
||||
}
|
||||
return Encoding.ASCII.GetString(bytes);
|
||||
}
|
||||
|
||||
public static string GetAsciiHexFromString (this string input) {
|
||||
var bytes = new ASCIIEncoding().GetBytes(input);
|
||||
return bytes.ToHexString();
|
||||
}
|
||||
|
||||
public 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) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var b in arr) {
|
||||
sb.Append(b.ToString("X2"));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
135
MewtocolNet/Mewtocol/MewtocolInterface.cs
Normal file
135
MewtocolNet/Mewtocol/MewtocolInterface.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MewtocolNet.Responses;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
public partial class MewtocolInterface {
|
||||
|
||||
/// <summary>
|
||||
/// Generic information about the connected PLC
|
||||
/// </summary>
|
||||
public PLCInfo PlcInfo {get;private set;}
|
||||
private CancellationTokenSource tokenSource;
|
||||
|
||||
private string ip {get;set;}
|
||||
private int port {get;set;}
|
||||
public int ConnectionTimeout {get;set;} = 2000;
|
||||
|
||||
#region Initialization
|
||||
/// <summary>
|
||||
/// Builds a new Interfacer for a PLC
|
||||
/// </summary>
|
||||
/// <param name="_ip"></param>
|
||||
/// <param name="_port"></param>
|
||||
public MewtocolInterface (string _ip, int _port = 9094) {
|
||||
ip = _ip;
|
||||
port = _port;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Low level command handling
|
||||
|
||||
/// <summary>
|
||||
/// Sends a command to the PLC and awaits results
|
||||
/// </summary>
|
||||
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
|
||||
/// <param name="_close">Auto close of frame [true]%01#RT01\r [false]%01#RT</param>
|
||||
/// <returns>Returns the result</returns>
|
||||
public async Task<CommandResult> SendCommandAsync (string _msg) {
|
||||
|
||||
_msg = _msg.BuildBCCFrame();
|
||||
_msg += "\r";
|
||||
|
||||
//send request
|
||||
var response = await SendSingleBlock(_msg);
|
||||
|
||||
if(response == null) {
|
||||
return new CommandResult {
|
||||
Success = false,
|
||||
Error = "0000",
|
||||
ErrorDescription = "null result"
|
||||
};
|
||||
}
|
||||
|
||||
//error catching
|
||||
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
|
||||
Match m = errorcheck.Match(response.ToString());
|
||||
if (m.Success) {
|
||||
string eCode = m.Groups[1].Value;
|
||||
string eDes = Links.LinkedData.ErrorCodes[Convert.ToInt32(eCode)];
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"Response is: {response}");
|
||||
Console.WriteLine($"Error on command {_msg.Replace("\r", "")} the PLC returned error code: {eCode}, {eDes}");
|
||||
Console.ResetColor();
|
||||
return new CommandResult {
|
||||
Success = false,
|
||||
Error = eCode,
|
||||
ErrorDescription = eDes
|
||||
};
|
||||
}
|
||||
|
||||
return new CommandResult {
|
||||
Success = true,
|
||||
Error = "0000",
|
||||
Response = response.ToString()
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private async Task<string> SendSingleBlock (string _blockString) {
|
||||
|
||||
if(isWriting) {
|
||||
return null;
|
||||
}
|
||||
|
||||
tokenSource = new CancellationTokenSource();
|
||||
|
||||
using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) {
|
||||
|
||||
try {
|
||||
await client.ConnectAsync(ip, port, tokenSource.Token);
|
||||
} catch(SocketException) {
|
||||
return null;
|
||||
}
|
||||
|
||||
using (NetworkStream stream = client.GetStream()) {
|
||||
var message = _blockString.ToHexASCIIBytes();
|
||||
var messageAscii = BitConverter.ToString(message).Replace("-", " ");
|
||||
//send request
|
||||
isWriting = true;
|
||||
using (var sendStream = new MemoryStream(message)) {
|
||||
await sendStream.CopyToAsync(stream);
|
||||
//log message sent
|
||||
ASCIIEncoding enc = new ASCIIEncoding();
|
||||
string characters = enc.GetString(message);
|
||||
}
|
||||
//await result
|
||||
StringBuilder response = new StringBuilder();
|
||||
byte[] responseBuffer = new byte[256];
|
||||
do {
|
||||
int bytes = stream.Read(responseBuffer, 0, responseBuffer.Length);
|
||||
response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes));
|
||||
}
|
||||
while (stream.DataAvailable);
|
||||
isWriting = false;
|
||||
return response.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
120
MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs
Normal file
120
MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
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 {
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a continous reader that reads back the Registers and Contacts
|
||||
/// </summary>
|
||||
public static Task AttachContinousReader (this Task<MewtocolInterface> interfaceTask, int _refreshTimeMS = 200) {
|
||||
|
||||
interfaceTask.Wait(-1);
|
||||
|
||||
var interf = interfaceTask.Result;
|
||||
|
||||
if (interf.ContinousReaderRunning)
|
||||
return Task.CompletedTask;
|
||||
|
||||
interf.cTokenAutoUpdater = new CancellationTokenSource();
|
||||
|
||||
Console.WriteLine("Attaching cont reader");
|
||||
|
||||
Task.Factory.StartNew(async () => {
|
||||
|
||||
var plcinf = await interf.GetPLCInfoAsync();
|
||||
if (plcinf == null) {
|
||||
Console.WriteLine("PLC is not reachable");
|
||||
throw new Exception("PLC is not reachable");
|
||||
}
|
||||
if (!plcinf.OperationMode.RunMode) {
|
||||
Console.WriteLine("PLC is not running");
|
||||
throw new Exception("PLC is not running");
|
||||
}
|
||||
|
||||
interf.ContinousReaderRunning = true;
|
||||
|
||||
while (true) {
|
||||
|
||||
//dont update when currently writing a var
|
||||
if (interf.isWriting) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await Task.Delay(_refreshTimeMS);
|
||||
foreach (var reg in interf.Registers) {
|
||||
|
||||
if (reg is NRegister<short> shortReg) {
|
||||
var lastVal = shortReg.Value;
|
||||
var readout = (await interf.ReadNumRegister(shortReg)).Register.Value;
|
||||
if (lastVal != readout) {
|
||||
shortReg.LastValue = readout;
|
||||
interf.InvokeRegisterChanged(shortReg);
|
||||
shortReg.TriggerNotifyChange();
|
||||
}
|
||||
}
|
||||
if (reg is NRegister<ushort> ushortReg) {
|
||||
var lastVal = ushortReg.Value;
|
||||
var readout = (await interf.ReadNumRegister(ushortReg)).Register.Value;
|
||||
if (lastVal != readout) {
|
||||
ushortReg.LastValue = readout;
|
||||
interf.InvokeRegisterChanged(ushortReg);
|
||||
ushortReg.TriggerNotifyChange();
|
||||
}
|
||||
}
|
||||
if (reg is NRegister<int> intReg) {
|
||||
var lastVal = intReg.Value;
|
||||
var readout = (await interf.ReadNumRegister(intReg)).Register.Value;
|
||||
if (lastVal != readout) {
|
||||
intReg.LastValue = readout;
|
||||
interf.InvokeRegisterChanged(intReg);
|
||||
intReg.TriggerNotifyChange();
|
||||
}
|
||||
}
|
||||
if (reg is NRegister<uint> uintReg) {
|
||||
var lastVal = uintReg.Value;
|
||||
var readout = (await interf.ReadNumRegister(uintReg)).Register.Value;
|
||||
if (lastVal != readout) {
|
||||
uintReg.LastValue = readout;
|
||||
interf.InvokeRegisterChanged(uintReg);
|
||||
uintReg.TriggerNotifyChange();
|
||||
}
|
||||
}
|
||||
if (reg is NRegister<float> floatReg) {
|
||||
var lastVal = floatReg.Value;
|
||||
var readout = (await interf.ReadNumRegister(floatReg)).Register.Value;
|
||||
if (lastVal != readout) {
|
||||
floatReg.LastValue = readout;
|
||||
interf.InvokeRegisterChanged(floatReg);
|
||||
floatReg.TriggerNotifyChange();
|
||||
}
|
||||
} else if (reg is SRegister stringReg) {
|
||||
var lastVal = stringReg.Value;
|
||||
var readout = (await interf.ReadStringRegister(stringReg)).Register.Value;
|
||||
if (lastVal != readout) {
|
||||
interf.InvokeRegisterChanged(stringReg);
|
||||
stringReg.TriggerNotifyChange();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}, interf.cTokenAutoUpdater.Token);
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
255
MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs
Normal file
255
MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using MewtocolNet.Responses;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
public partial class MewtocolInterface {
|
||||
|
||||
#region High level command handling
|
||||
|
||||
/// <summary>
|
||||
/// Gets generic information about the PLC
|
||||
/// </summary>
|
||||
/// <returns>A PLCInfo class</returns>
|
||||
public async Task<PLCInfo> GetPLCInfoAsync () {
|
||||
var resu = await SendCommandAsync("%01#RT");
|
||||
if(!resu.Success) return null;
|
||||
|
||||
var reg = new Regex(@"\%([0-9]{2})\$RT([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{4})..", RegexOptions.IgnoreCase);
|
||||
Match m = reg.Match(resu.Response);
|
||||
|
||||
if(m.Success) {
|
||||
|
||||
string station = m.Groups[1].Value;
|
||||
string cpu = m.Groups[2].Value;
|
||||
string version = m.Groups[3].Value;
|
||||
string capacity = m.Groups[4].Value;
|
||||
string operation = m.Groups[5].Value;
|
||||
|
||||
string errorflag = m.Groups[7].Value;
|
||||
string error = m.Groups[8].Value;
|
||||
|
||||
PLCInfo retInfo = new PLCInfo {
|
||||
CpuInformation = PLCInfo.CpuInfo.BuildFromHexString(cpu, version, capacity),
|
||||
OperationMode = PLCInfo.PLCMode.BuildFromHex(operation),
|
||||
ErrorCode = error,
|
||||
StationNumber = int.Parse(station ?? "0"),
|
||||
};
|
||||
return retInfo;
|
||||
|
||||
}
|
||||
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) {
|
||||
|
||||
//re order by contact pfx for faster querying
|
||||
_contactsToRead = _contactsToRead.OrderBy(x=>x.Prefix).ToList();
|
||||
|
||||
//return list
|
||||
List<IBoolContact> returnContacts = new List<IBoolContact>();
|
||||
|
||||
//grouped by 8 each
|
||||
List<List<Contact>> nestedContacts = new List<List<Contact>>();
|
||||
|
||||
//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>();
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the given numeric register from PLC
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam>
|
||||
/// <param name="_toRead">The register to read</param>
|
||||
/// <param name="_stationNumber">Station number to access</param>
|
||||
/// <returns>A result with the given NumberRegister containing the readback value and a result struct</returns>
|
||||
public async Task<NRegisterResult<T>> ReadNumRegister<T> (NRegister<T> _toRead, int _stationNumber = 1) {
|
||||
|
||||
Type numType = typeof(T);
|
||||
|
||||
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}";
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
|
||||
if (numType == typeof(short)) {
|
||||
|
||||
var resultBytes = result.Response.ParseDTByteString(4);
|
||||
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));
|
||||
} else if (numType == typeof(int)) {
|
||||
var resultBytes = result.Response.ParseDTBytes(8);
|
||||
var val = BitConverter.ToInt16(resultBytes);
|
||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
||||
} else if (numType == typeof(uint)) {
|
||||
var resultBytes = result.Response.ParseDTBytes(8);
|
||||
var val = BitConverter.ToInt16(resultBytes);
|
||||
_toRead.Value = (T)Convert.ChangeType(val, typeof(T));
|
||||
} 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 finalRes = new NRegisterResult<T> {
|
||||
Result = result,
|
||||
Register = _toRead
|
||||
};
|
||||
|
||||
return finalRes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the given numeric register from PLC
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam>
|
||||
/// <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) {
|
||||
|
||||
byte[] toWriteVal;
|
||||
Type numType = typeof(T);
|
||||
|
||||
if (numType == typeof(short)) {
|
||||
toWriteVal = BitConverter.GetBytes(Convert.ToInt16(_value));
|
||||
} else if (numType == typeof(ushort)) {
|
||||
toWriteVal = BitConverter.GetBytes(Convert.ToUInt16(_value));
|
||||
} else if (numType == typeof(int)) {
|
||||
toWriteVal = BitConverter.GetBytes(Convert.ToInt32(_value));
|
||||
} else if (numType == typeof(uint)) {
|
||||
toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_value));
|
||||
} else if (numType == typeof(float)) {
|
||||
toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public async Task<SRegisterResult> ReadStringRegister (SRegister _toRead, int _stationNumber = 1) {
|
||||
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}";
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
if (result.Success)
|
||||
_toRead.SetValueFromPLC(result.Response.ParseDTString());
|
||||
return new SRegisterResult {
|
||||
Result = result,
|
||||
Register = _toRead
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<SRegisterResult> WriteStringRegister(SRegister _toWrite, string _value, int _stationNumber = 1) {
|
||||
|
||||
if (_value == null) _value = "";
|
||||
if(_value.Length > _toWrite.ReservedSize) {
|
||||
throw new ArgumentException("Write string size cannot be longer than reserved string size");
|
||||
}
|
||||
|
||||
string stationNum = _stationNumber.ToString().PadLeft(2, '0');
|
||||
string dataArea = _toWrite.BuildMewtocolIdent();
|
||||
string dataString = _value.BuildDTString(_toWrite.ReservedSize);
|
||||
string requeststring = $"%{stationNum}#WD{dataArea}{dataString}";
|
||||
|
||||
Console.WriteLine($"reserved: {_toWrite.MemoryLength}, size: {_value.Length}");
|
||||
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
return new SRegisterResult {
|
||||
Result = result,
|
||||
Register = _toWrite
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
183
MewtocolNet/Mewtocol/Register.cs
Normal file
183
MewtocolNet/Mewtocol/Register.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
343
MewtocolNet/Mewtocol/Responses.cs
Normal file
343
MewtocolNet/Mewtocol/Responses.cs
Normal file
@@ -0,0 +1,343 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MewtocolNet.Responses {
|
||||
|
||||
/// <summary>
|
||||
/// The formatted result of a ascii command
|
||||
/// </summary>
|
||||
public struct CommandResult {
|
||||
public bool Success {get;set;}
|
||||
public string Response {get;set;}
|
||||
public string Error {get;set;}
|
||||
public string ErrorDescription {get;set;}
|
||||
|
||||
public override string ToString() {
|
||||
string errmsg = Success ? "" : ErrorDescription;
|
||||
return $"Success: {Success}, Response: {Response} {errmsg}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains generic information about the plc
|
||||
/// </summary>
|
||||
public class PLCInfo {
|
||||
|
||||
public class PLCMode {
|
||||
public bool RunMode {get;set;}
|
||||
public bool TestRunMode {get;set;}
|
||||
public bool BreakExcecuting {get;set;}
|
||||
public bool BreakValid {get;set;}
|
||||
public bool OutputEnabled {get;set;}
|
||||
public bool StepRunMode {get;set;}
|
||||
public bool MessageExecuting {get;set;}
|
||||
public bool RemoteMode {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Gets operation mode from 2 digit hex number
|
||||
/// </summary>
|
||||
public static PLCMode BuildFromHex (string _hexString) {
|
||||
|
||||
string lower = Convert.ToString(Convert.ToInt32(_hexString.Substring(0, 1)), 2).PadLeft(4, '0');
|
||||
string higher = Convert.ToString(Convert.ToInt32(_hexString.Substring(1, 1)), 2).PadLeft(4, '0');
|
||||
string combined = lower + higher;
|
||||
|
||||
var retMode = new PLCMode();
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
char digit = combined[i];
|
||||
bool state = false;
|
||||
if(digit.ToString() == "1") state = true;
|
||||
switch (i) {
|
||||
case 0 :
|
||||
retMode.RunMode = state;
|
||||
break;
|
||||
case 1 :
|
||||
retMode.TestRunMode = state;
|
||||
break;
|
||||
case 2 :
|
||||
retMode.BreakExcecuting = state;
|
||||
break;
|
||||
case 3 :
|
||||
retMode.BreakValid = state;
|
||||
break;
|
||||
case 4 :
|
||||
retMode.OutputEnabled = state;
|
||||
break;
|
||||
case 5 :
|
||||
retMode.StepRunMode = state;
|
||||
break;
|
||||
case 6 :
|
||||
retMode.MessageExecuting = state;
|
||||
break;
|
||||
case 7 :
|
||||
retMode.RemoteMode = state;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return retMode;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class CpuInfo {
|
||||
public enum CpuType {
|
||||
FP0_FP1_2_7K,
|
||||
FP0_FP1_5K_10K,
|
||||
FP1_M_0_9K,
|
||||
FP2_16K_32K,
|
||||
FP3_C_10K,
|
||||
FP3_C_16K,
|
||||
FP5_16K,
|
||||
FP5_24K,
|
||||
FP_Sigma_X_H_30K_60K_120K
|
||||
|
||||
}
|
||||
|
||||
public CpuType Cputype {get;set;}
|
||||
public int ProgramCapacity {get;set;}
|
||||
public string CpuVersion {get;set;}
|
||||
|
||||
|
||||
public static CpuInfo BuildFromHexString (string _cpuType, string _cpuVersion, string _progCapacity) {
|
||||
|
||||
CpuInfo retInf = new CpuInfo();
|
||||
|
||||
switch (_cpuType) {
|
||||
case "02":
|
||||
retInf.Cputype = CpuType.FP5_16K;
|
||||
break;
|
||||
case "03":
|
||||
retInf.Cputype = CpuType.FP3_C_10K;
|
||||
break;
|
||||
case "04":
|
||||
retInf.Cputype = CpuType.FP1_M_0_9K;
|
||||
break;
|
||||
case "05":
|
||||
retInf.Cputype = CpuType.FP0_FP1_2_7K;
|
||||
break;
|
||||
case "06":
|
||||
retInf.Cputype = CpuType.FP0_FP1_5K_10K;
|
||||
break;
|
||||
case "12":
|
||||
retInf.Cputype = CpuType.FP5_24K;
|
||||
break;
|
||||
case "13":
|
||||
retInf.Cputype = CpuType.FP3_C_16K;
|
||||
break;
|
||||
case "20":
|
||||
retInf.Cputype = CpuType.FP_Sigma_X_H_30K_60K_120K;
|
||||
break;
|
||||
case "50":
|
||||
retInf.Cputype = CpuType.FP2_16K_32K;
|
||||
break;
|
||||
}
|
||||
|
||||
retInf.ProgramCapacity = Convert.ToInt32(_progCapacity);
|
||||
retInf.CpuVersion = _cpuVersion.Insert(1, ".");
|
||||
return retInf;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public CpuInfo CpuInformation {get;set;}
|
||||
public PLCMode OperationMode {get;set;}
|
||||
public string ErrorCode {get;set;}
|
||||
public int StationNumber { get;set;}
|
||||
|
||||
public override string ToString () {
|
||||
|
||||
return $"Type: {CpuInformation.Cputype},\n" +
|
||||
$"Capacity: {CpuInformation.ProgramCapacity}k\n" +
|
||||
$"CPU v: {CpuInformation.CpuVersion}\n" +
|
||||
$"Station Num: {StationNumber}\n" +
|
||||
$"--------------------------------\n" +
|
||||
$"OP Mode: {(OperationMode.RunMode ? "Run" : "Prog")}\n" +
|
||||
$"Error Code: {ErrorCode}";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
14
MewtocolNet/MewtocolNet.csproj
Normal file
14
MewtocolNet/MewtocolNet.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<PackageId>AppLogger</PackageId>
|
||||
<Version>0.1.5</Version>
|
||||
<Authors>Felix Weiss</Authors>
|
||||
<Company>Womed</Company>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DocumentationFile>P:\QUELLEN\PRODUKTAKTE\DESIGN_V_V\Software SPS\Projekt FP-XH\MewtocolNet\Builds\MewtocolNet.xml</DocumentationFile>
|
||||
<OutputPath>P:\QUELLEN\PRODUKTAKTE\DESIGN_V_V\Software SPS\Projekt FP-XH\MewtocolNet\Builds\</OutputPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user