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 System.Linq;
|
||||
using System.Net;
|
||||
using System.IO.Ports;
|
||||
|
||||
namespace Examples;
|
||||
|
||||
@@ -21,7 +22,7 @@ public class ExampleScenarios {
|
||||
public void SetupLogger () {
|
||||
|
||||
//attaching the logger
|
||||
Logger.LogLevel = LogLevel.Error;
|
||||
Logger.LogLevel = LogLevel.Verbose;
|
||||
Logger.OnNewLogMessage((date, level, msg) => {
|
||||
|
||||
if (level == LogLevel.Error) Console.ForegroundColor = ConsoleColor.Red;
|
||||
@@ -38,7 +39,7 @@ public class ExampleScenarios {
|
||||
public async Task RunDisposalAndDisconnectAsync () {
|
||||
|
||||
//automatic disposal
|
||||
using (var interf = new MewtocolInterface("192.168.115.210")) {
|
||||
using (var interf = Mewtocol.Ethernet("192.168.115.210")) {
|
||||
|
||||
await interf.ConnectAsync();
|
||||
|
||||
@@ -55,7 +56,7 @@ public class ExampleScenarios {
|
||||
Console.WriteLine("Disposed, closed connection");
|
||||
|
||||
//manual close
|
||||
var interf2 = new MewtocolInterface("192.168.115.210");
|
||||
var interf2 = Mewtocol.Ethernet("192.168.115.210");
|
||||
|
||||
await interf2.ConnectAsync();
|
||||
|
||||
@@ -77,7 +78,7 @@ public class ExampleScenarios {
|
||||
public async Task RunReadTest () {
|
||||
|
||||
//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
|
||||
var builder = RegBuilder.ForInterface(interf);
|
||||
@@ -147,20 +148,18 @@ public class ExampleScenarios {
|
||||
|
||||
}
|
||||
|
||||
[Scenario("Test read speed 100 R registers")]
|
||||
public async Task ReadRSpeedTest() {
|
||||
[Scenario("Test read speed TCP (n) R registers")]
|
||||
public async Task ReadRSpeedTest (string registerCount) {
|
||||
|
||||
var preLogLevel = Logger.LogLevel;
|
||||
Logger.LogLevel = LogLevel.Critical;
|
||||
|
||||
//setting up a new PLC interface and register collection
|
||||
MewtocolInterface interf = new MewtocolInterface("192.168.115.210") {
|
||||
ConnectTimeout = 3000,
|
||||
};
|
||||
using var interf = Mewtocol.Ethernet("192.168.115.210");
|
||||
|
||||
//auto add all built registers to the interface
|
||||
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();
|
||||
|
||||
@@ -169,6 +168,11 @@ public class ExampleScenarios {
|
||||
//connect
|
||||
await interf.ConnectAsync();
|
||||
|
||||
if(!interf.IsConnected) {
|
||||
Console.WriteLine("Aborted, connection failed");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Poller cycle started");
|
||||
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");
|
||||
|
||||
interf.Disconnect();
|
||||
|
||||
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")]
|
||||
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");
|
||||
|
||||
if (found == null) return;
|
||||
|
||||
found.IPAddress = IPAddress.Parse($"192.168.1.{new Random().Next(20, 120)}");
|
||||
found.Name = $"Rand{new Random().Next(5, 15)}";
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ class Program {
|
||||
|
||||
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);
|
||||
|
||||
j++;
|
||||
@@ -78,6 +79,8 @@ class Program {
|
||||
var line = Console.ReadLine();
|
||||
|
||||
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)) {
|
||||
|
||||
@@ -93,14 +96,27 @@ class Program {
|
||||
|
||||
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 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();
|
||||
|
||||
} catch (TargetParameterCountException) {
|
||||
|
||||
Console.WriteLine("Missing parameters");
|
||||
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("The program ran to completition");
|
||||
Console.ResetColor();
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// Contains information about the plc and its cpu
|
||||
/// </summary>
|
||||
public partial class CpuInfo {
|
||||
public struct CpuInfo {
|
||||
|
||||
/// <summary>
|
||||
/// The cpu type of the plc
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace MewtocolNet {
|
||||
}
|
||||
}
|
||||
|
||||
if(task.IsCanceled) return default(T);
|
||||
|
||||
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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace MewtocolNet.Queue {
|
||||
|
||||
internal class SerialQueue {
|
||||
internal class AsyncQueue {
|
||||
|
||||
readonly object _locker = new object();
|
||||
readonly WeakReference<Task> _lastTask = new WeakReference<Task>(null);
|
||||
|
||||
internal Task<T> Enqueue<T>(Func<Task<T>> asyncFunction) {
|
||||
lock (_locker) {
|
||||
|
||||
Task lastTask;
|
||||
Task<T> resultTask;
|
||||
|
||||
@@ -22,6 +25,7 @@ namespace MewtocolNet.Queue {
|
||||
_lastTask.SetTarget(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) {
|
||||
LogInvoked?.Invoke(DateTime.Now, loglevel, message);
|
||||
} 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.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.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -20,177 +14,119 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
/// <summary>
|
||||
/// The PLC com interface class
|
||||
/// </summary>
|
||||
public partial class MewtocolInterface : INotifyPropertyChanged, IDisposable {
|
||||
public partial class MewtocolInterface : IPlc, INotifyPropertyChanged, IDisposable {
|
||||
|
||||
/// <summary>
|
||||
/// Gets triggered when the PLC connection was established
|
||||
/// </summary>
|
||||
#region Private fields
|
||||
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets triggered when the PLC connection was closed or lost
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public event Action Disconnected;
|
||||
|
||||
/// <summary>
|
||||
/// Gets triggered when a registered data register changes its value
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public event Action<IRegister> RegisterChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets triggered when a property of the interface changes
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private int connectTimeout = 3000;
|
||||
/// <summary>
|
||||
/// The initial connection timeout in milliseconds
|
||||
/// </summary>
|
||||
public int ConnectTimeout {
|
||||
get { return connectTimeout; }
|
||||
set { connectTimeout = value; }
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
private volatile int queuedMessages;
|
||||
/// <summary>
|
||||
/// Currently queued Messages
|
||||
/// </summary>
|
||||
public int QueuedMessages {
|
||||
get => queuedMessages;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public int QueuedMessages => queuedMessages;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <inheritdoc/>
|
||||
public bool IsConnected {
|
||||
get => isConnected;
|
||||
private set {
|
||||
private protected set {
|
||||
isConnected = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsConnected)));
|
||||
OnPropChange();
|
||||
}
|
||||
}
|
||||
|
||||
private bool disposed;
|
||||
/// <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>
|
||||
/// <inheritdoc/>
|
||||
public PLCInfo PlcInfo {
|
||||
get => plcInfo;
|
||||
private set {
|
||||
plcInfo = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PlcInfo)));
|
||||
OnPropChange();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The registered data registers of the PLC
|
||||
/// </summary>
|
||||
internal List<BaseRegister> RegistersUnderlying { get; private set; } = new List<BaseRegister>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IRegister> Registers => RegistersUnderlying.Cast<IRegister>();
|
||||
|
||||
internal IEnumerable<IRegisterInternal> RegistersInternal => RegistersUnderlying.Cast<IRegisterInternal>();
|
||||
|
||||
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>
|
||||
/// <inheritdoc/>
|
||||
public int StationNumber => stationNumber;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <inheritdoc/>
|
||||
public int BytesPerSecondUpstream {
|
||||
get { return bytesPerSecondUpstream; }
|
||||
private set {
|
||||
private protected set {
|
||||
bytesPerSecondUpstream = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BytesPerSecondUpstream)));
|
||||
OnPropChange();
|
||||
}
|
||||
}
|
||||
|
||||
private int bytesPerSecondDownstream = 0;
|
||||
/// <summary>
|
||||
/// The current transmission speed in bytes per second
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public int BytesPerSecondDownstream {
|
||||
get { return bytesPerSecondDownstream; }
|
||||
private set {
|
||||
private protected set {
|
||||
bytesPerSecondDownstream = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BytesPerSecondDownstream)));
|
||||
OnPropChange();
|
||||
}
|
||||
}
|
||||
|
||||
internal NetworkStream stream;
|
||||
internal TcpClient client;
|
||||
internal readonly SerialQueue queue = new SerialQueue();
|
||||
private int RecBufferSize = 128;
|
||||
internal int SendExceptionsInRow = 0;
|
||||
internal bool ImportantTaskRunning = false;
|
||||
#endregion
|
||||
|
||||
private Stopwatch speedStopwatchUpstr;
|
||||
private Stopwatch speedStopwatchDownstr;
|
||||
#region Public read/write Properties / Fields
|
||||
|
||||
private Task firstPollTask = new Task(() => { });
|
||||
/// <inheritdoc/>
|
||||
public int ConnectTimeout { get; set; } = 3000;
|
||||
|
||||
#region Initialization
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 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) {
|
||||
#region Methods
|
||||
|
||||
ip = _ip;
|
||||
port = _port;
|
||||
stationNumber = _station;
|
||||
private protected MewtocolInterface () {
|
||||
|
||||
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>
|
||||
/// 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>
|
||||
/// <inheritdoc/>
|
||||
public void Disconnect() {
|
||||
|
||||
if (!IsConnected)
|
||||
return;
|
||||
if (!IsConnected) return;
|
||||
|
||||
pollCycleTask.Wait();
|
||||
|
||||
OnMajorSocketExceptionWhileConnected();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a poller to the interface that continously
|
||||
/// polls the registered data registers and writes the values to them
|
||||
/// </summary>
|
||||
public MewtocolInterface WithPoller() {
|
||||
/// <inheritdoc/>
|
||||
public void Dispose() {
|
||||
|
||||
usePoller = true;
|
||||
return this;
|
||||
if (Disposed) return;
|
||||
Disconnect();
|
||||
//GC.SuppressFinalize(this);
|
||||
Disposed = true;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <inheritdoc/>
|
||||
public virtual string GetConnectionInfo() => throw new NotImplementedException();
|
||||
|
||||
#region TCP connection state handling
|
||||
|
||||
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) {
|
||||
/// <inheritdoc/>
|
||||
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1) {
|
||||
|
||||
//send request
|
||||
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++;
|
||||
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 {
|
||||
|
||||
//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)
|
||||
frame = $"{frame.BuildBCCFrame()}";
|
||||
|
||||
@@ -428,82 +214,32 @@ namespace MewtocolNet {
|
||||
|
||||
//write inital command
|
||||
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
||||
await stream.WriteAsync(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);
|
||||
|
||||
stream.Write(writeBuffer, 0, writeBuffer.Length);
|
||||
|
||||
Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this);
|
||||
Logger.Log($"--> OUT MSG: {frame.Replace("\r", "(CR)")}", LogLevel.Critical, this);
|
||||
|
||||
//read
|
||||
List<byte> totalResponse = new List<byte>();
|
||||
byte[] responseBuffer = new byte[512];
|
||||
var readResult = await ReadCommandAsync();
|
||||
|
||||
bool wasMultiFramedResponse = false;
|
||||
CommandState cmdState = CommandState.Intial;
|
||||
//did not receive bytes but no errors, the com port was not configured right
|
||||
if (readResult.Item1.Length == 0) {
|
||||
|
||||
//read until command complete
|
||||
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);
|
||||
|
||||
}
|
||||
return new MewtocolFrameResponse(402, "Receive buffer was empty");
|
||||
|
||||
}
|
||||
|
||||
//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('&');
|
||||
|
||||
@@ -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($"Total bytes parsed: {resString.Length}", 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
|
||||
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
|
||||
@@ -557,11 +352,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Disposing
|
||||
|
||||
private void OnMajorSocketExceptionWhileConnecting() {
|
||||
private protected void OnMajorSocketExceptionWhileConnecting() {
|
||||
|
||||
if (IsConnected) {
|
||||
|
||||
@@ -572,7 +363,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
private void OnMajorSocketExceptionWhileConnected() {
|
||||
private protected void OnMajorSocketExceptionWhileConnected() {
|
||||
|
||||
if (IsConnected) {
|
||||
|
||||
@@ -583,25 +374,30 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
private protected virtual void OnConnected (PLCInfo plcinf) {
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the current interface and clears all its members
|
||||
/// </summary>
|
||||
public void Dispose() {
|
||||
Logger.Log("Connected", LogLevel.Info, this);
|
||||
Logger.Log($"\n\n{plcinf.ToString()}\n\n", LogLevel.Verbose, this);
|
||||
|
||||
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;
|
||||
BytesPerSecondUpstream = 0;
|
||||
@@ -612,14 +408,10 @@ namespace MewtocolNet {
|
||||
|
||||
Disconnected?.Invoke();
|
||||
KillPoller();
|
||||
client.Close();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void ClearRegisterVals() {
|
||||
private protected void ClearRegisterVals() {
|
||||
|
||||
for (int i = 0; i < RegistersUnderlying.Count; i++) {
|
||||
|
||||
@@ -630,28 +422,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#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) {
|
||||
private protected void OnPropChange([CallerMemberName] string propertyName = null) {
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
@@ -12,24 +13,14 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet
|
||||
{
|
||||
namespace MewtocolNet {
|
||||
|
||||
/// <summary>
|
||||
/// The PLC com interface class
|
||||
/// </summary>
|
||||
public partial class MewtocolInterface {
|
||||
|
||||
internal event Action PolledCycle;
|
||||
|
||||
internal volatile bool pollerTaskStopped = true;
|
||||
internal volatile bool pollerFirstCycle;
|
||||
|
||||
internal bool usePoller = false;
|
||||
|
||||
private int tcpMessagesSentThisCycle = 0;
|
||||
|
||||
private int pollerCycleDurationMs;
|
||||
internal Task pollCycleTask;
|
||||
|
||||
/// <summary>
|
||||
/// True if the poller is actvice (can be paused)
|
||||
@@ -87,7 +78,8 @@ namespace MewtocolNet
|
||||
|
||||
tcpMessagesSentThisCycle = 0;
|
||||
|
||||
await OnMultiFrameCycle();
|
||||
pollCycleTask = OnMultiFrameCycle();
|
||||
await pollCycleTask;
|
||||
|
||||
return tcpMessagesSentThisCycle;
|
||||
|
||||
@@ -104,9 +96,10 @@ namespace MewtocolNet
|
||||
|
||||
tcpMessagesSentThisCycle = 0;
|
||||
|
||||
await OnMultiFrameCycle();
|
||||
pollCycleTask = OnMultiFrameCycle();
|
||||
await pollCycleTask;
|
||||
|
||||
if(!IsConnected) {
|
||||
if (!IsConnected) {
|
||||
pollerTaskStopped = true;
|
||||
return;
|
||||
}
|
||||
@@ -19,8 +19,9 @@ namespace MewtocolNet {
|
||||
/// Gets generic information about the PLC
|
||||
/// </summary>
|
||||
/// <returns>A PLCInfo class</returns>
|
||||
public async Task<PLCInfo> GetPLCInfoAsync() {
|
||||
var resu = await SendCommandAsync("%01#RT");
|
||||
public async Task<PLCInfo> GetPLCInfoAsync(int timeout = -1) {
|
||||
|
||||
var resu = await SendCommandAsync("%01#RT", true, timeout);
|
||||
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);
|
||||
|
||||
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>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// All modes
|
||||
/// </summary>
|
||||
public class PLCMode {
|
||||
public struct PLCMode {
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// The size of the bitwise register
|
||||
/// </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();
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MewtocolNet {
|
||||
namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
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")]
|
||||
public void BooleanGen() {
|
||||
|
||||
var interf = new MewtocolInterface("192.168.0.1");
|
||||
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1));
|
||||
@@ -122,7 +122,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "Boolean input XD generation")]
|
||||
public void BooleanInputGen() {
|
||||
|
||||
var interf = new MewtocolInterface("192.168.0.1");
|
||||
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBoolInputXD));
|
||||
@@ -135,7 +135,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "Int16 generation")]
|
||||
public void Int16Gen() {
|
||||
|
||||
var interf = new MewtocolInterface("192.168.0.1");
|
||||
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt16));
|
||||
@@ -148,7 +148,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "UInt16 generation")]
|
||||
public void UInt16Gen() {
|
||||
|
||||
var interf = new MewtocolInterface("192.168.0.1");
|
||||
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt16));
|
||||
@@ -161,7 +161,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "Int32 generation")]
|
||||
public void Int32Gen() {
|
||||
|
||||
var interf = new MewtocolInterface("192.168.0.1");
|
||||
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt32));
|
||||
@@ -174,7 +174,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "UInt32 generation")]
|
||||
public void UInt32Gen() {
|
||||
|
||||
var interf = new MewtocolInterface("192.168.0.1");
|
||||
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt32));
|
||||
@@ -187,7 +187,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "Float32 generation")]
|
||||
public void Float32Gen() {
|
||||
|
||||
var interf = new MewtocolInterface("192.168.0.1");
|
||||
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestFloat32));
|
||||
@@ -200,7 +200,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "TimeSpan generation")]
|
||||
public void TimespanGen() {
|
||||
|
||||
var interf = new MewtocolInterface("192.168.0.1");
|
||||
var interf = new MewtocolInterfaceShared("192.168.0.1");
|
||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime));
|
||||
|
||||
@@ -73,9 +73,9 @@ namespace MewtocolTests
|
||||
|
||||
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);
|
||||
|
||||
@@ -94,9 +94,9 @@ namespace MewtocolTests
|
||||
|
||||
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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user