mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Added COM cassette features
This commit is contained in:
@@ -10,6 +10,9 @@ using MewtocolNet.Registers;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
using MewtocolNet.ComCassette;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
namespace Examples;
|
namespace Examples;
|
||||||
|
|
||||||
@@ -31,61 +34,6 @@ public class ExampleScenarios {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Scenario("Permament connection with poller")]
|
|
||||||
public async Task RunCyclicPollerAsync () {
|
|
||||||
|
|
||||||
Console.WriteLine("Starting poller scenario");
|
|
||||||
|
|
||||||
int runTime = 10000;
|
|
||||||
int remainingTime = runTime;
|
|
||||||
|
|
||||||
//setting up a new PLC interface and register collection
|
|
||||||
MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
|
|
||||||
TestRegisters registers = new TestRegisters();
|
|
||||||
|
|
||||||
//attaching the register collection and an automatic poller
|
|
||||||
interf.WithRegisterCollection(registers).WithPoller();
|
|
||||||
|
|
||||||
await interf.ConnectAsync();
|
|
||||||
await interf.AwaitFirstDataAsync();
|
|
||||||
|
|
||||||
_ = Task.Factory.StartNew(async () => {
|
|
||||||
|
|
||||||
while (interf.IsConnected) {
|
|
||||||
|
|
||||||
//flip the bool register each tick and wait for it to be registered
|
|
||||||
//await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1);
|
|
||||||
|
|
||||||
Console.Title =
|
|
||||||
$"Speed UP: {interf.BytesPerSecondUpstream} B/s, " +
|
|
||||||
$"Speed DOWN: {interf.BytesPerSecondDownstream} B/s, " +
|
|
||||||
$"Poll cycle: {interf.PollerCycleDurationMs} ms, " +
|
|
||||||
$"Queued MSGs: {interf.QueuedMessages}";
|
|
||||||
|
|
||||||
Console.Clear();
|
|
||||||
Console.WriteLine("Underlying registers on tick: \n");
|
|
||||||
|
|
||||||
foreach (var register in interf.Registers)
|
|
||||||
Console.WriteLine($"{register.ToString(true)}");
|
|
||||||
|
|
||||||
Console.WriteLine($"{registers.TestBool1}");
|
|
||||||
Console.WriteLine($"{registers.TestDuplicate}");
|
|
||||||
|
|
||||||
remainingTime -= 1000;
|
|
||||||
|
|
||||||
Console.WriteLine($"\nStopping in: {remainingTime}ms");
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.Delay(runTime);
|
|
||||||
interf.Disconnect();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Scenario("Dispose and disconnect connection")]
|
[Scenario("Dispose and disconnect connection")]
|
||||||
public async Task RunDisposalAndDisconnectAsync () {
|
public async Task RunDisposalAndDisconnectAsync () {
|
||||||
|
|
||||||
@@ -125,68 +73,7 @@ public class ExampleScenarios {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Scenario("Test auto enums and bitwise, needs the example program from MewtocolNet/PLC_Test")]
|
[Scenario("Read all kinds of example registers")]
|
||||||
public async Task RunEnumsBitwiseAsync () {
|
|
||||||
|
|
||||||
Console.WriteLine("Starting auto enums and bitwise");
|
|
||||||
|
|
||||||
//setting up a new PLC interface and register collection
|
|
||||||
MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
|
|
||||||
TestRegistersEnumBitwise registers = new TestRegistersEnumBitwise();
|
|
||||||
|
|
||||||
//attaching the register collection and an automatic poller
|
|
||||||
interf.WithRegisterCollection(registers).WithPoller();
|
|
||||||
|
|
||||||
registers.PropertyChanged += (s, e) => {
|
|
||||||
|
|
||||||
Console.Clear();
|
|
||||||
|
|
||||||
var props = registers.GetType().GetProperties();
|
|
||||||
|
|
||||||
foreach (var prop in props) {
|
|
||||||
|
|
||||||
var val = prop.GetValue(registers);
|
|
||||||
string printVal = val?.ToString() ?? "null";
|
|
||||||
|
|
||||||
if (val is BitArray bitarr) {
|
|
||||||
printVal = bitarr.ToBitString();
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.Write($"{prop.Name} - ");
|
|
||||||
|
|
||||||
if(printVal == "True") {
|
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.Write($"{printVal}");
|
|
||||||
|
|
||||||
Console.ResetColor();
|
|
||||||
|
|
||||||
Console.WriteLine();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
await interf.ConnectAsync();
|
|
||||||
|
|
||||||
//use the async method to make sure the cycling is stopped
|
|
||||||
//await interf.SetRegisterAsync(nameof(registers.StartCyclePLC), false);
|
|
||||||
|
|
||||||
await Task.Delay(5000);
|
|
||||||
|
|
||||||
//set the register without waiting for it async
|
|
||||||
registers.StartCyclePLC = true;
|
|
||||||
|
|
||||||
await Task.Delay(5000);
|
|
||||||
|
|
||||||
registers.StartCyclePLC = false;
|
|
||||||
|
|
||||||
await Task.Delay(2000);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Scenario("Read register test")]
|
|
||||||
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
|
||||||
@@ -260,8 +147,8 @@ public class ExampleScenarios {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Scenario("Test multi frame")]
|
[Scenario("Test read speed 100 R registers")]
|
||||||
public async Task MultiFrameTest() {
|
public async Task ReadRSpeedTest() {
|
||||||
|
|
||||||
var preLogLevel = Logger.LogLevel;
|
var preLogLevel = Logger.LogLevel;
|
||||||
Logger.LogLevel = LogLevel.Critical;
|
Logger.LogLevel = LogLevel.Critical;
|
||||||
@@ -273,11 +160,7 @@ public class ExampleScenarios {
|
|||||||
|
|
||||||
//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);
|
||||||
var r0reg = builder.FromPlcRegName("R0").Build();
|
for (int i = 0; i < 100; i++) {
|
||||||
builder.FromPlcRegName("R1").Build();
|
|
||||||
builder.FromPlcRegName("DT0").AsBytes(100).Build();
|
|
||||||
|
|
||||||
for (int i = 1; i < 100; i++) {
|
|
||||||
|
|
||||||
builder.FromPlcRegName($"R{i}A").Build();
|
builder.FromPlcRegName($"R{i}A").Build();
|
||||||
|
|
||||||
@@ -295,7 +178,7 @@ public class ExampleScenarios {
|
|||||||
|
|
||||||
Console.WriteLine("Poller cycle finished");
|
Console.WriteLine("Poller cycle finished");
|
||||||
|
|
||||||
Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms for {cmdCount} commands");
|
Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms for {cmdCount} commands and {interf.Registers.Count()} R registers");
|
||||||
|
|
||||||
interf.Disconnect();
|
interf.Disconnect();
|
||||||
|
|
||||||
@@ -303,4 +186,38 @@ public class ExampleScenarios {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Scenario("Find all COM5 cassettes in the network")]
|
||||||
|
public async Task FindCassettes () {
|
||||||
|
|
||||||
|
Console.Clear();
|
||||||
|
|
||||||
|
var casettes = await CassetteFinder.FindClientsAsync();
|
||||||
|
|
||||||
|
foreach (var cassette in casettes) {
|
||||||
|
|
||||||
|
Console.WriteLine($"{cassette.Name}");
|
||||||
|
Console.WriteLine($"IP: {cassette.IPAddress}");
|
||||||
|
Console.WriteLine($"Port: {cassette.Port}");
|
||||||
|
Console.WriteLine($"DHCP: {cassette.UsesDHCP}");
|
||||||
|
Console.WriteLine($"Subnet Mask: {cassette.SubnetMask}");
|
||||||
|
Console.WriteLine($"Gateway: {cassette.GatewayAddress}");
|
||||||
|
Console.WriteLine($"Mac: {cassette.MacAddress.ToHexString(":")}");
|
||||||
|
Console.WriteLine($"Firmware: {cassette.FirmwareVersion}");
|
||||||
|
Console.WriteLine($"Status: {cassette.Status}");
|
||||||
|
Console.WriteLine($"Endpoint: {cassette.EndpointName} - {cassette.Endpoint.Address}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(5000);
|
||||||
|
|
||||||
|
var found = casettes.FirstOrDefault(x => x.Endpoint.Address.ToString() == "10.237.191.75");
|
||||||
|
|
||||||
|
found.IPAddress = IPAddress.Parse($"192.168.1.{new Random().Next(20, 120)}");
|
||||||
|
found.Name = $"Rand{new Random().Next(5, 15)}";
|
||||||
|
|
||||||
|
await found.SendNewConfigAsync();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using System.Reflection;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MewtocolNet.Logging;
|
using MewtocolNet.Logging;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Examples;
|
namespace Examples;
|
||||||
|
|
||||||
@@ -16,13 +18,21 @@ class Program {
|
|||||||
|
|
||||||
static void Main(string[] args) {
|
static void Main(string[] args) {
|
||||||
|
|
||||||
|
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us");
|
||||||
|
|
||||||
|
Console.Clear();
|
||||||
|
|
||||||
AppDomain.CurrentDomain.UnhandledException += (s,e) => {
|
AppDomain.CurrentDomain.UnhandledException += (s,e) => {
|
||||||
Console.WriteLine(e.ExceptionObject.ToString());
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
|
Console.WriteLine($"Uncatched exception: {e.ExceptionObject.ToString()}");
|
||||||
|
Console.ResetColor();
|
||||||
};
|
};
|
||||||
|
|
||||||
TaskScheduler.UnobservedTaskException += (s,e) => {
|
//TaskScheduler.UnobservedTaskException += (s,e) => {
|
||||||
Console.WriteLine(e.Exception.ToString());
|
// Console.ForegroundColor = ConsoleColor.Magenta;
|
||||||
};
|
// Console.WriteLine($"Unobserved Task Uncatched exception: {e.Exception.ToString()}");
|
||||||
|
// Console.ResetColor();
|
||||||
|
//};
|
||||||
|
|
||||||
ExampleSzenarios.SetupLogger();
|
ExampleSzenarios.SetupLogger();
|
||||||
|
|
||||||
|
|||||||
161
MewtocolNet/ComCassette/CassetteFinder.cs
Normal file
161
MewtocolNet/ComCassette/CassetteFinder.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MewtocolNet.ComCassette {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a interface to modify and find PLC network cassettes also known as COM5
|
||||||
|
/// </summary>
|
||||||
|
public class CassetteFinder {
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<CassetteInformation>> FindClientsAsync (string ipSource = null, int timeoutMs = 100) {
|
||||||
|
|
||||||
|
var from = new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
|
||||||
|
List<CassetteInformation> cassettesFound = new List<CassetteInformation>();
|
||||||
|
List<Task<List<CassetteInformation>>> interfacesTasks = new List<Task<List<CassetteInformation>>>();
|
||||||
|
|
||||||
|
var usableInterfaces = GetUseableNetInterfaces();
|
||||||
|
|
||||||
|
if (ipSource == null) {
|
||||||
|
|
||||||
|
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||||
|
|
||||||
|
foreach (NetworkInterface netInterface in usableInterfaces) {
|
||||||
|
|
||||||
|
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
|
||||||
|
var unicastInfo = ipProps.UnicastAddresses
|
||||||
|
.FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
|
||||||
|
|
||||||
|
var ep = new IPEndPoint(unicastInfo.Address, 0);
|
||||||
|
interfacesTasks.Add(FindClientsForEndpoint(ep, timeoutMs, netInterface.Name));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
from = new IPEndPoint(IPAddress.Parse(ipSource), 0);
|
||||||
|
|
||||||
|
var netInterface = usableInterfaces.FirstOrDefault(x => x.GetIPProperties().UnicastAddresses.Any(y => y.Address.ToString() == ipSource));
|
||||||
|
|
||||||
|
if (netInterface == null)
|
||||||
|
throw new NotSupportedException($"The host endpoint {ipSource}, is not available");
|
||||||
|
|
||||||
|
interfacesTasks.Add(FindClientsForEndpoint(from, timeoutMs, netInterface.Name));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//run the interface querys
|
||||||
|
var grouped = await Task.WhenAll(interfacesTasks);
|
||||||
|
|
||||||
|
foreach (var item in grouped)
|
||||||
|
cassettesFound.AddRange(item);
|
||||||
|
|
||||||
|
return cassettesFound;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<NetworkInterface> GetUseableNetInterfaces () {
|
||||||
|
|
||||||
|
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) {
|
||||||
|
|
||||||
|
bool isEthernet =
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit ||
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx ||
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT ||
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet;
|
||||||
|
|
||||||
|
bool isWlan = netInterface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211;
|
||||||
|
|
||||||
|
bool isUsable = netInterface.OperationalStatus == OperationalStatus.Up;
|
||||||
|
|
||||||
|
if (!isUsable) continue;
|
||||||
|
if (!(isWlan || isEthernet)) continue;
|
||||||
|
|
||||||
|
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
|
||||||
|
var hasUnicastInfo = ipProps.UnicastAddresses
|
||||||
|
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
|
||||||
|
|
||||||
|
if (!hasUnicastInfo) continue;
|
||||||
|
|
||||||
|
yield return netInterface;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<CassetteInformation>> FindClientsForEndpoint (IPEndPoint from, int timeoutMs, string ipEndpointName) {
|
||||||
|
|
||||||
|
var cassettesFound = new List<CassetteInformation>();
|
||||||
|
|
||||||
|
int plcPort = 9090;
|
||||||
|
|
||||||
|
// Byte msg to request the status transmission of all plcs
|
||||||
|
byte[] requestCode = new byte[] { 0x88, 0x40, 0x00 };
|
||||||
|
|
||||||
|
// The start code of the status transmission response
|
||||||
|
byte[] startCode = new byte[] { 0x88, 0xC0, 0x00 };
|
||||||
|
|
||||||
|
using(var udpClient = new UdpClient()) {
|
||||||
|
|
||||||
|
udpClient.EnableBroadcast = true;
|
||||||
|
|
||||||
|
udpClient.Client.Bind(from);
|
||||||
|
|
||||||
|
//broadcast packet to all devices (plc specific package)
|
||||||
|
udpClient.Send(requestCode, requestCode.Length, "255.255.255.255", plcPort);
|
||||||
|
|
||||||
|
//canceling after no new data was read
|
||||||
|
CancellationTokenSource tSource = new CancellationTokenSource();
|
||||||
|
var tm = new System.Timers.Timer(timeoutMs);
|
||||||
|
tm.Elapsed += (s, e) => {
|
||||||
|
tSource.Cancel();
|
||||||
|
tm.Stop();
|
||||||
|
};
|
||||||
|
tm.Start();
|
||||||
|
|
||||||
|
//wait for devices to send response
|
||||||
|
try {
|
||||||
|
|
||||||
|
byte[] recvBuffer = null;
|
||||||
|
|
||||||
|
while (!tSource.Token.IsCancellationRequested) {
|
||||||
|
|
||||||
|
var res = await udpClient.ReceiveAsync().WithCancellation(tSource.Token);
|
||||||
|
|
||||||
|
if (res.Buffer == null) break;
|
||||||
|
|
||||||
|
recvBuffer = res.Buffer;
|
||||||
|
|
||||||
|
if (recvBuffer.SearchBytePattern(startCode) == 0) {
|
||||||
|
|
||||||
|
tm.Stop();
|
||||||
|
tm.Start();
|
||||||
|
|
||||||
|
var parsed = CassetteInformation.FromBytes(recvBuffer, from, ipEndpointName);
|
||||||
|
if (parsed != null) cassettesFound.Add(parsed);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (OperationCanceledException) { } catch (SocketException) { }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return cassettesFound;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
182
MewtocolNet/ComCassette/CassetteInformation.cs
Normal file
182
MewtocolNet/ComCassette/CassetteInformation.cs
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
//WARNING! The whole UDP protocol was reverse engineered and is not fully implemented..
|
||||||
|
|
||||||
|
namespace MewtocolNet.ComCassette {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Information about the COM cassette
|
||||||
|
/// </summary>
|
||||||
|
public class CassetteInformation {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the cassette is currently configurating
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConfigurating { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the COM cassette
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates the usage of DHCP
|
||||||
|
/// </summary>
|
||||||
|
public bool UsesDHCP { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IP Address of the COM cassette
|
||||||
|
/// </summary>
|
||||||
|
public IPAddress IPAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subnet mask of the cassette
|
||||||
|
/// </summary>
|
||||||
|
public IPAddress SubnetMask { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default gateway of the cassette
|
||||||
|
/// </summary>
|
||||||
|
public IPAddress GatewayAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mac address of the cassette
|
||||||
|
/// </summary>
|
||||||
|
public byte[] MacAddress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The source endpoint the cassette is reachable from
|
||||||
|
/// </summary>
|
||||||
|
public IPEndPoint Endpoint { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the endpoint the device is reachable from, or null if not specifically defined
|
||||||
|
/// </summary>
|
||||||
|
public string EndpointName { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Firmware version as string
|
||||||
|
/// </summary>
|
||||||
|
public string FirmwareVersion { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tcp port of the cassette
|
||||||
|
/// </summary>
|
||||||
|
public int Port { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status of the cassette
|
||||||
|
/// </summary>
|
||||||
|
public CassetteStatus Status { get; private set; }
|
||||||
|
|
||||||
|
internal static CassetteInformation FromBytes(byte[] bytes, IPEndPoint endpoint, string endpointName) {
|
||||||
|
|
||||||
|
// Receive data package explained:
|
||||||
|
// 0 3 4 8 12 17 22 24 27 29 31 32
|
||||||
|
// 88 C0 00 | 00 | C0 A8 73 D4 | FF FF FF 00 | C0 A8 73 3C | 00 | C0 8F 60 53 1C | 01 10 | 23 86 | 00 | 25 | 00 | 00 | 00 | 0D | (byte) * (n) NAME LEN
|
||||||
|
// Header |DHCP| IPv4 addr. | Subnet Mask | IPv4 Gatwy | | Mac Addr. | Ver. | Port | | | |STAT| | Name LEN | Name
|
||||||
|
// 1 or 0 Procuct Type? StatusCode Length of Name
|
||||||
|
|
||||||
|
//get ips / mac
|
||||||
|
var dhcpOn = bytes.Skip(3).First() != 0x00;
|
||||||
|
var ipAdd = new IPAddress(bytes.Skip(4).Take(4).ToArray());
|
||||||
|
var subnetMask = new IPAddress(bytes.Skip(8).Take(4).ToArray());
|
||||||
|
var gateWaysAdd = new IPAddress(bytes.Skip(12).Take(4).ToArray());
|
||||||
|
var macAdd = bytes.Skip(17).Take(5).ToArray();
|
||||||
|
var firmwareV = string.Join(".", bytes.Skip(22).Take(2).Select(x => x.ToString("X1")).ToArray());
|
||||||
|
var port = BitConverter.ToUInt16(bytes.Skip(24).Take(2).Reverse().ToArray(), 0);
|
||||||
|
var status = (CassetteStatus)bytes.Skip(29).First();
|
||||||
|
|
||||||
|
//missing blocks, later
|
||||||
|
|
||||||
|
//get name
|
||||||
|
var name = Encoding.ASCII.GetString(bytes.Skip(32).ToArray());
|
||||||
|
|
||||||
|
return new CassetteInformation {
|
||||||
|
|
||||||
|
Name = name,
|
||||||
|
UsesDHCP = dhcpOn,
|
||||||
|
IPAddress = ipAdd,
|
||||||
|
SubnetMask = subnetMask,
|
||||||
|
GatewayAddress = gateWaysAdd,
|
||||||
|
MacAddress = macAdd,
|
||||||
|
Endpoint = endpoint,
|
||||||
|
EndpointName = endpointName,
|
||||||
|
FirmwareVersion = firmwareV,
|
||||||
|
Port = port,
|
||||||
|
Status = status,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendNewConfigAsync () {
|
||||||
|
|
||||||
|
if (IsConfigurating) return;
|
||||||
|
|
||||||
|
// this command gets sent to a specific plc ip address to overwrite the cassette config
|
||||||
|
// If dhcp is set to 1 the ip is ignored but still must be valid
|
||||||
|
|
||||||
|
// 88 41 00 | 00 | C0 8F 61 07 1B | 05 | 54 65 73 74 31 | 05 | 46 50 58 45 54 | 00 | C0 A8 01 07 | FF FF FF 00 | C0 A8 73 3C
|
||||||
|
// Header | | | 5 | T e s t 1 | 05 | F P X E T |0||1| 192.168.1.7 | 255.255... | 192.168.115.60
|
||||||
|
// Header | | Mac Address |LEN>| ASCII Name |LEN>| Static |DHCP| Target IP | Subnet Mask | Gateway
|
||||||
|
|
||||||
|
IsConfigurating = true;
|
||||||
|
|
||||||
|
List<byte> sendBytes = new List<byte>();
|
||||||
|
|
||||||
|
//add cmd header
|
||||||
|
sendBytes.AddRange(new byte[] { 0x88, 0x41, 0x00, 0x00 });
|
||||||
|
|
||||||
|
//add mac
|
||||||
|
sendBytes.AddRange(MacAddress);
|
||||||
|
|
||||||
|
//add name length
|
||||||
|
sendBytes.Add((byte)Name.Length);
|
||||||
|
|
||||||
|
//add name
|
||||||
|
sendBytes.AddRange(Encoding.ASCII.GetBytes(Name));
|
||||||
|
|
||||||
|
//FPXET
|
||||||
|
var subname = Encoding.ASCII.GetBytes("TESTFP");
|
||||||
|
|
||||||
|
//add sub name length
|
||||||
|
sendBytes.Add((byte)subname.Length);
|
||||||
|
|
||||||
|
//add subname
|
||||||
|
sendBytes.AddRange(subname);
|
||||||
|
|
||||||
|
//add dhcp 0 | 1
|
||||||
|
sendBytes.Add((byte)(UsesDHCP ? 0x01 : 0x00));
|
||||||
|
|
||||||
|
//add ip address
|
||||||
|
sendBytes.AddRange(IPAddress.GetAddressBytes());
|
||||||
|
|
||||||
|
//add subnet mask ip address
|
||||||
|
sendBytes.AddRange(SubnetMask.GetAddressBytes());
|
||||||
|
|
||||||
|
//add gateway ip
|
||||||
|
sendBytes.AddRange(GatewayAddress.GetAddressBytes());
|
||||||
|
|
||||||
|
var sendBytesArr = sendBytes.ToArray();
|
||||||
|
|
||||||
|
using(var udpClient = new UdpClient()) {
|
||||||
|
|
||||||
|
udpClient.Client.Bind(Endpoint);
|
||||||
|
|
||||||
|
//broadcast packet to all devices (plc specific package)
|
||||||
|
await udpClient.SendAsync(sendBytesArr, sendBytesArr.Length, "255.255.255.255", 9090);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
23
MewtocolNet/ComCassette/CassetteStatus.cs
Normal file
23
MewtocolNet/ComCassette/CassetteStatus.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MewtocolNet.ComCassette {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Needs a list of all status codes.. hard to reverse engineer
|
||||||
|
/// </summary>
|
||||||
|
public enum CassetteStatus {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cassette is running as intended
|
||||||
|
/// </summary>
|
||||||
|
Normal = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Cassette DHCP resolution error
|
||||||
|
/// </summary>
|
||||||
|
DHCPError = 2,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
27
MewtocolNet/Extensions/AsyncExtensions.cs
Normal file
27
MewtocolNet/Extensions/AsyncExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace MewtocolNet {
|
||||||
|
|
||||||
|
internal static class AsyncExtensions {
|
||||||
|
|
||||||
|
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken) {
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) {
|
||||||
|
if (task != await Task.WhenAny(task, tcs.Task)) {
|
||||||
|
throw new OperationCanceledException(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return task.Result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -59,6 +59,29 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#region Byte and string operation helpers
|
#region Byte and string operation helpers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches a byte array for a pattern
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="src"></param>
|
||||||
|
/// <param name="pattern"></param>
|
||||||
|
/// <returns>The start index of the found pattern or -1</returns>
|
||||||
|
public static int SearchBytePattern(this byte[] src, byte[] pattern) {
|
||||||
|
|
||||||
|
int maxFirstCharSlot = src.Length - pattern.Length + 1;
|
||||||
|
for (int i = 0; i < maxFirstCharSlot; i++) {
|
||||||
|
if (src[i] != pattern[0]) // compare only first byte
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// found a match on first byte, now try to match rest of the pattern
|
||||||
|
for (int j = pattern.Length - 1; j >= 1; j--) {
|
||||||
|
if (src[i + j] != pattern[j]) break;
|
||||||
|
if (j == 1) return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a string (after converting to upper case) to ascii bytes
|
/// Converts a string (after converting to upper case) to ascii bytes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -204,7 +227,7 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="seperator">Seperator between the hex numbers</param>
|
/// <param name="seperator">Seperator between the hex numbers</param>
|
||||||
/// <param name="arr">The byte array</param>
|
/// <param name="arr">The byte array</param>
|
||||||
internal static string ToHexString (this byte[] arr, string seperator = "") {
|
public static string ToHexString (this byte[] arr, string seperator = "") {
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ namespace MewtocolNet {
|
|||||||
await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
|
await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
|
||||||
|
|
||||||
bool terminatorReceived = responseBuffer.Any(x => x == (byte)CR);
|
bool terminatorReceived = responseBuffer.Any(x => x == (byte)CR);
|
||||||
var delimiterTerminatorIdx = SearchBytePattern(responseBuffer, new byte[] { (byte)DELIMITER, (byte)CR });
|
var delimiterTerminatorIdx = responseBuffer.SearchBytePattern(new byte[] { (byte)DELIMITER, (byte)CR });
|
||||||
|
|
||||||
if (terminatorReceived && delimiterTerminatorIdx == -1) {
|
if (terminatorReceived && delimiterTerminatorIdx == -1) {
|
||||||
cmdState = CommandState.Complete;
|
cmdState = CommandState.Complete;
|
||||||
@@ -557,23 +557,6 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int SearchBytePattern (byte[] src, byte[] pattern) {
|
|
||||||
|
|
||||||
int maxFirstCharSlot = src.Length - pattern.Length + 1;
|
|
||||||
for (int i = 0; i < maxFirstCharSlot; i++) {
|
|
||||||
if (src[i] != pattern[0]) // compare only first byte
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// found a match on first byte, now try to match rest of the pattern
|
|
||||||
for (int j = pattern.Length - 1; j >= 1; j--) {
|
|
||||||
if (src[i + j] != pattern[j]) break;
|
|
||||||
if (j == 1) return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Disposing
|
#region Disposing
|
||||||
|
|||||||
Reference in New Issue
Block a user