Added support for TIME types

- fixed writing string registers
- refactoring and more comments
- fixed dc exception handling
- fixed some null reference bugs
This commit is contained in:
Felix Weiß
2022-06-20 14:33:50 +02:00
parent ac265491e2
commit d0519f2409
10 changed files with 358 additions and 202 deletions

View File

@@ -6,7 +6,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -1,56 +1,10 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq;
using System.Text.Json;
using MewtocolNet; using MewtocolNet;
using MewtocolNet.RegisterAttributes;
using System.Collections;
using MewtocolNet.Logging; using MewtocolNet.Logging;
namespace Examples { namespace Examples {
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 { class Program {
static void Main(string[] args) { static void Main(string[] args) {
@@ -58,7 +12,7 @@ namespace Examples {
Task.Factory.StartNew(async () => { Task.Factory.StartNew(async () => {
//attaching the logger //attaching the logger
Logger.LogLevel = LogLevel.Critical; Logger.LogLevel = LogLevel.Verbose;
Logger.OnNewLogMessage((date, msg) => { Logger.OnNewLogMessage((date, msg) => {
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}"); Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}");
}); });
@@ -88,8 +42,10 @@ namespace Examples {
interf.SetRegister(nameof(registers.TestInt32), (registers.TestInt32 + 1)); interf.SetRegister(nameof(registers.TestInt32), (registers.TestInt32 + 1));
//adds 11.11 each time the plc connects to the PLCs REAL regíster //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.TestFloat32), (float)(registers.TestFloat32 + 11.11));
//writes 'Hello' to the PLCs string register
interf.SetRegister(nameof(registers.TestString2), new Random().Next(0, 99999).ToString()); interf.SetRegister(nameof(registers.TestString2), "Hello");
//set the current second to the PLCs TIME register
interf.SetRegister(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Second));
}); });

52
Examples/TestRegisters.cs Normal file
View File

@@ -0,0 +1,52 @@
using MewtocolNet;
using MewtocolNet.RegisterAttributes;
using System;
using System.Collections;
namespace Examples {
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; }
//corresponds to a DT7012 - DT7013 as a 32bit time value that gets parsed as a timespan (TIME)
//the smallest value to communicate to the PLC is 10ms
[Register(7012)]
public TimeSpan TestTime { get; private set; }
}
}

View File

@@ -21,6 +21,13 @@ namespace MewtocolNet {
#region Register Polling #region Register Polling
internal void KillPoller () {
ContinousReaderRunning = false;
cTokenAutoUpdater.Cancel();
}
/// <summary> /// <summary>
/// Attaches a continous reader that reads back the Registers and Contacts /// Attaches a continous reader that reads back the Registers and Contacts
/// </summary> /// </summary>
@@ -32,123 +39,134 @@ namespace MewtocolNet {
Logger.Log("Poller is attaching", LogLevel.Info, this); Logger.Log("Poller is attaching", LogLevel.Info, this);
Task.Factory.StartNew(async () => { try {
var plcinf = await GetPLCInfoAsync(); Task.Factory.StartNew(async () => {
if (plcinf == null) {
Logger.Log("PLC not reachable, stopping logger", LogLevel.Info, this);
return;
}
PolledCycle += MewtocolInterface_PolledCycle; var plcinf = await GetPLCInfoAsync();
void MewtocolInterface_PolledCycle () { if (plcinf == null) {
Logger.Log("PLC not reachable, stopping logger", LogLevel.Info, this);
StringBuilder stringBuilder = new StringBuilder(); return;
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" + PolledCycle += MewtocolInterface_PolledCycle;
$"--------------------\n" + void MewtocolInterface_PolledCycle () {
$"{stringBuilder.ToString()}" +
$"--------------------",
LogLevel.Verbose, this);
Logger.Log("Logger did its first cycle successfully", LogLevel.Info, this); StringBuilder stringBuilder = new StringBuilder();
foreach (var reg in GetAllRegisters()) {
PolledCycle -= MewtocolInterface_PolledCycle; string address = $"{reg.GetRegisterString()}{reg.GetStartingMemoryArea()}".PadRight(8, (char)32);
} stringBuilder.AppendLine($"{address}{(reg.Name != null ? $" ({reg.Name})" : "")}: {reg.GetValueString()}");
}
ContinousReaderRunning = true; Logger.Log($"Registers loaded are: \n" +
$"--------------------\n" +
$"{stringBuilder.ToString()}" +
$"--------------------",
LogLevel.Verbose, this);
while (true) { Logger.Log("Logger did its first cycle successfully", LogLevel.Info, this);
//do priority tasks first PolledCycle -= MewtocolInterface_PolledCycle;
if(PriorityTasks.Count > 0) { }
await PriorityTasks.FirstOrDefault(x => !x.IsCompleted); ContinousReaderRunning = true;
while (ContinousReaderRunning) {
//do priority tasks first
if (PriorityTasks.Count > 0) {
await PriorityTasks.FirstOrDefault(x => !x.IsCompleted);
}
foreach (var registerPair in Registers) {
var reg = registerPair.Value;
if (reg is NRegister<short> shortReg) {
var lastVal = shortReg.Value;
var readout = (await ReadNumRegister(shortReg, stationNumber)).Register.Value;
if (lastVal != readout) {
shortReg.LastValue = readout;
InvokeRegisterChanged(shortReg);
shortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<ushort> ushortReg) {
var lastVal = ushortReg.Value;
var readout = (await ReadNumRegister(ushortReg, stationNumber)).Register.Value;
if (lastVal != readout) {
ushortReg.LastValue = readout;
InvokeRegisterChanged(ushortReg);
ushortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<int> intReg) {
var lastVal = intReg.Value;
var readout = (await ReadNumRegister(intReg, stationNumber)).Register.Value;
if (lastVal != readout) {
intReg.LastValue = readout;
InvokeRegisterChanged(intReg);
intReg.TriggerNotifyChange();
}
}
if (reg is NRegister<uint> uintReg) {
var lastVal = uintReg.Value;
var readout = (await ReadNumRegister(uintReg, stationNumber)).Register.Value;
if (lastVal != readout) {
uintReg.LastValue = readout;
InvokeRegisterChanged(uintReg);
uintReg.TriggerNotifyChange();
}
}
if (reg is NRegister<float> floatReg) {
var lastVal = floatReg.Value;
var readout = (await ReadNumRegister(floatReg, stationNumber)).Register.Value;
if (lastVal != readout) {
floatReg.LastValue = readout;
InvokeRegisterChanged(floatReg);
floatReg.TriggerNotifyChange();
}
}
if (reg is NRegister<TimeSpan> tsReg) {
var lastVal = tsReg.Value;
var readout = (await ReadNumRegister(tsReg, stationNumber)).Register.Value;
if (lastVal != readout) {
tsReg.LastValue = readout;
InvokeRegisterChanged(tsReg);
tsReg.TriggerNotifyChange();
}
}
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) {
InvokeRegisterChanged(stringReg);
stringReg.TriggerNotifyChange();
}
}
}
//invoke cycle polled event
InvokePolledCycleDone();
} }
//await Task.Delay(pollingDelayMs); }, cTokenAutoUpdater.Token);
foreach (var registerPair in Registers) { } catch (TaskCanceledException) { }
var reg = registerPair.Value;
if (reg is NRegister<short> shortReg) {
var lastVal = shortReg.Value;
var readout = (await ReadNumRegister(shortReg, stationNumber)).Register.Value;
if (lastVal != readout) {
shortReg.LastValue = readout;
InvokeRegisterChanged(shortReg);
shortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<ushort> ushortReg) {
var lastVal = ushortReg.Value;
var readout = (await ReadNumRegister(ushortReg, stationNumber)).Register.Value;
if (lastVal != readout) {
ushortReg.LastValue = readout;
InvokeRegisterChanged(ushortReg);
ushortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<int> intReg) {
var lastVal = intReg.Value;
var readout = (await ReadNumRegister(intReg, stationNumber)).Register.Value;
if (lastVal != readout) {
intReg.LastValue = readout;
InvokeRegisterChanged(intReg);
intReg.TriggerNotifyChange();
}
}
if (reg is NRegister<uint> uintReg) {
var lastVal = uintReg.Value;
var readout = (await ReadNumRegister(uintReg, stationNumber)).Register.Value;
if (lastVal != readout) {
uintReg.LastValue = readout;
InvokeRegisterChanged(uintReg);
uintReg.TriggerNotifyChange();
}
}
if (reg is NRegister<float> floatReg) {
var lastVal = floatReg.Value;
var readout = (await ReadNumRegister(floatReg, stationNumber)).Register.Value;
if (lastVal != readout) {
floatReg.LastValue = readout;
InvokeRegisterChanged(floatReg);
floatReg.TriggerNotifyChange();
}
}
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) {
InvokeRegisterChanged(stringReg);
stringReg.TriggerNotifyChange();
}
}
}
//invoke cycle polled event
InvokePolledCycleDone();
}
}, cTokenAutoUpdater.Token);
} }
@@ -265,6 +283,8 @@ namespace MewtocolNet {
Registers.Add(_address, new NRegister<float>(_address, _name)); Registers.Add(_address, new NRegister<float>(_address, _name));
} else if (regType == typeof(string)) { } else if (regType == typeof(string)) {
Registers.Add(_address, new SRegister(_address, _length, _name)); Registers.Add(_address, new SRegister(_address, _length, _name));
} else if (regType == typeof(TimeSpan)) {
Registers.Add(_address, new NRegister<TimeSpan>(_address, _name));
} else if (regType == typeof(bool)) { } else if (regType == typeof(bool)) {
Registers.Add(_address, new BRegister(_address, RegisterType.R, _name)); Registers.Add(_address, new BRegister(_address, RegisterType.R, _name));
} else { } else {

View File

@@ -45,12 +45,17 @@ namespace MewtocolNet {
} }
internal static string ParseDTByteString (this string _onString, int _blockSize = 4) { internal static string ParseDTByteString (this string _onString, int _blockSize = 4) {
if (_onString == null)
return null;
var res = new Regex(@"\%([0-9]{2})\$RD(.{" + _blockSize + "})").Match(_onString); var res = new Regex(@"\%([0-9]{2})\$RD(.{" + _blockSize + "})").Match(_onString);
if (res.Success) { if (res.Success) {
string val = res.Groups[2].Value; string val = res.Groups[2].Value;
return val; return val;
} }
return null; return null;
} }
internal static bool? ParseRCSingleBit (this string _onString, int _blockSize = 4) { internal static bool? ParseRCSingleBit (this string _onString, int _blockSize = 4) {
@@ -73,6 +78,8 @@ namespace MewtocolNet {
internal static string ReverseByteOrder (this string _onString) { internal static string ReverseByteOrder (this string _onString) {
if(_onString == null) return null;
//split into 2 chars //split into 2 chars
var stringBytes = _onString.SplitInParts(2).ToList(); var stringBytes = _onString.SplitInParts(2).ToList();
@@ -93,17 +100,38 @@ namespace MewtocolNet {
} }
internal static string BuildDTString (this string _inString, short _stringReservedSize) { internal static string BuildDTString (this string _inString, short _stringReservedSize) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
//06000600
short stringSize = (short)_inString.Length; //clamp string lenght
var sizeBytes = BitConverter.GetBytes(stringSize).ToHexString(); if (_inString.Length > _stringReservedSize) {
_inString = _inString.Substring(0, _stringReservedSize);
}
//actual string content
var hexstring = _inString.GetAsciiHexFromString();
var sizeBytes = BitConverter.GetBytes((short)(hexstring.Length / 2)).ToHexString();
if (hexstring.Length >= 2) {
var remainderBytes = (hexstring.Length / 2) % 2;
if (remainderBytes != 0) {
hexstring += "20";
}
}
var reservedSizeBytes = BitConverter.GetBytes(_stringReservedSize).ToHexString(); var reservedSizeBytes = BitConverter.GetBytes(_stringReservedSize).ToHexString();
//reserved string count bytes //reserved string count bytes
sb.Append(reservedSizeBytes); sb.Append(reservedSizeBytes);
//string count actual bytes //string count actual bytes
sb.Append(sizeBytes); sb.Append(sizeBytes);
//actual string content
sb.Append(_inString.GetAsciiHexFromString());
sb.Append(hexstring);
return sb.ToString(); return sb.ToString();
} }

View File

@@ -32,19 +32,24 @@ namespace MewtocolNet {
/// </summary> /// </summary>
public event Action<Register> RegisterChanged; public event Action<Register> RegisterChanged;
/// <summary>
/// The current connection state of the interface
/// </summary>
public bool IsConnected { get; private set; }
/// <summary> /// <summary>
/// Generic information about the connected PLC /// Generic information about the connected PLC
/// </summary> /// </summary>
public PLCInfo PlcInfo {get;private set;} public PLCInfo PlcInfo { get; private set; }
/// <summary> /// <summary>
/// The registered data registers of the PLC /// The registered data registers of the PLC
/// </summary> /// </summary>
public Dictionary<int, Register> Registers { get; set; } = new Dictionary<int, Register>(); public Dictionary<int, Register> Registers { get; set; } = new Dictionary<int, Register>();
private string ip {get;set;} private string ip;
private int port {get;set;} private int port;
private int stationNumber {get;set;} private int stationNumber;
/// <summary> /// <summary>
/// The current IP of the PLC connection /// The current IP of the PLC connection
@@ -82,6 +87,8 @@ namespace MewtocolNet {
if (usePoller) if (usePoller)
AttachPoller(); AttachPoller();
IsConnected = true;
} }
RegisterChanged += (o) => { RegisterChanged += (o) => {
@@ -240,6 +247,10 @@ namespace MewtocolNet {
} }
if (prop.PropertyType == typeof(TimeSpan)) {
AddRegister<TimeSpan>(cAttribute.MemoryArea, _name: propName);
}
} }
} }
@@ -404,6 +415,12 @@ namespace MewtocolNet {
} }
if (foundRegister.GetType() == typeof(NRegister<TimeSpan>)) {
_ = WriteNumRegister((NRegister<TimeSpan>)foundRegister, (TimeSpan)value, StationNumber);
}
if (foundRegister.GetType() == typeof(SRegister)) { if (foundRegister.GetType() == typeof(SRegister)) {
_ = WriteStringRegister((SRegister)foundRegister, (string)value, StationNumber); _ = WriteStringRegister((SRegister)foundRegister, (string)value, StationNumber);
@@ -417,7 +434,7 @@ namespace MewtocolNet {
#region Low level command handling #region Low level command handling
/// <summary> /// <summary>
/// Sends a command to the PLC and awaits results /// Calculates checksum and sends a command to the PLC then awaits results
/// </summary> /// </summary>
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param> /// <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> /// <param name="_close">Auto close of frame [true]%01#RT01\r [false]%01#RT</param>
@@ -490,35 +507,42 @@ namespace MewtocolNet {
using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) { using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) {
try { try {
await client.ConnectAsync(ip, port); await client.ConnectAsync(ip, port);
} catch(SocketException) {
using (NetworkStream stream = client.GetStream()) {
var message = _blockString.ToHexASCIIBytes();
var messageAscii = BitConverter.ToString(message).Replace("-", " ");
//send request
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);
}
//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);
sw.Stop();
Logger.Log($"IN MSG ({(int)sw.Elapsed.TotalMilliseconds}ms): {_blockString}", LogLevel.Critical, this);
return response.ToString();
}
} catch(Exception) {
IsConnected = false;
KillPoller();
Logger.Log("The PLC connection was closed", LogLevel.Error, this);
return null; return null;
} }
using (NetworkStream stream = client.GetStream()) {
var message = _blockString.ToHexASCIIBytes();
var messageAscii = BitConverter.ToString(message).Replace("-", " ");
//send request
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);
}
//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);
sw.Stop();
Logger.Log($"IN MSG ({(int)sw.Elapsed.TotalMilliseconds}ms): {_blockString}", LogLevel.Critical, this);
return response.ToString();
}
} }
} }

View File

@@ -53,6 +53,11 @@ namespace MewtocolNet {
#region Bool register reading / writing #region Bool register reading / writing
/// <summary>
/// Reads the given boolean register from the PLC
/// </summary>
/// <param name="_toRead">The register to read</param>
/// <param name="_stationNumber">Station number to access</param>
public async Task<BRegisterResult> ReadBoolRegister (BRegister _toRead, int _stationNumber = 1) { public async Task<BRegisterResult> ReadBoolRegister (BRegister _toRead, int _stationNumber = 1) {
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RCS{_toRead.BuildMewtocolIdent()}"; string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RCS{_toRead.BuildMewtocolIdent()}";
@@ -79,6 +84,12 @@ namespace MewtocolNet {
} }
/// <summary>
/// Writes to the given bool register on the PLC
/// </summary>
/// <param name="_toWrite">The register to write to</param>
/// <param name="_stationNumber">Station number to access</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> WriteBoolRegister (BRegister _toWrite, bool value, int _stationNumber = 1) { 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")}"; string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#WCS{_toWrite.BuildMewtocolIdent()}{(value ? "1" : "0")}";
@@ -94,7 +105,7 @@ namespace MewtocolNet {
#region Number register reading / writing #region Number register reading / writing
/// <summary> /// <summary>
/// Reads the given numeric register from PLC /// Reads the given numeric register from the PLC
/// </summary> /// </summary>
/// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam> /// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam>
/// <param name="_toRead">The register to read</param> /// <param name="_toRead">The register to read</param>
@@ -107,6 +118,13 @@ namespace MewtocolNet {
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}"; string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}";
var result = await SendCommandAsync(requeststring); var result = await SendCommandAsync(requeststring);
if(!result.Success || string.IsNullOrEmpty(result.Response)) {
return new NRegisterResult<T> {
Result = result,
Register = _toRead
};
}
if (numType == typeof(short)) { if (numType == typeof(short)) {
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder(); var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
@@ -142,6 +160,17 @@ namespace MewtocolNet {
(_toRead as NRegister<float>).LastValue = finalFloat; (_toRead as NRegister<float>).LastValue = finalFloat;
} else if (numType == typeof(TimeSpan)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
//convert to unsigned int first
var vallong = long.Parse(resultBytes, NumberStyles.HexNumber);
var valMillis = vallong * 10;
var ts = TimeSpan.FromMilliseconds(valMillis);
//minmax writable / readable value is 10ms
(_toRead as NRegister<TimeSpan>).LastValue = ts;
} }
var finalRes = new NRegisterResult<T> { var finalRes = new NRegisterResult<T> {
@@ -153,12 +182,12 @@ namespace MewtocolNet {
} }
/// <summary> /// <summary>
/// Reads the given numeric register from PLC /// Reads the given numeric register from the PLC
/// </summary> /// </summary>
/// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam> /// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam>
/// <param name="_toWrite">The register to write</param> /// <param name="_toWrite">The register to write</param>
/// <param name="_stationNumber">Station number to access</param> /// <param name="_stationNumber">Station number to access</param>
/// <returns>A result with the given NumberRegister and a result struct</returns> /// <returns>The success state of the write operation</returns>
public async Task<bool> WriteNumRegister<T> (NRegister<T> _toWrite, T _value, int _stationNumber = 1) { public async Task<bool> WriteNumRegister<T> (NRegister<T> _toWrite, T _value, int _stationNumber = 1) {
byte[] toWriteVal; byte[] toWriteVal;
@@ -180,6 +209,15 @@ namespace MewtocolNet {
toWriteVal = BitConverter.GetBytes(fl.Value); toWriteVal = BitConverter.GetBytes(fl.Value);
} else if (numType == typeof(TimeSpan)) {
var fl = _value as TimeSpan?;
if (fl == null)
throw new NullReferenceException("Timespan cannot be null");
var tLong = (uint)(fl.Value.TotalMilliseconds / 10);
toWriteVal = BitConverter.GetBytes(tLong);
} else { } else {
toWriteVal = null; toWriteVal = null;
} }
@@ -196,14 +234,20 @@ namespace MewtocolNet {
#region String register reading / writing #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 is build up like this /// <summary>
//04 00 04 00 53 50 33 35 13 /// Reads back the value of a string register
//0, 1 = reserved size /// </summary>
//1, 2 = current size /// <param name="_toRead">The register to read</param>
//3,4,5,6 = ASCII encoded chars (SP35) /// <param name="_stationNumber">The station number of the PLC</param>
//7,8 = checksum /// <returns></returns>
public async Task<SRegisterResult> ReadStringRegister (SRegister _toRead, int _stationNumber = 1) {
string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}"; string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}";
var result = await SendCommandAsync(requeststring); var result = await SendCommandAsync(requeststring);
@@ -215,6 +259,13 @@ namespace MewtocolNet {
}; };
} }
/// <summary>
/// Writes a string to a string register
/// </summary>
/// <param name="_toWrite">The register to write</param>
/// <param name="_value">The value to write, if the strings length is longer than the cap size it gets trimmed to the max char length</param>
/// <param name="_stationNumber">The station number of the PLC</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> WriteStringRegister(SRegister _toWrite, string _value, int _stationNumber = 1) { public async Task<bool> WriteStringRegister(SRegister _toWrite, string _value, int _stationNumber = 1) {
if (_value == null) _value = ""; if (_value == null) _value = "";
@@ -223,13 +274,14 @@ namespace MewtocolNet {
} }
string stationNum = _stationNumber.ToString().PadLeft(2, '0'); string stationNum = _stationNumber.ToString().PadLeft(2, '0');
string dataArea = _toWrite.BuildMewtocolIdent();
string dataString = _value.BuildDTString(_toWrite.ReservedSize); string dataString = _value.BuildDTString(_toWrite.ReservedSize);
string dataArea = _toWrite.BuildCustomIdent(dataString.Length / 4);
string requeststring = $"%{stationNum}#WD{dataArea}{dataString}"; string requeststring = $"%{stationNum}#WD{dataArea}{dataString}";
Console.WriteLine($"reserved: {_toWrite.MemoryLength}, size: {_value.Length}");
var result = await SendCommandAsync(requeststring); var result = await SendCommandAsync(requeststring);
return result.Success && result.Response.StartsWith($"%{ _stationNumber.ToString().PadLeft(2, '0')}#WD"); return result.Success && result.Response.StartsWith($"%{ _stationNumber.ToString().PadLeft(2, '0')}#WD");
} }

View File

@@ -42,6 +42,8 @@ namespace MewtocolNet.Responses {
MemoryLength = 1; MemoryLength = 1;
} else if (numType == typeof(float)) { } else if (numType == typeof(float)) {
MemoryLength = 1; MemoryLength = 1;
} else if (numType == typeof(TimeSpan)) {
MemoryLength = 1;
} else { } else {
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers"); throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
} }

View File

@@ -75,6 +75,9 @@ namespace MewtocolNet.Responses {
if (this is NRegister<float> floatReg) { if (this is NRegister<float> floatReg) {
return floatReg.Value.ToString(); return floatReg.Value.ToString();
} }
if (this is NRegister<TimeSpan> tsReg) {
return tsReg.Value.ToString();
}
if (this is BRegister boolReg) { if (this is BRegister boolReg) {
return boolReg.Value.ToString(); return boolReg.Value.ToString();
} }
@@ -130,6 +133,9 @@ namespace MewtocolNet.Responses {
if (this is NRegister<float> floatReg) { if (this is NRegister<float> floatReg) {
return "DDT"; return "DDT";
} }
if (this is NRegister<TimeSpan> tsReg) {
return "DDT";
}
if (this is BRegister boolReg) { if (this is BRegister boolReg) {
return boolReg.RegType.ToString(); return boolReg.RegType.ToString();
} }

View File

@@ -40,9 +40,25 @@ namespace MewtocolNet.Responses {
} }
public override string BuildMewtocolIdent() { public override string BuildMewtocolIdent() {
StringBuilder asciistring = new StringBuilder("D"); StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0')); asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0')); asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
internal string BuildCustomIdent (int overwriteWordLength) {
if (overwriteWordLength <= 0)
throw new Exception("overwriteWordLength cant be 0 or less");
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAdress + overwriteWordLength - 1).ToString().PadLeft(5, '0'));
return asciistring.ToString(); return asciistring.ToString();
} }