mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Added serial port support
- complete restructure of codebase
This commit is contained in:
@@ -13,6 +13,7 @@ using Microsoft.Win32;
|
|||||||
using MewtocolNet.ComCassette;
|
using MewtocolNet.ComCassette;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.IO.Ports;
|
||||||
|
|
||||||
namespace Examples;
|
namespace Examples;
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ public class ExampleScenarios {
|
|||||||
public void SetupLogger () {
|
public void SetupLogger () {
|
||||||
|
|
||||||
//attaching the logger
|
//attaching the logger
|
||||||
Logger.LogLevel = LogLevel.Error;
|
Logger.LogLevel = LogLevel.Verbose;
|
||||||
Logger.OnNewLogMessage((date, level, msg) => {
|
Logger.OnNewLogMessage((date, level, msg) => {
|
||||||
|
|
||||||
if (level == LogLevel.Error) Console.ForegroundColor = ConsoleColor.Red;
|
if (level == LogLevel.Error) Console.ForegroundColor = ConsoleColor.Red;
|
||||||
@@ -38,7 +39,7 @@ public class ExampleScenarios {
|
|||||||
public async Task RunDisposalAndDisconnectAsync () {
|
public async Task RunDisposalAndDisconnectAsync () {
|
||||||
|
|
||||||
//automatic disposal
|
//automatic disposal
|
||||||
using (var interf = new MewtocolInterface("192.168.115.210")) {
|
using (var interf = Mewtocol.Ethernet("192.168.115.210")) {
|
||||||
|
|
||||||
await interf.ConnectAsync();
|
await interf.ConnectAsync();
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ public class ExampleScenarios {
|
|||||||
Console.WriteLine("Disposed, closed connection");
|
Console.WriteLine("Disposed, closed connection");
|
||||||
|
|
||||||
//manual close
|
//manual close
|
||||||
var interf2 = new MewtocolInterface("192.168.115.210");
|
var interf2 = Mewtocol.Ethernet("192.168.115.210");
|
||||||
|
|
||||||
await interf2.ConnectAsync();
|
await interf2.ConnectAsync();
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ public class ExampleScenarios {
|
|||||||
public async Task RunReadTest () {
|
public async Task RunReadTest () {
|
||||||
|
|
||||||
//setting up a new PLC interface and register collection
|
//setting up a new PLC interface and register collection
|
||||||
MewtocolInterface interf = new MewtocolInterface("192.168.115.210").WithPoller();
|
var interf = Mewtocol.Ethernet("192.168.115.210").WithPoller();
|
||||||
|
|
||||||
//auto add all built registers to the interface
|
//auto add all built registers to the interface
|
||||||
var builder = RegBuilder.ForInterface(interf);
|
var builder = RegBuilder.ForInterface(interf);
|
||||||
@@ -147,20 +148,18 @@ public class ExampleScenarios {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Scenario("Test read speed 100 R registers")]
|
[Scenario("Test read speed TCP (n) R registers")]
|
||||||
public async Task ReadRSpeedTest() {
|
public async Task ReadRSpeedTest (string registerCount) {
|
||||||
|
|
||||||
var preLogLevel = Logger.LogLevel;
|
var preLogLevel = Logger.LogLevel;
|
||||||
Logger.LogLevel = LogLevel.Critical;
|
Logger.LogLevel = LogLevel.Critical;
|
||||||
|
|
||||||
//setting up a new PLC interface and register collection
|
//setting up a new PLC interface and register collection
|
||||||
MewtocolInterface interf = new MewtocolInterface("192.168.115.210") {
|
using var interf = Mewtocol.Ethernet("192.168.115.210");
|
||||||
ConnectTimeout = 3000,
|
|
||||||
};
|
|
||||||
|
|
||||||
//auto add all built registers to the interface
|
//auto add all built registers to the interface
|
||||||
var builder = RegBuilder.ForInterface(interf);
|
var builder = RegBuilder.ForInterface(interf);
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < int.Parse(registerCount); i++) {
|
||||||
|
|
||||||
builder.FromPlcRegName($"R{i}A").Build();
|
builder.FromPlcRegName($"R{i}A").Build();
|
||||||
|
|
||||||
@@ -169,6 +168,11 @@ public class ExampleScenarios {
|
|||||||
//connect
|
//connect
|
||||||
await interf.ConnectAsync();
|
await interf.ConnectAsync();
|
||||||
|
|
||||||
|
if(!interf.IsConnected) {
|
||||||
|
Console.WriteLine("Aborted, connection failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine("Poller cycle started");
|
Console.WriteLine("Poller cycle started");
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
@@ -180,12 +184,75 @@ public class ExampleScenarios {
|
|||||||
|
|
||||||
Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms for {cmdCount} commands and {interf.Registers.Count()} R registers");
|
Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms for {cmdCount} commands and {interf.Registers.Count()} R registers");
|
||||||
|
|
||||||
interf.Disconnect();
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Scenario("Test read speed Serial (n) R registers")]
|
||||||
|
public async Task ReadRSpeedTestSerial (string registerCount) {
|
||||||
|
|
||||||
|
var preLogLevel = Logger.LogLevel;
|
||||||
|
Logger.LogLevel = LogLevel.Critical;
|
||||||
|
|
||||||
|
//setting up a new PLC interface and register collection
|
||||||
|
//MewtocolInterfaceShared interf = Mewtocol.SerialAuto("COM4");
|
||||||
|
using var interf = Mewtocol.Serial("COM4", BaudRate._115200, DataBits.Eight, Parity.Odd, StopBits.One);
|
||||||
|
|
||||||
|
//auto add all built registers to the interface
|
||||||
|
var builder = RegBuilder.ForInterface(interf);
|
||||||
|
for (int i = 0; i < int.Parse(registerCount); i++) {
|
||||||
|
|
||||||
|
builder.FromPlcRegName($"R{i}A").Build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//connect
|
||||||
|
await interf.ConnectAsync();
|
||||||
|
|
||||||
|
if (!interf.IsConnected) {
|
||||||
|
Console.WriteLine("Aborted, connection failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Poller cycle started");
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
int cmdCount = await interf.RunPollerCylceManual();
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
|
||||||
|
Console.WriteLine("Poller cycle finished");
|
||||||
|
|
||||||
|
Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms for {cmdCount} commands and {interf.Registers.Count()} R registers");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Scenario("Test automatic serial port setup")]
|
||||||
|
public async Task TestAutoSerialSetup () {
|
||||||
|
|
||||||
|
var preLogLevel = Logger.LogLevel;
|
||||||
|
Logger.LogLevel = LogLevel.Critical;
|
||||||
|
|
||||||
|
//setting up a new PLC interface and register collection
|
||||||
|
var interf = Mewtocol.SerialAuto("COM4");
|
||||||
|
|
||||||
|
//connect
|
||||||
|
await interf.ConnectAsync();
|
||||||
|
|
||||||
|
if (!interf.IsConnected) {
|
||||||
|
|
||||||
|
Console.WriteLine("Aborted, connection failed");
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Console.WriteLine("Serial port settings found");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
[Scenario("Find all COM5 cassettes in the network")]
|
[Scenario("Find all COM5 cassettes in the network")]
|
||||||
public async Task FindCassettes () {
|
public async Task FindCassettes () {
|
||||||
|
|
||||||
@@ -209,10 +276,10 @@ public class ExampleScenarios {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(5000);
|
|
||||||
|
|
||||||
var found = casettes.FirstOrDefault(x => x.Endpoint.Address.ToString() == "10.237.191.75");
|
var found = casettes.FirstOrDefault(x => x.Endpoint.Address.ToString() == "10.237.191.75");
|
||||||
|
|
||||||
|
if (found == null) return;
|
||||||
|
|
||||||
found.IPAddress = IPAddress.Parse($"192.168.1.{new Random().Next(20, 120)}");
|
found.IPAddress = IPAddress.Parse($"192.168.1.{new Random().Next(20, 120)}");
|
||||||
found.Name = $"Rand{new Random().Next(5, 15)}";
|
found.Name = $"Rand{new Random().Next(5, 15)}";
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ class Program {
|
|||||||
|
|
||||||
if(foundAtt != null && foundAtt is ScenarioAttribute att) {
|
if(foundAtt != null && foundAtt is ScenarioAttribute att) {
|
||||||
|
|
||||||
Console.WriteLine($"[{j + 1}] {method.Name}() - {att.Description}");
|
string paramsStr = string.Join(" ", method.GetParameters().Select(x => x.Name));
|
||||||
|
Console.WriteLine($"[{j + 1}] {method.Name}({paramsStr}) - {att.Description}");
|
||||||
invokeableMethods.Add(method);
|
invokeableMethods.Add(method);
|
||||||
|
|
||||||
j++;
|
j++;
|
||||||
@@ -78,6 +79,8 @@ class Program {
|
|||||||
var line = Console.ReadLine();
|
var line = Console.ReadLine();
|
||||||
|
|
||||||
var loggerMatch = Regex.Match(line, @"logger (?<level>[a-zA-Z]{0,})");
|
var loggerMatch = Regex.Match(line, @"logger (?<level>[a-zA-Z]{0,})");
|
||||||
|
var splitInput = Regex.Split(line, " ");
|
||||||
|
|
||||||
|
|
||||||
if (loggerMatch.Success && Enum.TryParse<LogLevel>(loggerMatch.Groups["level"].Value, out var loglevel)) {
|
if (loggerMatch.Success && Enum.TryParse<LogLevel>(loggerMatch.Groups["level"].Value, out var loglevel)) {
|
||||||
|
|
||||||
@@ -93,14 +96,27 @@ class Program {
|
|||||||
|
|
||||||
Console.Clear();
|
Console.Clear();
|
||||||
|
|
||||||
} else if (int.TryParse(line, out var lineNum)) {
|
} else if (int.TryParse(splitInput[0], out var lineNum)) {
|
||||||
|
|
||||||
var index = Math.Clamp(lineNum - 1, 0, invokeableMethods.Count - 1);
|
var index = Math.Clamp(lineNum - 1, 0, invokeableMethods.Count - 1);
|
||||||
|
|
||||||
var task = (Task)invokeableMethods.ElementAt(index).Invoke(ExampleSzenarios, null);
|
object[] invParams = null;
|
||||||
|
|
||||||
|
if(splitInput.Length > 1) {
|
||||||
|
invParams = splitInput.Skip(1).Cast<object>().ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
var task = (Task)invokeableMethods.ElementAt(index).Invoke(ExampleSzenarios, invParams);
|
||||||
task.Wait();
|
task.Wait();
|
||||||
|
|
||||||
|
} catch (TargetParameterCountException) {
|
||||||
|
|
||||||
|
Console.WriteLine("Missing parameters");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.WriteLine("The program ran to completition");
|
Console.WriteLine("The program ran to completition");
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace MewtocolNet {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains information about the plc and its cpu
|
/// Contains information about the plc and its cpu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CpuInfo {
|
public struct CpuInfo {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The cpu type of the plc
|
/// The cpu type of the plc
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(task.IsCanceled) return default(T);
|
||||||
|
|
||||||
return task.Result;
|
return task.Result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
MewtocolNet/Extensions/SerialPortExtensions.cs
Normal file
36
MewtocolNet/Extensions/SerialPortExtensions.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
internal static class SerialPortExtensions {
|
||||||
|
|
||||||
|
public async static Task WriteAsync (this SerialPort serialPort, byte[] buffer, int offset, int count) {
|
||||||
|
|
||||||
|
await serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async static Task ReadAsync (this SerialPort serialPort, byte[] buffer, int offset, int count) {
|
||||||
|
var bytesToRead = count;
|
||||||
|
var temp = new byte[count];
|
||||||
|
|
||||||
|
while (bytesToRead > 0) {
|
||||||
|
var readBytes = await serialPort.BaseStream.ReadAsync(temp, 0, bytesToRead);
|
||||||
|
Array.Copy(temp, 0, buffer, offset + count - bytesToRead, readBytes);
|
||||||
|
bytesToRead -= readBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async static Task<byte[]> ReadAsync (this SerialPort serialPort, int count) {
|
||||||
|
var buffer = new byte[count];
|
||||||
|
await serialPort.ReadAsync(buffer, 0, count);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MewtocolNet.Queue {
|
namespace MewtocolNet.Queue {
|
||||||
|
|
||||||
internal class SerialQueue {
|
internal class AsyncQueue {
|
||||||
|
|
||||||
readonly object _locker = new object();
|
readonly object _locker = new object();
|
||||||
readonly WeakReference<Task> _lastTask = new WeakReference<Task>(null);
|
readonly WeakReference<Task> _lastTask = new WeakReference<Task>(null);
|
||||||
|
|
||||||
internal Task<T> Enqueue<T>(Func<Task<T>> asyncFunction) {
|
internal Task<T> Enqueue<T>(Func<Task<T>> asyncFunction) {
|
||||||
lock (_locker) {
|
lock (_locker) {
|
||||||
|
|
||||||
Task lastTask;
|
Task lastTask;
|
||||||
Task<T> resultTask;
|
Task<T> resultTask;
|
||||||
|
|
||||||
@@ -22,6 +25,7 @@ namespace MewtocolNet.Queue {
|
|||||||
_lastTask.SetTarget(resultTask);
|
_lastTask.SetTarget(resultTask);
|
||||||
|
|
||||||
return resultTask;
|
return resultTask;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
96
MewtocolNet/IPlc.cs
Normal file
96
MewtocolNet/IPlc.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a interface for Panasonic PLCs
|
||||||
|
/// </summary>
|
||||||
|
public interface IPlc : IDisposable {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current connection state of the interface
|
||||||
|
/// </summary>
|
||||||
|
bool IsConnected { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current transmission speed in bytes per second
|
||||||
|
/// </summary>
|
||||||
|
int BytesPerSecondUpstream { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current transmission speed in bytes per second
|
||||||
|
/// </summary>
|
||||||
|
int BytesPerSecondDownstream { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current poller cycle duration
|
||||||
|
/// </summary>
|
||||||
|
int PollerCycleDurationMs { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Currently queued message count
|
||||||
|
/// </summary>
|
||||||
|
int QueuedMessages { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The registered data registers of the PLC
|
||||||
|
/// </summary>
|
||||||
|
IEnumerable<IRegister> Registers { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generic information about the connected PLC
|
||||||
|
/// </summary>
|
||||||
|
PLCInfo PlcInfo { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The station number of the PLC
|
||||||
|
/// </summary>
|
||||||
|
int StationNumber { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initial connection timeout in milliseconds
|
||||||
|
/// </summary>
|
||||||
|
int ConnectTimeout { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to establish a connection with the device asynchronously
|
||||||
|
/// </summary>
|
||||||
|
Task ConnectAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnects the devive from its current connection
|
||||||
|
/// </summary>
|
||||||
|
void Disconnect();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the checksum automatically and sends a command to the PLC then awaits results
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
|
||||||
|
/// <param name="withTerminator">Append the checksum and bcc automatically</param>
|
||||||
|
/// <param name="timeoutMs">Timout to wait for a response</param>
|
||||||
|
/// <returns>Returns the result</returns>
|
||||||
|
Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use this to await the first poll iteration after connecting,
|
||||||
|
/// This also completes if the initial connection fails
|
||||||
|
/// </summary>
|
||||||
|
Task AwaitFirstDataAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a single poller cycle manually,
|
||||||
|
/// useful if you want to use a custom update frequency
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The number of inidvidual mewtocol commands sent</returns>
|
||||||
|
Task<int> RunPollerCylceManual();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the connection info string
|
||||||
|
/// </summary>
|
||||||
|
string GetConnectionInfo();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
MewtocolNet/IPlcEthernet.cs
Normal file
33
MewtocolNet/IPlcEthernet.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a interface for Panasonic PLCs over a ethernet connection
|
||||||
|
/// </summary>
|
||||||
|
public interface IPlcEthernet : IPlc {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current IP of the PLC connection
|
||||||
|
/// </summary>
|
||||||
|
string IpAddress { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current port of the PLC connection
|
||||||
|
/// </summary>
|
||||||
|
int Port { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches a poller to the interface
|
||||||
|
/// </summary>
|
||||||
|
public IPlcEthernet WithPoller();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the serial interface
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_ip">IP adress of the PLC</param>
|
||||||
|
/// <param name="_port">Port of the PLC</param>
|
||||||
|
/// <param name="_station">Station Number of the PLC</param>
|
||||||
|
void ConfigureConnection(string _ip, int _port = 9094, int _station = 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
57
MewtocolNet/IPlcSerial.cs
Normal file
57
MewtocolNet/IPlcSerial.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a interface for Panasonic PLCs over a serial port connection
|
||||||
|
/// </summary>
|
||||||
|
public interface IPlcSerial : IPlc {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Port name of the serial port that this device is configured for
|
||||||
|
/// </summary>
|
||||||
|
string PortName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The serial connection baud rate that this device is configured for
|
||||||
|
/// </summary>
|
||||||
|
int SerialBaudRate { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The serial connection data bits
|
||||||
|
/// </summary>
|
||||||
|
int SerialDataBits { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The serial connection parity
|
||||||
|
/// </summary>
|
||||||
|
Parity SerialParity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The serial connection stop bits
|
||||||
|
/// </summary>
|
||||||
|
StopBits SerialStopBits { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches a poller to the interface
|
||||||
|
/// </summary>
|
||||||
|
public IPlcSerial WithPoller();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets up the connection settings for the device
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_portName">Port name of COM port</param>
|
||||||
|
/// <param name="_baudRate">The serial connection baud rate</param>
|
||||||
|
/// <param name="_dataBits">The serial connection data bits</param>
|
||||||
|
/// <param name="_parity">The serial connection parity</param>
|
||||||
|
/// <param name="_stopBits">The serial connection stop bits</param>
|
||||||
|
/// <param name="_station">The station number of the PLC</param>
|
||||||
|
void ConfigureConnection(string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
12
MewtocolNet/InternalEnums/CommandState.cs
Normal file
12
MewtocolNet/InternalEnums/CommandState.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
internal enum CommandState {
|
||||||
|
|
||||||
|
Initial,
|
||||||
|
LineFeed,
|
||||||
|
RequestedNextFrame,
|
||||||
|
Complete
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ namespace MewtocolNet.Logging {
|
|||||||
if (sender == null) {
|
if (sender == null) {
|
||||||
LogInvoked?.Invoke(DateTime.Now, loglevel, message);
|
LogInvoked?.Invoke(DateTime.Now, loglevel, message);
|
||||||
} else {
|
} else {
|
||||||
LogInvoked?.Invoke(DateTime.Now, loglevel, $"[{sender.GetConnectionPortInfo()}] {message}");
|
LogInvoked?.Invoke(DateTime.Now, loglevel, $"[{sender.GetConnectionInfo()}] {message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
62
MewtocolNet/Mewtocol.cs
Normal file
62
MewtocolNet/Mewtocol.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builder helper for mewtocol interfaces
|
||||||
|
/// </summary>
|
||||||
|
public static class Mewtocol {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a ethernet based Mewtocol Interface
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_ip"></param>
|
||||||
|
/// <param name="_port"></param>
|
||||||
|
/// <param name="_station"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IPlcEthernet Ethernet (string _ip, int _port = 9094, int _station = 1) {
|
||||||
|
|
||||||
|
var instance = new MewtocolInterfaceTcp();
|
||||||
|
instance.ConfigureConnection(_ip, _port, _station);
|
||||||
|
return instance;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a serial port based Mewtocol Interface
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_portName"></param>
|
||||||
|
/// <param name="_baudRate"></param>
|
||||||
|
/// <param name="_dataBits"></param>
|
||||||
|
/// <param name="_parity"></param>
|
||||||
|
/// <param name="_stopBits"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IPlcSerial Serial (string _portName, BaudRate _baudRate = BaudRate._19200, DataBits _dataBits = DataBits.Eight, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 1) {
|
||||||
|
|
||||||
|
var instance = new MewtocolInterfaceSerial();
|
||||||
|
instance.ConfigureConnection(_portName, (int)_baudRate, (int)_dataBits, _parity, _stopBits, _station);
|
||||||
|
return instance;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a serial mewtocol interface that finds the correct settings for the given port name automatically
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="_portName"></param>
|
||||||
|
/// <param name="_station"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IPlcSerial SerialAuto (string _portName, int _station = 1) {
|
||||||
|
|
||||||
|
var instance = new MewtocolInterfaceSerial();
|
||||||
|
instance.ConfigureConnection(_portName, _station);
|
||||||
|
instance.ConfigureConnectionAuto();
|
||||||
|
return instance;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,18 +1,12 @@
|
|||||||
using MewtocolNet.Exceptions;
|
using MewtocolNet.Logging;
|
||||||
using MewtocolNet.Logging;
|
|
||||||
using MewtocolNet.Queue;
|
using MewtocolNet.Queue;
|
||||||
using MewtocolNet.RegisterAttributes;
|
|
||||||
using MewtocolNet.Registers;
|
using MewtocolNet.Registers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel.Design;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -20,177 +14,119 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace MewtocolNet {
|
namespace MewtocolNet {
|
||||||
|
|
||||||
/// <summary>
|
public partial class MewtocolInterface : IPlc, INotifyPropertyChanged, IDisposable {
|
||||||
/// The PLC com interface class
|
|
||||||
/// </summary>
|
|
||||||
public partial class MewtocolInterface : INotifyPropertyChanged, IDisposable {
|
|
||||||
|
|
||||||
/// <summary>
|
#region Private fields
|
||||||
/// Gets triggered when the PLC connection was established
|
|
||||||
/// </summary>
|
private protected Stream stream;
|
||||||
|
|
||||||
|
private int tcpMessagesSentThisCycle = 0;
|
||||||
|
private int pollerCycleDurationMs;
|
||||||
|
private volatile int queuedMessages;
|
||||||
|
private bool isConnected;
|
||||||
|
private PLCInfo plcInfo;
|
||||||
|
private protected int stationNumber;
|
||||||
|
|
||||||
|
private protected int bytesTotalCountedUpstream = 0;
|
||||||
|
private protected int bytesTotalCountedDownstream = 0;
|
||||||
|
private protected int cycleTimeMs = 25;
|
||||||
|
private protected int bytesPerSecondUpstream = 0;
|
||||||
|
private protected int bytesPerSecondDownstream = 0;
|
||||||
|
|
||||||
|
private protected AsyncQueue queue = new AsyncQueue();
|
||||||
|
private protected int RecBufferSize = 128;
|
||||||
|
private protected Stopwatch speedStopwatchUpstr;
|
||||||
|
private protected Stopwatch speedStopwatchDownstr;
|
||||||
|
private protected Task firstPollTask = new Task(() => { });
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal fields
|
||||||
|
|
||||||
|
internal event Action PolledCycle;
|
||||||
|
internal volatile bool pollerTaskStopped = true;
|
||||||
|
internal volatile bool pollerFirstCycle;
|
||||||
|
internal bool usePoller = false;
|
||||||
|
|
||||||
|
internal List<BaseRegister> RegistersUnderlying { get; private set; } = new List<BaseRegister>();
|
||||||
|
internal IEnumerable<IRegisterInternal> RegistersInternal => RegistersUnderlying.Cast<IRegisterInternal>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Read Only Properties / Fields
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public event Action<PLCInfo> Connected;
|
public event Action<PLCInfo> Connected;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets triggered when the PLC connection was closed or lost
|
|
||||||
/// </summary>
|
|
||||||
public event Action Disconnected;
|
public event Action Disconnected;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets triggered when a registered data register changes its value
|
|
||||||
/// </summary>
|
|
||||||
public event Action<IRegister> RegisterChanged;
|
public event Action<IRegister> RegisterChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets triggered when a property of the interface changes
|
|
||||||
/// </summary>
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
private int connectTimeout = 3000;
|
/// <inheritdoc/>
|
||||||
/// <summary>
|
public bool Disposed { get; private set; }
|
||||||
/// The initial connection timeout in milliseconds
|
|
||||||
/// </summary>
|
|
||||||
public int ConnectTimeout {
|
|
||||||
get { return connectTimeout; }
|
|
||||||
set { connectTimeout = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private volatile int queuedMessages;
|
/// <inheritdoc/>
|
||||||
/// <summary>
|
public int QueuedMessages => queuedMessages;
|
||||||
/// Currently queued Messages
|
|
||||||
/// </summary>
|
|
||||||
public int QueuedMessages {
|
|
||||||
get => queuedMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// The host ip endpoint, leave it null to use an automatic interface
|
|
||||||
/// </summary>
|
|
||||||
public IPEndPoint HostEndpoint { get; set; }
|
|
||||||
|
|
||||||
private bool isConnected;
|
|
||||||
/// <summary>
|
|
||||||
/// The current connection state of the interface
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected {
|
public bool IsConnected {
|
||||||
get => isConnected;
|
get => isConnected;
|
||||||
private set {
|
private protected set {
|
||||||
isConnected = value;
|
isConnected = value;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsConnected)));
|
OnPropChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool disposed;
|
/// <inheritdoc/>
|
||||||
/// <summary>
|
|
||||||
/// True if the current interface was disposed
|
|
||||||
/// </summary>
|
|
||||||
public bool Disposed {
|
|
||||||
get { return disposed; }
|
|
||||||
private set { disposed = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PLCInfo plcInfo;
|
|
||||||
/// <summary>
|
|
||||||
/// Generic information about the connected PLC
|
|
||||||
/// </summary>
|
|
||||||
public PLCInfo PlcInfo {
|
public PLCInfo PlcInfo {
|
||||||
get => plcInfo;
|
get => plcInfo;
|
||||||
private set {
|
private set {
|
||||||
plcInfo = value;
|
plcInfo = value;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PlcInfo)));
|
OnPropChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// The registered data registers of the PLC
|
|
||||||
/// </summary>
|
|
||||||
internal List<BaseRegister> RegistersUnderlying { get; private set; } = new List<BaseRegister>();
|
|
||||||
|
|
||||||
public IEnumerable<IRegister> Registers => RegistersUnderlying.Cast<IRegister>();
|
public IEnumerable<IRegister> Registers => RegistersUnderlying.Cast<IRegister>();
|
||||||
|
|
||||||
internal IEnumerable<IRegisterInternal> RegistersInternal => RegistersUnderlying.Cast<IRegisterInternal>();
|
/// <inheritdoc/>
|
||||||
|
|
||||||
private string ip;
|
|
||||||
private int port;
|
|
||||||
private int stationNumber;
|
|
||||||
private int cycleTimeMs = 25;
|
|
||||||
|
|
||||||
private int bytesTotalCountedUpstream = 0;
|
|
||||||
private int bytesTotalCountedDownstream = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current IP of the PLC connection
|
|
||||||
/// </summary>
|
|
||||||
public string IpAddress => ip;
|
|
||||||
/// <summary>
|
|
||||||
/// The current port of the PLC connection
|
|
||||||
/// </summary>
|
|
||||||
public int Port => port;
|
|
||||||
/// <summary>
|
|
||||||
/// The station number of the PLC
|
|
||||||
/// </summary>
|
|
||||||
public int StationNumber => stationNumber;
|
public int StationNumber => stationNumber;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// The duration of the last message cycle
|
|
||||||
/// </summary>
|
|
||||||
public int CycleTimeMs {
|
|
||||||
get { return cycleTimeMs; }
|
|
||||||
private set {
|
|
||||||
cycleTimeMs = value;
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CycleTimeMs)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int bytesPerSecondUpstream = 0;
|
|
||||||
/// <summary>
|
|
||||||
/// The current transmission speed in bytes per second
|
|
||||||
/// </summary>
|
|
||||||
public int BytesPerSecondUpstream {
|
public int BytesPerSecondUpstream {
|
||||||
get { return bytesPerSecondUpstream; }
|
get { return bytesPerSecondUpstream; }
|
||||||
private set {
|
private protected set {
|
||||||
bytesPerSecondUpstream = value;
|
bytesPerSecondUpstream = value;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BytesPerSecondUpstream)));
|
OnPropChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int bytesPerSecondDownstream = 0;
|
/// <inheritdoc/>
|
||||||
/// <summary>
|
|
||||||
/// The current transmission speed in bytes per second
|
|
||||||
/// </summary>
|
|
||||||
public int BytesPerSecondDownstream {
|
public int BytesPerSecondDownstream {
|
||||||
get { return bytesPerSecondDownstream; }
|
get { return bytesPerSecondDownstream; }
|
||||||
private set {
|
private protected set {
|
||||||
bytesPerSecondDownstream = value;
|
bytesPerSecondDownstream = value;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BytesPerSecondDownstream)));
|
OnPropChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal NetworkStream stream;
|
#endregion
|
||||||
internal TcpClient client;
|
|
||||||
internal readonly SerialQueue queue = new SerialQueue();
|
|
||||||
private int RecBufferSize = 128;
|
|
||||||
internal int SendExceptionsInRow = 0;
|
|
||||||
internal bool ImportantTaskRunning = false;
|
|
||||||
|
|
||||||
private Stopwatch speedStopwatchUpstr;
|
#region Public read/write Properties / Fields
|
||||||
private Stopwatch speedStopwatchDownstr;
|
|
||||||
|
|
||||||
private Task firstPollTask = new Task(() => { });
|
/// <inheritdoc/>
|
||||||
|
public int ConnectTimeout { get; set; } = 3000;
|
||||||
|
|
||||||
#region Initialization
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
#region Methods
|
||||||
/// Builds a new Interfacer for a PLC
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="_ip">IP adress of the PLC</param>
|
|
||||||
/// <param name="_port">Port of the PLC</param>
|
|
||||||
/// <param name="_station">Station Number of the PLC</param>
|
|
||||||
public MewtocolInterface(string _ip, int _port = 9094, int _station = 1) {
|
|
||||||
|
|
||||||
ip = _ip;
|
private protected MewtocolInterface () {
|
||||||
port = _port;
|
|
||||||
stationNumber = _station;
|
|
||||||
|
|
||||||
Connected += MewtocolInterface_Connected;
|
Connected += MewtocolInterface_Connected;
|
||||||
|
|
||||||
@@ -216,210 +152,60 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
/// <inheritdoc/>
|
||||||
|
public virtual Task ConnectAsync () => throw new NotImplementedException();
|
||||||
|
|
||||||
#region Setup
|
/// <inheritdoc/>
|
||||||
|
public async Task AwaitFirstDataAsync() => await firstPollTask;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// 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
|
|
||||||
/// <para/>
|
|
||||||
/// If <see cref="WithPoller"/> is used it waits for the first data receive cycle to complete
|
|
||||||
/// </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) {
|
|
||||||
|
|
||||||
firstPollTask = new Task(() => { });
|
|
||||||
|
|
||||||
Logger.Log("Connecting to PLC...", LogLevel.Info, this);
|
|
||||||
|
|
||||||
var plcinf = await GetPLCInfoAsync();
|
|
||||||
|
|
||||||
if (plcinf != null) {
|
|
||||||
|
|
||||||
Logger.Log("Connected", LogLevel.Info, this);
|
|
||||||
Logger.Log($"\n\n{plcinf.ToString()}\n\n", LogLevel.Verbose, this);
|
|
||||||
|
|
||||||
Connected?.Invoke(plcinf);
|
|
||||||
|
|
||||||
if (!usePoller) {
|
|
||||||
if (OnConnected != null) OnConnected(plcinf);
|
|
||||||
firstPollTask.RunSynchronously();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
PolledCycle += OnPollCycleDone;
|
|
||||||
void OnPollCycleDone() {
|
|
||||||
|
|
||||||
if (OnConnected != null) OnConnected(plcinf);
|
|
||||||
firstPollTask.RunSynchronously();
|
|
||||||
PolledCycle -= OnPollCycleDone;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (OnFailed != null) {
|
|
||||||
OnFailed();
|
|
||||||
Disconnected?.Invoke();
|
|
||||||
firstPollTask.RunSynchronously();
|
|
||||||
Logger.Log("Initial connection failed", LogLevel.Info, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Use this to await the first poll iteration after connecting,
|
|
||||||
/// This also completes if the initial connection fails
|
|
||||||
/// </summary>
|
|
||||||
public async Task AwaitFirstDataAsync () => await firstPollTask;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Changes the connections parameters of the PLC, only applyable when the connection is offline
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="_ip">Ip adress</param>
|
|
||||||
/// <param name="_port">Port number</param>
|
|
||||||
/// <param name="_station">Station number</param>
|
|
||||||
public void ChangeConnectionSettings(string _ip, int _port, int _station = 1) {
|
|
||||||
|
|
||||||
if (IsConnected)
|
|
||||||
throw new Exception("Cannot change the connection settings while the PLC is connected");
|
|
||||||
|
|
||||||
ip = _ip;
|
|
||||||
port = _port;
|
|
||||||
stationNumber = _station;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Closes the connection all cyclic polling
|
|
||||||
/// </summary>
|
|
||||||
public void Disconnect() {
|
public void Disconnect() {
|
||||||
|
|
||||||
if (!IsConnected)
|
if (!IsConnected) return;
|
||||||
return;
|
|
||||||
|
pollCycleTask.Wait();
|
||||||
|
|
||||||
OnMajorSocketExceptionWhileConnected();
|
OnMajorSocketExceptionWhileConnected();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Attaches a poller to the interface that continously
|
public void Dispose() {
|
||||||
/// polls the registered data registers and writes the values to them
|
|
||||||
/// </summary>
|
|
||||||
public MewtocolInterface WithPoller() {
|
|
||||||
|
|
||||||
usePoller = true;
|
if (Disposed) return;
|
||||||
return this;
|
Disconnect();
|
||||||
|
//GC.SuppressFinalize(this);
|
||||||
|
Disposed = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
/// <inheritdoc/>
|
||||||
|
public virtual string GetConnectionInfo() => throw new NotImplementedException();
|
||||||
|
|
||||||
#region TCP connection state handling
|
/// <inheritdoc/>
|
||||||
|
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1) {
|
||||||
private async Task ConnectTCP() {
|
|
||||||
|
|
||||||
if (!IPAddress.TryParse(ip, out var targetIP)) {
|
|
||||||
throw new ArgumentException("The IP adress of the PLC was no valid format");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (HostEndpoint != null) {
|
|
||||||
|
|
||||||
client = new TcpClient(HostEndpoint) {
|
|
||||||
ReceiveBufferSize = RecBufferSize,
|
|
||||||
NoDelay = false,
|
|
||||||
};
|
|
||||||
var ep = (IPEndPoint)client.Client.LocalEndPoint;
|
|
||||||
Logger.Log($"Connecting [MAN] endpoint: {ep.Address}:{ep.Port}", LogLevel.Verbose, this);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
client = new TcpClient() {
|
|
||||||
ReceiveBufferSize = RecBufferSize,
|
|
||||||
NoDelay = false,
|
|
||||||
ExclusiveAddressUse = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = client.BeginConnect(targetIP, port, null, null);
|
|
||||||
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeout));
|
|
||||||
|
|
||||||
if (!success || !client.Connected) {
|
|
||||||
OnMajorSocketExceptionWhileConnecting();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HostEndpoint == null) {
|
|
||||||
var ep = (IPEndPoint)client.Client.LocalEndPoint;
|
|
||||||
Logger.Log($"Connecting [AUTO] endpoint: {ep.Address.MapToIPv4()}:{ep.Port}", LogLevel.Verbose, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = client.GetStream();
|
|
||||||
stream.ReadTimeout = 1000;
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
|
||||||
|
|
||||||
} catch (SocketException) {
|
|
||||||
|
|
||||||
OnMajorSocketExceptionWhileConnecting();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Low level command handling
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the checksum automatically and sends a command to the PLC then awaits results
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
|
|
||||||
/// <param name="withTerminator">Append the checksum and bcc automatically</param>
|
|
||||||
/// <returns>Returns the result</returns>
|
|
||||||
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true) {
|
|
||||||
|
|
||||||
//send request
|
//send request
|
||||||
queuedMessages++;
|
queuedMessages++;
|
||||||
var tempResponse = await queue.Enqueue(() => SendFrameAsync(_msg, withTerminator, withTerminator));
|
|
||||||
|
var tempResponse = queue.Enqueue(async () => await SendFrameAsync(_msg, withTerminator, withTerminator));
|
||||||
|
|
||||||
|
if (await Task.WhenAny(tempResponse, Task.Delay(timeoutMs)) != tempResponse) {
|
||||||
|
// timeout logic
|
||||||
|
return new MewtocolFrameResponse(403, "Timed out");
|
||||||
|
}
|
||||||
|
|
||||||
tcpMessagesSentThisCycle++;
|
tcpMessagesSentThisCycle++;
|
||||||
queuedMessages--;
|
queuedMessages--;
|
||||||
|
|
||||||
return tempResponse;
|
return tempResponse.Result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MewtocolFrameResponse> SendFrameAsync (string frame, bool useBcc = true, bool useCr = true) {
|
private protected async Task<MewtocolFrameResponse> SendFrameAsync (string frame, bool useBcc = true, bool useCr = true) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
//stop time
|
|
||||||
if (speedStopwatchUpstr == null) {
|
|
||||||
speedStopwatchUpstr = Stopwatch.StartNew();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (speedStopwatchUpstr.Elapsed.TotalSeconds >= 1) {
|
|
||||||
speedStopwatchUpstr.Restart();
|
|
||||||
bytesTotalCountedUpstream = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char CR = '\r';
|
|
||||||
const char DELIMITER = '&';
|
|
||||||
|
|
||||||
if (client == null || !client.Connected) await ConnectTCP();
|
|
||||||
|
|
||||||
if (useBcc)
|
if (useBcc)
|
||||||
frame = $"{frame.BuildBCCFrame()}";
|
frame = $"{frame.BuildBCCFrame()}";
|
||||||
|
|
||||||
@@ -428,82 +214,32 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
//write inital command
|
//write inital command
|
||||||
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
||||||
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
stream.Write(writeBuffer, 0, writeBuffer.Length);
|
||||||
|
|
||||||
//calc upstream speed
|
|
||||||
bytesTotalCountedUpstream += writeBuffer.Length;
|
|
||||||
|
|
||||||
var perSecUpstream = (double)((bytesTotalCountedUpstream / speedStopwatchUpstr.Elapsed.TotalMilliseconds) * 1000);
|
|
||||||
if (perSecUpstream <= 10000)
|
|
||||||
BytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
|
|
||||||
|
|
||||||
|
|
||||||
Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this);
|
Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this);
|
||||||
Logger.Log($"--> OUT MSG: {frame.Replace("\r", "(CR)")}", LogLevel.Critical, this);
|
Logger.Log($"--> OUT MSG: {frame.Replace("\r", "(CR)")}", LogLevel.Critical, this);
|
||||||
|
|
||||||
//read
|
var readResult = await ReadCommandAsync();
|
||||||
List<byte> totalResponse = new List<byte>();
|
|
||||||
byte[] responseBuffer = new byte[512];
|
|
||||||
|
|
||||||
bool wasMultiFramedResponse = false;
|
//did not receive bytes but no errors, the com port was not configured right
|
||||||
CommandState cmdState = CommandState.Intial;
|
if (readResult.Item1.Length == 0) {
|
||||||
|
|
||||||
//read until command complete
|
return new MewtocolFrameResponse(402, "Receive buffer was empty");
|
||||||
while (cmdState != CommandState.Complete) {
|
|
||||||
|
|
||||||
//time measuring
|
|
||||||
if (speedStopwatchDownstr == null) {
|
|
||||||
speedStopwatchDownstr = Stopwatch.StartNew();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (speedStopwatchDownstr.Elapsed.TotalSeconds >= 1) {
|
|
||||||
speedStopwatchDownstr.Restart();
|
|
||||||
bytesTotalCountedDownstream = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
responseBuffer = new byte[128];
|
|
||||||
|
|
||||||
await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
|
|
||||||
|
|
||||||
bool terminatorReceived = responseBuffer.Any(x => x == (byte)CR);
|
|
||||||
var delimiterTerminatorIdx = responseBuffer.SearchBytePattern(new byte[] { (byte)DELIMITER, (byte)CR });
|
|
||||||
|
|
||||||
if (terminatorReceived && delimiterTerminatorIdx == -1) {
|
|
||||||
cmdState = CommandState.Complete;
|
|
||||||
} else if (delimiterTerminatorIdx != -1) {
|
|
||||||
cmdState = CommandState.RequestedNextFrame;
|
|
||||||
} else {
|
|
||||||
cmdState = CommandState.LineFeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
//log message parts
|
|
||||||
var tempMsg = Encoding.UTF8.GetString(responseBuffer).Replace("\r", "(CR)");
|
|
||||||
Logger.Log($">> IN PART: {tempMsg}, Command state: {cmdState}", LogLevel.Critical, this);
|
|
||||||
|
|
||||||
//error response
|
|
||||||
int errorCode = CheckForErrorMsg(tempMsg);
|
|
||||||
if (errorCode != 0) return new MewtocolFrameResponse(errorCode);
|
|
||||||
|
|
||||||
//add complete response to collector without empty bytes
|
|
||||||
totalResponse.AddRange(responseBuffer.Where(x => x != (byte)0x0));
|
|
||||||
|
|
||||||
//request next part of the command if the delimiter was received
|
|
||||||
if (cmdState == CommandState.RequestedNextFrame) {
|
|
||||||
|
|
||||||
Logger.Log($"Requesting next frame...", LogLevel.Critical, this);
|
|
||||||
|
|
||||||
wasMultiFramedResponse = true;
|
|
||||||
writeBuffer = Encoding.UTF8.GetBytes("%01**&\r");
|
|
||||||
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//build final result
|
//build final result
|
||||||
string resString = Encoding.UTF8.GetString(totalResponse.ToArray());
|
string resString = Encoding.UTF8.GetString(readResult.Item1);
|
||||||
|
|
||||||
if (wasMultiFramedResponse) {
|
//check if the message had errors
|
||||||
|
//error response
|
||||||
|
var gotErrorcode = CheckForErrorMsg(resString);
|
||||||
|
if (gotErrorcode != 0) {
|
||||||
|
return new MewtocolFrameResponse(gotErrorcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
//was multiframed response
|
||||||
|
if (readResult.Item2) {
|
||||||
|
|
||||||
var split = resString.Split('&');
|
var split = resString.Split('&');
|
||||||
|
|
||||||
@@ -519,13 +255,6 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bytesTotalCountedDownstream += Encoding.ASCII.GetByteCount(resString);
|
|
||||||
|
|
||||||
var perSecDownstream = (double)((bytesTotalCountedDownstream / speedStopwatchDownstr.Elapsed.TotalMilliseconds) * 1000);
|
|
||||||
|
|
||||||
if (perSecUpstream <= 10000)
|
|
||||||
BytesPerSecondDownstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
|
|
||||||
|
|
||||||
Logger.Log($"<-- IN MSG: {resString.Replace("\r", "(CR)")}", LogLevel.Critical, this);
|
Logger.Log($"<-- IN MSG: {resString.Replace("\r", "(CR)")}", LogLevel.Critical, this);
|
||||||
Logger.Log($"Total bytes parsed: {resString.Length}", LogLevel.Critical, this);
|
Logger.Log($"Total bytes parsed: {resString.Length}", LogLevel.Critical, this);
|
||||||
Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this);
|
Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this);
|
||||||
@@ -540,7 +269,73 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CheckForErrorMsg (string msg) {
|
private protected async Task<(byte[], bool)> ReadCommandAsync () {
|
||||||
|
|
||||||
|
//read total
|
||||||
|
List<byte> totalResponse = new List<byte>();
|
||||||
|
bool wasMultiFramedResponse = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
bool needsRead = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
byte[] buffer = new byte[128];
|
||||||
|
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
|
||||||
|
byte[] received = new byte[bytesRead];
|
||||||
|
Buffer.BlockCopy(buffer, 0, received, 0, bytesRead);
|
||||||
|
|
||||||
|
var commandRes = ParseBufferFrame(received);
|
||||||
|
needsRead = commandRes == CommandState.LineFeed || commandRes == CommandState.RequestedNextFrame;
|
||||||
|
|
||||||
|
var tempMsg = Encoding.UTF8.GetString(received).Replace("\r", "(CR)");
|
||||||
|
Logger.Log($">> IN PART: {tempMsg}, Command state: {commandRes}", LogLevel.Critical, this);
|
||||||
|
|
||||||
|
//add complete response to collector without empty bytes
|
||||||
|
totalResponse.AddRange(received.Where(x => x != (byte)0x0));
|
||||||
|
|
||||||
|
if (commandRes == CommandState.RequestedNextFrame) {
|
||||||
|
|
||||||
|
//request next frame
|
||||||
|
var writeBuffer = Encoding.UTF8.GetBytes("%01**&\r");
|
||||||
|
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
||||||
|
wasMultiFramedResponse = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (needsRead);
|
||||||
|
|
||||||
|
} catch (OperationCanceledException) { }
|
||||||
|
|
||||||
|
return (totalResponse.ToArray(), wasMultiFramedResponse);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private protected CommandState ParseBufferFrame(byte[] received) {
|
||||||
|
|
||||||
|
const char CR = '\r';
|
||||||
|
const char DELIMITER = '&';
|
||||||
|
|
||||||
|
CommandState cmdState;
|
||||||
|
|
||||||
|
bool terminatorReceived = received.Any(x => x == (byte)CR);
|
||||||
|
var delimiterTerminatorIdx = received.ToArray().SearchBytePattern(new byte[] { (byte)DELIMITER, (byte)CR });
|
||||||
|
|
||||||
|
if (terminatorReceived && delimiterTerminatorIdx == -1) {
|
||||||
|
cmdState = CommandState.Complete;
|
||||||
|
} else if (delimiterTerminatorIdx != -1) {
|
||||||
|
cmdState = CommandState.RequestedNextFrame;
|
||||||
|
} else {
|
||||||
|
cmdState = CommandState.LineFeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdState;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private protected int CheckForErrorMsg (string msg) {
|
||||||
|
|
||||||
//error catching
|
//error catching
|
||||||
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
|
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
|
||||||
@@ -557,11 +352,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
private protected void OnMajorSocketExceptionWhileConnecting() {
|
||||||
|
|
||||||
#region Disposing
|
|
||||||
|
|
||||||
private void OnMajorSocketExceptionWhileConnecting() {
|
|
||||||
|
|
||||||
if (IsConnected) {
|
if (IsConnected) {
|
||||||
|
|
||||||
@@ -572,7 +363,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMajorSocketExceptionWhileConnected() {
|
private protected void OnMajorSocketExceptionWhileConnected() {
|
||||||
|
|
||||||
if (IsConnected) {
|
if (IsConnected) {
|
||||||
|
|
||||||
@@ -583,25 +374,30 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private protected virtual void OnConnected (PLCInfo plcinf) {
|
||||||
|
|
||||||
/// <summary>
|
Logger.Log("Connected", LogLevel.Info, this);
|
||||||
/// Disposes the current interface and clears all its members
|
Logger.Log($"\n\n{plcinf.ToString()}\n\n", LogLevel.Verbose, this);
|
||||||
/// </summary>
|
|
||||||
public void Dispose() {
|
|
||||||
|
|
||||||
if (Disposed) return;
|
IsConnected = true;
|
||||||
|
|
||||||
Disconnect();
|
Connected?.Invoke(plcinf);
|
||||||
|
|
||||||
//GC.SuppressFinalize(this);
|
if (!usePoller) {
|
||||||
|
firstPollTask.RunSynchronously();
|
||||||
|
}
|
||||||
|
|
||||||
Disposed = true;
|
PolledCycle += OnPollCycleDone;
|
||||||
|
void OnPollCycleDone() {
|
||||||
|
|
||||||
|
firstPollTask.RunSynchronously();
|
||||||
|
PolledCycle -= OnPollCycleDone;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisconnect () {
|
}
|
||||||
|
|
||||||
if (IsConnected) {
|
private protected virtual void OnDisconnect () {
|
||||||
|
|
||||||
BytesPerSecondDownstream = 0;
|
BytesPerSecondDownstream = 0;
|
||||||
BytesPerSecondUpstream = 0;
|
BytesPerSecondUpstream = 0;
|
||||||
@@ -612,14 +408,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
Disconnected?.Invoke();
|
Disconnected?.Invoke();
|
||||||
KillPoller();
|
KillPoller();
|
||||||
client.Close();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private protected void ClearRegisterVals() {
|
||||||
|
|
||||||
|
|
||||||
private void ClearRegisterVals() {
|
|
||||||
|
|
||||||
for (int i = 0; i < RegistersUnderlying.Count; i++) {
|
for (int i = 0; i < RegistersUnderlying.Count; i++) {
|
||||||
|
|
||||||
@@ -630,28 +422,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
private protected void OnPropChange([CallerMemberName] string propertyName = null) {
|
||||||
|
|
||||||
#region Accessing Info
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the connection info string
|
|
||||||
/// </summary>
|
|
||||||
public string GetConnectionPortInfo() {
|
|
||||||
|
|
||||||
return $"{IpAddress}:{Port}";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Property change evnts
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Triggers a property changed event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyName">Name of the property to trigger for</param>
|
|
||||||
private void OnPropChange ([CallerMemberName]string propertyName = null) {
|
|
||||||
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using MewtocolNet.Exceptions;
|
using MewtocolNet.Exceptions;
|
||||||
using MewtocolNet.Logging;
|
using MewtocolNet.Logging;
|
||||||
using MewtocolNet.RegisterAttributes;
|
using MewtocolNet.RegisterAttributes;
|
||||||
|
using MewtocolNet.RegisterBuilding;
|
||||||
using MewtocolNet.Registers;
|
using MewtocolNet.Registers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
@@ -12,24 +13,14 @@ using System.Text;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MewtocolNet
|
namespace MewtocolNet {
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The PLC com interface class
|
/// The PLC com interface class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MewtocolInterface {
|
public partial class MewtocolInterface {
|
||||||
|
|
||||||
internal event Action PolledCycle;
|
internal Task pollCycleTask;
|
||||||
|
|
||||||
internal volatile bool pollerTaskStopped = true;
|
|
||||||
internal volatile bool pollerFirstCycle;
|
|
||||||
|
|
||||||
internal bool usePoller = false;
|
|
||||||
|
|
||||||
private int tcpMessagesSentThisCycle = 0;
|
|
||||||
|
|
||||||
private int pollerCycleDurationMs;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the poller is actvice (can be paused)
|
/// True if the poller is actvice (can be paused)
|
||||||
@@ -87,7 +78,8 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
tcpMessagesSentThisCycle = 0;
|
tcpMessagesSentThisCycle = 0;
|
||||||
|
|
||||||
await OnMultiFrameCycle();
|
pollCycleTask = OnMultiFrameCycle();
|
||||||
|
await pollCycleTask;
|
||||||
|
|
||||||
return tcpMessagesSentThisCycle;
|
return tcpMessagesSentThisCycle;
|
||||||
|
|
||||||
@@ -104,9 +96,10 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
tcpMessagesSentThisCycle = 0;
|
tcpMessagesSentThisCycle = 0;
|
||||||
|
|
||||||
await OnMultiFrameCycle();
|
pollCycleTask = OnMultiFrameCycle();
|
||||||
|
await pollCycleTask;
|
||||||
|
|
||||||
if(!IsConnected) {
|
if (!IsConnected) {
|
||||||
pollerTaskStopped = true;
|
pollerTaskStopped = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,9 @@ namespace MewtocolNet {
|
|||||||
/// Gets generic information about the PLC
|
/// Gets generic information about the PLC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A PLCInfo class</returns>
|
/// <returns>A PLCInfo class</returns>
|
||||||
public async Task<PLCInfo> GetPLCInfoAsync() {
|
public async Task<PLCInfo> GetPLCInfoAsync(int timeout = -1) {
|
||||||
var resu = await SendCommandAsync("%01#RT");
|
|
||||||
|
var resu = await SendCommandAsync("%01#RT", true, timeout);
|
||||||
if (!resu.Success) return null;
|
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);
|
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);
|
||||||
|
|||||||
260
MewtocolNet/MewtocolInterfaceSerial.cs
Normal file
260
MewtocolNet/MewtocolInterfaceSerial.cs
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
using MewtocolNet.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
public class MewtocolInterfaceSerial : MewtocolInterface, IPlcSerial {
|
||||||
|
|
||||||
|
private bool autoSerial;
|
||||||
|
|
||||||
|
//serial config
|
||||||
|
public string PortName { get; private set; }
|
||||||
|
public int SerialBaudRate { get; private set; }
|
||||||
|
public int SerialDataBits { get; private set; }
|
||||||
|
public Parity SerialParity { get; private set; }
|
||||||
|
public StopBits SerialStopBits { get; private set; }
|
||||||
|
|
||||||
|
//Serial
|
||||||
|
internal SerialPort serialClient;
|
||||||
|
|
||||||
|
internal MewtocolInterfaceSerial () : base() { }
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IPlcSerial WithPoller () {
|
||||||
|
|
||||||
|
usePoller = true;
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string GetConnectionInfo() {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.Append($"{PortName}, ");
|
||||||
|
sb.Append($"{SerialBaudRate}, ");
|
||||||
|
sb.Append($"{SerialDataBits} ");
|
||||||
|
|
||||||
|
sb.Append($"{SerialParity.ToString().Substring(0, 1)} ");
|
||||||
|
|
||||||
|
switch (SerialStopBits) {
|
||||||
|
case StopBits.None:
|
||||||
|
sb.Append("0");
|
||||||
|
break;
|
||||||
|
case StopBits.One:
|
||||||
|
sb.Append("1");
|
||||||
|
break;
|
||||||
|
case StopBits.Two:
|
||||||
|
sb.Append("2");
|
||||||
|
break;
|
||||||
|
case StopBits.OnePointFive:
|
||||||
|
sb.Append("1.5");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void ConfigureConnection (string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 1) {
|
||||||
|
|
||||||
|
PortName = _portName;
|
||||||
|
SerialBaudRate = _baudRate;
|
||||||
|
SerialDataBits = _dataBits;
|
||||||
|
SerialParity = _parity;
|
||||||
|
SerialStopBits = _stopBits;
|
||||||
|
stationNumber = _station;
|
||||||
|
|
||||||
|
OnSerialPropsChanged();
|
||||||
|
Disconnect();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConfigureConnectionAuto () {
|
||||||
|
|
||||||
|
autoSerial = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override async Task ConnectAsync () {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
PLCInfo gotInfo = null;
|
||||||
|
|
||||||
|
if(autoSerial) {
|
||||||
|
|
||||||
|
gotInfo = await TryConnectAsyncMulti();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
gotInfo = await TryConnectAsyncSingle(PortName, SerialBaudRate, SerialDataBits, SerialParity, SerialStopBits);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(gotInfo != null) {
|
||||||
|
|
||||||
|
OnConnected(gotInfo);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Logger.Log("Initial connection failed", LogLevel.Info, this);
|
||||||
|
OnMajorSocketExceptionWhileConnecting();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
} catch (SocketException) {
|
||||||
|
|
||||||
|
OnMajorSocketExceptionWhileConnecting();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<PLCInfo> TryConnectAsyncMulti () {
|
||||||
|
|
||||||
|
var baudRates = Enum.GetValues(typeof(BaudRate)).Cast<BaudRate>();
|
||||||
|
|
||||||
|
//ordered by most commonly used
|
||||||
|
baudRates = new List<BaudRate> {
|
||||||
|
//most common 3
|
||||||
|
BaudRate._19200,
|
||||||
|
BaudRate._115200,
|
||||||
|
BaudRate._9600,
|
||||||
|
//others
|
||||||
|
BaudRate._1200,
|
||||||
|
BaudRate._2400,
|
||||||
|
BaudRate._4800,
|
||||||
|
BaudRate._38400,
|
||||||
|
BaudRate._57600,
|
||||||
|
BaudRate._230400,
|
||||||
|
};
|
||||||
|
|
||||||
|
var dataBits = Enum.GetValues(typeof(DataBits)).Cast<DataBits>();
|
||||||
|
var parities = new List<Parity>() { Parity.None, Parity.Odd, Parity.Even, Parity.Mark };
|
||||||
|
var stopBits = new List<StopBits> { StopBits.One, StopBits.Two };
|
||||||
|
|
||||||
|
foreach (var baud in baudRates) {
|
||||||
|
|
||||||
|
foreach (var databit in dataBits) {
|
||||||
|
|
||||||
|
foreach (var parity in parities) {
|
||||||
|
|
||||||
|
foreach (var stopBit in stopBits) {
|
||||||
|
|
||||||
|
var res = await TryConnectAsyncSingle(PortName, (int)baud, (int)databit, parity, stopBit);
|
||||||
|
if(res != null) return res;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<PLCInfo> TryConnectAsyncSingle (string port, int baud, int dbits, Parity par, StopBits sbits) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
serialClient = new SerialPort() {
|
||||||
|
PortName = port,
|
||||||
|
BaudRate = baud,
|
||||||
|
DataBits = dbits,
|
||||||
|
Parity = par,
|
||||||
|
StopBits = sbits,
|
||||||
|
ReadTimeout = 100,
|
||||||
|
Handshake = Handshake.None
|
||||||
|
};
|
||||||
|
|
||||||
|
PortName = port;
|
||||||
|
SerialBaudRate = baud;
|
||||||
|
SerialDataBits = dbits;
|
||||||
|
SerialParity = par;
|
||||||
|
SerialStopBits = sbits;
|
||||||
|
OnSerialPropsChanged();
|
||||||
|
|
||||||
|
serialClient.Open();
|
||||||
|
|
||||||
|
if (!serialClient.IsOpen) {
|
||||||
|
|
||||||
|
Logger.Log($"Failed to open [SERIAL]: {GetConnectionInfo()}", LogLevel.Verbose, this);
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
stream = serialClient.BaseStream;
|
||||||
|
|
||||||
|
Logger.Log($"Opened [SERIAL]: {GetConnectionInfo()}", LogLevel.Verbose, this);
|
||||||
|
|
||||||
|
var plcinf = await GetPLCInfoAsync(100);
|
||||||
|
|
||||||
|
if (plcinf == null) CloseClient();
|
||||||
|
|
||||||
|
return plcinf;
|
||||||
|
|
||||||
|
} catch (UnauthorizedAccessException) {
|
||||||
|
|
||||||
|
Logger.Log($"The port {serialClient.PortName} is currently in use. Close all accessing applications first", LogLevel.Error, this);
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseClient () {
|
||||||
|
|
||||||
|
if(serialClient.IsOpen) {
|
||||||
|
|
||||||
|
serialClient.Close();
|
||||||
|
Logger.Log($"Closed [SERIAL]", LogLevel.Verbose, this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private protected override void OnDisconnect() {
|
||||||
|
|
||||||
|
if (IsConnected) {
|
||||||
|
|
||||||
|
base.OnDisconnect();
|
||||||
|
|
||||||
|
CloseClient();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSerialPropsChanged () {
|
||||||
|
|
||||||
|
OnPropChange(nameof(PortName));
|
||||||
|
OnPropChange(nameof(SerialBaudRate));
|
||||||
|
OnPropChange(nameof(SerialDataBits));
|
||||||
|
OnPropChange(nameof(SerialParity));
|
||||||
|
OnPropChange(nameof(SerialStopBits));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
164
MewtocolNet/MewtocolInterfaceTcp.cs
Normal file
164
MewtocolNet/MewtocolInterfaceTcp.cs
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
using MewtocolNet.Exceptions;
|
||||||
|
using MewtocolNet.Logging;
|
||||||
|
using MewtocolNet.Queue;
|
||||||
|
using MewtocolNet.RegisterAttributes;
|
||||||
|
using MewtocolNet.Registers;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.Design;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The PLC com interface class
|
||||||
|
/// </summary>
|
||||||
|
public class MewtocolInterfaceTcp : MewtocolInterface, IPlcEthernet {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The host ip endpoint, leave it null to use an automatic interface
|
||||||
|
/// </summary>
|
||||||
|
public IPEndPoint HostEndpoint { get; set; }
|
||||||
|
|
||||||
|
//TCP
|
||||||
|
internal TcpClient client;
|
||||||
|
|
||||||
|
//tcp/ip config
|
||||||
|
private string ip;
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string IpAddress => ip;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Port => port;
|
||||||
|
|
||||||
|
internal MewtocolInterfaceTcp () : base() { }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IPlcEthernet WithPoller () {
|
||||||
|
|
||||||
|
usePoller = true;
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#region TCP connection state handling
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void ConfigureConnection (string _ip, int _port = 9094, int _station = 1) {
|
||||||
|
|
||||||
|
ip = _ip;
|
||||||
|
port = _port;
|
||||||
|
stationNumber = _station;
|
||||||
|
|
||||||
|
Disconnect();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override async Task ConnectAsync () {
|
||||||
|
|
||||||
|
if (!IPAddress.TryParse(ip, out var targetIP)) {
|
||||||
|
throw new ArgumentException("The IP adress of the PLC was no valid format");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (HostEndpoint != null) {
|
||||||
|
|
||||||
|
client = new TcpClient(HostEndpoint) {
|
||||||
|
ReceiveBufferSize = RecBufferSize,
|
||||||
|
NoDelay = false,
|
||||||
|
};
|
||||||
|
var ep = (IPEndPoint)client.Client.LocalEndPoint;
|
||||||
|
Logger.Log($"Connecting [MAN] endpoint: {ep.Address}:{ep.Port}", LogLevel.Verbose, this);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
client = new TcpClient() {
|
||||||
|
ReceiveBufferSize = RecBufferSize,
|
||||||
|
NoDelay = false,
|
||||||
|
ExclusiveAddressUse = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = client.BeginConnect(targetIP, port, null, null);
|
||||||
|
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeout));
|
||||||
|
|
||||||
|
if (!success || !client.Connected) {
|
||||||
|
OnMajorSocketExceptionWhileConnecting();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HostEndpoint == null) {
|
||||||
|
var ep = (IPEndPoint)client.Client.LocalEndPoint;
|
||||||
|
Logger.Log($"Connecting [AUTO] endpoint: {ep.Address.MapToIPv4()}:{ep.Port}", LogLevel.Verbose, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//get the stream
|
||||||
|
stream = client.GetStream();
|
||||||
|
stream.ReadTimeout = 1000;
|
||||||
|
|
||||||
|
//get plc info
|
||||||
|
var plcinf = await GetPLCInfoAsync();
|
||||||
|
|
||||||
|
if (plcinf != null) {
|
||||||
|
|
||||||
|
OnConnected(plcinf);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Logger.Log("Initial connection failed", LogLevel.Info, this);
|
||||||
|
OnDisconnect();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
} catch (SocketException) {
|
||||||
|
|
||||||
|
OnMajorSocketExceptionWhileConnecting();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the connection info string
|
||||||
|
/// </summary>
|
||||||
|
public override string GetConnectionInfo() {
|
||||||
|
|
||||||
|
return $"{IpAddress}:{Port}";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private protected override void OnDisconnect() {
|
||||||
|
|
||||||
|
if (IsConnected) {
|
||||||
|
|
||||||
|
base.OnDisconnect();
|
||||||
|
|
||||||
|
client.Close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,4 +23,7 @@
|
|||||||
<_Parameter1>MewtocolTests</_Parameter1>
|
<_Parameter1>MewtocolTests</_Parameter1>
|
||||||
</AssemblyAttribute>
|
</AssemblyAttribute>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace MewtocolNet {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// All modes
|
/// All modes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PLCMode {
|
public struct PLCMode {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// PLC is running
|
/// PLC is running
|
||||||
|
|||||||
21
MewtocolNet/PublicEnums/BaudRate.cs
Normal file
21
MewtocolNet/PublicEnums/BaudRate.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
public enum BaudRate {
|
||||||
|
|
||||||
|
_1200 = 1200,
|
||||||
|
_2400 = 2400,
|
||||||
|
_4800 = 4800,
|
||||||
|
_9600 = 9600,
|
||||||
|
_19200 = 19200,
|
||||||
|
_38400 = 38400,
|
||||||
|
_57600 = 57600,
|
||||||
|
_115200 = 115200,
|
||||||
|
_230400 = 230400,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
namespace MewtocolNet.RegisterAttributes {
|
namespace MewtocolNet {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The size of the bitwise register
|
/// The size of the bitwise register
|
||||||
/// </summary>
|
/// </summary>
|
||||||
9
MewtocolNet/PublicEnums/DataBits.cs
Normal file
9
MewtocolNet/PublicEnums/DataBits.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace MewtocolNet {
|
||||||
|
public enum DataBits {
|
||||||
|
|
||||||
|
Seven = 7,
|
||||||
|
Eight = 8,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
26
MewtocolNet/PublicEnums/IOType.cs
Normal file
26
MewtocolNet/PublicEnums/IOType.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
// this is just used as syntactic sugar,
|
||||||
|
// when creating registers that are R/X/Y typed you dont need the DT types
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of an input/output register
|
||||||
|
/// </summary>
|
||||||
|
public enum IOType {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Physical input as a bool (Relay)
|
||||||
|
/// </summary>
|
||||||
|
X = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Physical output as a bool (Relay)
|
||||||
|
/// </summary>
|
||||||
|
Y = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Internal relay
|
||||||
|
/// </summary>
|
||||||
|
R = 2,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -34,27 +34,4 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is just used as syntactic sugar,
|
|
||||||
// when creating registers that are R/X/Y typed you dont need the DT types
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The type of an input/output register
|
|
||||||
/// </summary>
|
|
||||||
public enum IOType {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Physical input as a bool (Relay)
|
|
||||||
/// </summary>
|
|
||||||
X = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// Physical output as a bool (Relay)
|
|
||||||
/// </summary>
|
|
||||||
Y = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// Internal relay
|
|
||||||
/// </summary>
|
|
||||||
R = 2,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -21,10 +21,18 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static RegBuilder ForInterface (MewtocolInterface interf) {
|
public static RegBuilder ForInterface (IPlcEthernet interf) {
|
||||||
|
|
||||||
var rb = new RegBuilder();
|
var rb = new RegBuilder();
|
||||||
rb.forInterface = interf;
|
rb.forInterface = interf as MewtocolInterface;
|
||||||
|
return rb;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegBuilder ForInterface(IPlcSerial interf) {
|
||||||
|
|
||||||
|
var rb = new RegBuilder();
|
||||||
|
rb.forInterface = interf as MewtocolInterface;
|
||||||
return rb;
|
return rb;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace MewtocolNet {
|
namespace MewtocolNet.RegisterBuilding {
|
||||||
|
|
||||||
internal struct RegisterBuildInfo {
|
internal struct RegisterBuildInfo {
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
namespace MewtocolNet {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The formatted result of a ascii command
|
|
||||||
/// </summary>
|
|
||||||
public struct CommandResult {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Success state of the message
|
|
||||||
/// </summary>
|
|
||||||
public bool Success { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Response text of the message
|
|
||||||
/// </summary>
|
|
||||||
public string Response { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Error code of the message
|
|
||||||
/// </summary>
|
|
||||||
public string Error { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Error text of the message
|
|
||||||
/// </summary>
|
|
||||||
public string ErrorDescription { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
namespace MewtocolNet {
|
|
||||||
internal enum TCPMessageResult {
|
|
||||||
|
|
||||||
Waiting,
|
|
||||||
Success,
|
|
||||||
NotConnected,
|
|
||||||
FailedWithException,
|
|
||||||
FailedLineFeed,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum CommandState {
|
|
||||||
|
|
||||||
Intial,
|
|
||||||
LineFeed,
|
|
||||||
RequestedNextFrame,
|
|
||||||
Complete
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -109,7 +109,7 @@ namespace MewtocolTests {
|
|||||||
[Fact(DisplayName = "Boolean R generation")]
|
[Fact(DisplayName = "Boolean R generation")]
|
||||||
public void BooleanGen() {
|
public void BooleanGen() {
|
||||||
|
|
||||||
var interf = new MewtocolInterface("192.168.0.1");
|
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||||
|
|
||||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1));
|
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1));
|
||||||
@@ -122,7 +122,7 @@ namespace MewtocolTests {
|
|||||||
[Fact(DisplayName = "Boolean input XD generation")]
|
[Fact(DisplayName = "Boolean input XD generation")]
|
||||||
public void BooleanInputGen() {
|
public void BooleanInputGen() {
|
||||||
|
|
||||||
var interf = new MewtocolInterface("192.168.0.1");
|
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||||
|
|
||||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBoolInputXD));
|
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBoolInputXD));
|
||||||
@@ -135,7 +135,7 @@ namespace MewtocolTests {
|
|||||||
[Fact(DisplayName = "Int16 generation")]
|
[Fact(DisplayName = "Int16 generation")]
|
||||||
public void Int16Gen() {
|
public void Int16Gen() {
|
||||||
|
|
||||||
var interf = new MewtocolInterface("192.168.0.1");
|
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||||
|
|
||||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt16));
|
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt16));
|
||||||
@@ -148,7 +148,7 @@ namespace MewtocolTests {
|
|||||||
[Fact(DisplayName = "UInt16 generation")]
|
[Fact(DisplayName = "UInt16 generation")]
|
||||||
public void UInt16Gen() {
|
public void UInt16Gen() {
|
||||||
|
|
||||||
var interf = new MewtocolInterface("192.168.0.1");
|
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||||
|
|
||||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt16));
|
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt16));
|
||||||
@@ -161,7 +161,7 @@ namespace MewtocolTests {
|
|||||||
[Fact(DisplayName = "Int32 generation")]
|
[Fact(DisplayName = "Int32 generation")]
|
||||||
public void Int32Gen() {
|
public void Int32Gen() {
|
||||||
|
|
||||||
var interf = new MewtocolInterface("192.168.0.1");
|
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||||
|
|
||||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt32));
|
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt32));
|
||||||
@@ -174,7 +174,7 @@ namespace MewtocolTests {
|
|||||||
[Fact(DisplayName = "UInt32 generation")]
|
[Fact(DisplayName = "UInt32 generation")]
|
||||||
public void UInt32Gen() {
|
public void UInt32Gen() {
|
||||||
|
|
||||||
var interf = new MewtocolInterface("192.168.0.1");
|
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||||
|
|
||||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt32));
|
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt32));
|
||||||
@@ -187,7 +187,7 @@ namespace MewtocolTests {
|
|||||||
[Fact(DisplayName = "Float32 generation")]
|
[Fact(DisplayName = "Float32 generation")]
|
||||||
public void Float32Gen() {
|
public void Float32Gen() {
|
||||||
|
|
||||||
var interf = new MewtocolInterface("192.168.0.1");
|
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||||
|
|
||||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestFloat32));
|
var register = interf.GetRegister(nameof(TestRegisterCollection.TestFloat32));
|
||||||
@@ -200,7 +200,7 @@ namespace MewtocolTests {
|
|||||||
[Fact(DisplayName = "TimeSpan generation")]
|
[Fact(DisplayName = "TimeSpan generation")]
|
||||||
public void TimespanGen() {
|
public void TimespanGen() {
|
||||||
|
|
||||||
var interf = new MewtocolInterface("192.168.0.1");
|
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||||
|
|
||||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime));
|
var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime));
|
||||||
|
|||||||
@@ -73,9 +73,9 @@ namespace MewtocolTests
|
|||||||
|
|
||||||
output.WriteLine($"Testing: {plc.PLCName}");
|
output.WriteLine($"Testing: {plc.PLCName}");
|
||||||
|
|
||||||
var cycleClient = new MewtocolInterface(plc.PLCIP, plc.PLCPort);
|
var cycleClient = new MewtocolInterfaceShared(plc.PLCIP, plc.PLCPort);
|
||||||
|
|
||||||
await cycleClient.ConnectAsync();
|
await cycleClient.ConnectAsyncOld();
|
||||||
|
|
||||||
Assert.True(cycleClient.IsConnected);
|
Assert.True(cycleClient.IsConnected);
|
||||||
|
|
||||||
@@ -94,9 +94,9 @@ namespace MewtocolTests
|
|||||||
|
|
||||||
output.WriteLine($"Testing: {plc.PLCName}\n");
|
output.WriteLine($"Testing: {plc.PLCName}\n");
|
||||||
|
|
||||||
var client = new MewtocolInterface(plc.PLCIP, plc.PLCPort);
|
var client = new MewtocolInterfaceShared(plc.PLCIP, plc.PLCPort);
|
||||||
|
|
||||||
await client.ConnectAsync();
|
await client.ConnectAsyncOld();
|
||||||
|
|
||||||
output.WriteLine($"{client.PlcInfo}\n");
|
output.WriteLine($"{client.PlcInfo}\n");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user