diff --git a/Examples/ExampleScenarios.cs b/Examples/ExampleScenarios.cs index 442844f..59d4bb5 100644 --- a/Examples/ExampleScenarios.cs +++ b/Examples/ExampleScenarios.cs @@ -126,7 +126,7 @@ public class ExampleScenarios { } //await first register data - await interf.AwaitFirstDataAsync(); + await interf.AwaitFirstDataCycleAsync(); _ = Task.Factory.StartNew(async () => { diff --git a/MewTerminal/Commands/ClearCommand.cs b/MewTerminal/Commands/ClearCommand.cs new file mode 100644 index 0000000..098f89c --- /dev/null +++ b/MewTerminal/Commands/ClearCommand.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using CommandLine; +using MewtocolNet; +using MewtocolNet.ComCassette; +using Spectre.Console; + +namespace MewTerminal.Commands; + +[Verb("clear", HelpText = "Clears console", Hidden = true)] +internal class ClearCommand : CommandLineExcecuteable { + + public override void Run() { + + Console.Clear(); + + } + +} \ No newline at end of file diff --git a/MewTerminal/Commands/CommandLineExcecuteable.cs b/MewTerminal/Commands/CommandLineExcecuteable.cs new file mode 100644 index 0000000..977b438 --- /dev/null +++ b/MewTerminal/Commands/CommandLineExcecuteable.cs @@ -0,0 +1,41 @@ +using CommandLine.Text; +using CommandLine; +using MewtocolNet.Logging; + +namespace MewTerminal.Commands; + +public abstract class CommandLineExcecuteable { + + static UnParserSettings UnparserSet = new UnParserSettings { + PreferShortName = true, + }; + + [Option('v', "verbosity", HelpText = "Sets the Loglevel verbosity", Default = LogLevel.None)] + public LogLevel LogLevel { get; set; } = LogLevel.None; + + [Usage] + public static IEnumerable Examples { + get { + return new List() { + new Example( + helpText: "Sanning from adapter with ip 127.0.0.1 and logging all critical messages", + formatStyle: UnparserSet, + sample: new ScanCommand { + IPSource = "127.0.0.1", + LogLevel = LogLevel.Critical, + }), + new Example( + helpText: "Scanning from all adapters and logging only errors", + formatStyle: UnparserSet, + sample: new ScanCommand { + LogLevel = LogLevel.Error, + }), + }; + } + } + + public virtual void Run() { } + + public virtual Task RunAsync () => Task.CompletedTask; + +} diff --git a/MewTerminal/Commands/ScanCommand.cs b/MewTerminal/Commands/ScanCommand.cs new file mode 100644 index 0000000..697766b --- /dev/null +++ b/MewTerminal/Commands/ScanCommand.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using CommandLine; +using MewtocolNet; +using MewtocolNet.ComCassette; +using MewtocolNet.Logging; +using Spectre.Console; + +namespace MewTerminal.Commands; + +[Verb("scan", HelpText = "Scans all network PLCs")] +internal class ScanCommand : CommandLineExcecuteable { + + [Option("ip", HelpText = "IP of the source adapter" )] + public string? IPSource { get; set; } + + [Option("timeout", Default = 100)] + public int? TimeoutMS { get; set; } + + [Option("plc", Required = false, HelpText = "Gets the PLC types")] + public bool GetPLCTypes { get; set; } + + private class PLCCassetteTypeInfo { + + public CassetteInformation Cassette { get; set; } + + public PLCInfo PLCInf { get; set; } + + } + + public override async Task RunAsync () { + + await AnsiConsole.Status() + .Spinner(Spinner.Known.Dots) + .StartAsync("Scanning...", async ctx => { + + var query = await CassetteFinder.FindClientsAsync(IPSource, TimeoutMS ?? 100); + + var found = query.Select(x => new PLCCassetteTypeInfo { Cassette = x }).ToList(); + + if (found.Count > 0 && GetPLCTypes) { + + foreach (var item in found) { + + ctx.Status($"Getting cassette PLC {item.Cassette.IPAddress}:{item.Cassette.Port}") + .Spinner(Spinner.Known.Dots); + + var dev = Mewtocol.Ethernet(item.Cassette.IPAddress, item.Cassette.Port); + dev.ConnectTimeout = 1000; + await dev.ConnectAsync(); + item.PLCInf = dev.PlcInfo; + + dev.Disconnect(); + + } + + } + + if (found.Count() > 0) { + + AnsiConsole.MarkupLineInterpolated($"✅ Found {found.Count()} devices..."); + + } else { + + AnsiConsole.MarkupLineInterpolated($"❌ Found no devices"); + return; + + } + + if (found.Any(x => x.PLCInf != PLCInfo.None)) { + + AnsiConsole.Write(found.Select(x => new { + x.Cassette.Name, + PLC = x.PLCInf.TypeCode.ToName(), + IsRun = x.PLCInf.OperationMode.HasFlag(OPMode.Run), + IP = x.Cassette.IPAddress, + x.Cassette.Port, + DHCP = x.Cassette.UsesDHCP, + MAC = x.Cassette.MacAddress, + Ver = x.Cassette.FirmwareVersion, + x.Cassette.Status, + }).ToTable()); + + } else { + + AnsiConsole.Write(found.Select(x => new { + x.Cassette.Name, + IP = x.Cassette.IPAddress, + x.Cassette.Port, + DHCP = x.Cassette.UsesDHCP, + MAC = x.Cassette.MacAddress, + Ver = x.Cassette.FirmwareVersion, + x.Cassette.Status, + }).ToTable()); + + } + + }); + + } + +} \ No newline at end of file diff --git a/MewTerminal/Helpers/Helpers.cs b/MewTerminal/Helpers/Helpers.cs new file mode 100644 index 0000000..bc03a36 --- /dev/null +++ b/MewTerminal/Helpers/Helpers.cs @@ -0,0 +1,54 @@ +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MewTerminal; + +internal static class Helpers { + + internal static Table ToTable (this IEnumerable data, params string[] markups) { + + // Create a table + var table = new Table(); + var type = typeof(T); + + var props = type.GetProperties(); + bool isFirst = true; + + foreach (var item in data) { + + var rowVals = new List(); + + foreach (var prop in props) { + + if(isFirst) table.AddColumn(prop.Name); + + var propVal = prop.GetValue(item); + + string strVal = propVal?.ToString() ?? "null"; + + if (propVal is byte[] bArr) { + strVal = string.Join(" ", bArr.Select(x => x.ToString("X2"))); + } + + strVal = strVal.Replace("[", ""); + strVal = strVal.Replace("]", ""); + + rowVals.Add(strVal); + + } + + isFirst = false; + + table.AddRow(rowVals.ToArray()); + + } + + return table; + + } + +} diff --git a/MewTerminal/MewTerminal.csproj b/MewTerminal/MewTerminal.csproj new file mode 100644 index 0000000..e75817e --- /dev/null +++ b/MewTerminal/MewTerminal.csproj @@ -0,0 +1,19 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + + + + + + diff --git a/MewTerminal/Program.cs b/MewTerminal/Program.cs new file mode 100644 index 0000000..2de046c --- /dev/null +++ b/MewTerminal/Program.cs @@ -0,0 +1,86 @@ +using CommandLine; +using CommandLine.Text; +using MewTerminal.Commands; +using MewtocolNet.Logging; +using Spectre.Console; +using System.Reflection; + +namespace MewTerminal; + +internal class Program { + + static void Main(string[] args) { + + Logger.OnNewLogMessage((dt, lv, msg) => { + + AnsiConsole.WriteLine($"{msg}"); + + }); + + #if DEBUG + + Console.Clear(); + + var firstArg = new string[] { "help" }; + + start: + + if(firstArg == null) { + Console.WriteLine("Enter arguments [DEBUG MODE]"); + args = Console.ReadLine().SplitArgs(); + } + + //print help first time + InitParser(firstArg ?? args); + firstArg = null; + goto start; + + #else + + InitParser(args); + + #endif + + } + + private static Type[] LoadVerbs() { + + var lst = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => t.GetCustomAttribute() != null) + .ToArray(); + return lst; + + } + + static void InitParser (string[] args) { + + var types = LoadVerbs(); + + var parseRes = Parser.Default.ParseArguments(args, types); + + var helpText = HelpText.AutoBuild(parseRes, h => { + + h.AddEnumValuesToHelpText = true; + + return h; + + }, e => e); + + parseRes.WithNotParsed(err => { + + }); + + if(parseRes?.Value != null && parseRes.Value is CommandLineExcecuteable exc) { + + Logger.LogLevel = exc.LogLevel; + + exc.Run(); + var task = Task.Run(exc.RunAsync); + task.Wait(); + + } + + } + +} diff --git a/MewtocolNet.sln b/MewtocolNet.sln index 0fe6862..5c0ee6e 100644 --- a/MewtocolNet.sln +++ b/MewtocolNet.sln @@ -11,7 +11,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewtocolTests", "MewtocolTe EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewExplorer", "MewExplorer\MewExplorer.csproj", "{F243F38A-76D3-4C38-BAE6-C61729479661}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocBuilder", "DocBuilder\DocBuilder.csproj", "{50F2D23F-C875-4006-A0B6-7F5A181BC944}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocBuilder", "DocBuilder\DocBuilder.csproj", "{50F2D23F-C875-4006-A0B6-7F5A181BC944}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MewTerminal", "MewTerminal\MewTerminal.csproj", "{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -89,6 +91,18 @@ Global {50F2D23F-C875-4006-A0B6-7F5A181BC944}.Release|x64.Build.0 = Release|Any CPU {50F2D23F-C875-4006-A0B6-7F5A181BC944}.Release|x86.ActiveCfg = Release|Any CPU {50F2D23F-C875-4006-A0B6-7F5A181BC944}.Release|x86.Build.0 = Release|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|x64.ActiveCfg = Debug|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|x64.Build.0 = Debug|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|x86.ActiveCfg = Debug|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Debug|x86.Build.0 = Debug|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|Any CPU.Build.0 = Release|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x64.ActiveCfg = Release|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x64.Build.0 = Release|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.ActiveCfg = Release|Any CPU + {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MewtocolNet/ComCassette/CassetteFinder.cs b/MewtocolNet/ComCassette/CassetteFinder.cs index 346b212..748d689 100644 --- a/MewtocolNet/ComCassette/CassetteFinder.cs +++ b/MewtocolNet/ComCassette/CassetteFinder.cs @@ -1,4 +1,5 @@ -using System; +using MewtocolNet.Helpers; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -20,8 +21,7 @@ namespace MewtocolNet.ComCassette { var from = new IPEndPoint(IPAddress.Any, 0); - List cassettesFound = new List(); - List>> interfacesTasks = new List>>(); + var interfacesTasks = new List>>(); var usableInterfaces = GetUseableNetInterfaces(); @@ -56,10 +56,21 @@ namespace MewtocolNet.ComCassette { //run the interface querys var grouped = await Task.WhenAll(interfacesTasks); - foreach (var item in grouped) - cassettesFound.AddRange(item); + var decomposed = new List(); - return cassettesFound; + foreach (var grp in grouped) { + + foreach (var cassette in grp) { + + if (decomposed.Any(x => x.MacAddress.SequenceEqual(cassette.MacAddress))) continue; + + decomposed.Add(cassette); + + } + + } + + return decomposed; } diff --git a/MewtocolNet/CpuInfo.cs b/MewtocolNet/CpuInfo.cs deleted file mode 100644 index 273e636..0000000 --- a/MewtocolNet/CpuInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; - -namespace MewtocolNet { - - /// - /// Contains information about the plc and its cpu - /// - public struct CpuInfo { - - /// - /// The cpu type of the plc - /// - public CpuType Cputype { get; set; } - - /// - /// Program capacity in 1K steps - /// - public int ProgramCapacity { get; set; } - - /// - /// Version of the cpu - /// - public string CpuVersion { get; set; } - - internal static CpuInfo BuildFromHexString(string _cpuType, string _cpuVersion, string _progCapacity) { - - CpuInfo retInf = new CpuInfo(); - - retInf.ProgramCapacity = Convert.ToInt32(_progCapacity); - retInf.CpuVersion = _cpuVersion.Insert(1, "."); - return retInf; - - } - - } - -} \ No newline at end of file diff --git a/MewtocolNet/Helpers/LinqHelpers.cs b/MewtocolNet/Helpers/LinqHelpers.cs new file mode 100644 index 0000000..03d9595 --- /dev/null +++ b/MewtocolNet/Helpers/LinqHelpers.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MewtocolNet.Helpers { + + internal static class LinqHelpers { + + internal static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) { + return DistinctBy(source, keySelector, null); + } + + internal static IEnumerable DistinctBy + (this IEnumerable source, Func keySelector, IEqualityComparer comparer) { + + if (source == null) throw new ArgumentNullException(nameof(source)); + if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); + + return _(); IEnumerable _() { + var knownKeys = new HashSet(comparer); + foreach (var element in source) { + if (knownKeys.Add(keySelector(element))) + yield return element; + } + } + + } + + } + +} diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index 33e692e..0633e98 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -323,7 +323,9 @@ namespace MewtocolNet { /// public static string ToName(this PlcType plcT) { - return string.Join(" or ", ParsedPlcName.LegacyPlcDeconstruct(plcT).Select(x => x.WholeName)); + if (plcT == 0) return "Unknown"; + + return string.Join(" or ", ParsedPlcName.PlcDeconstruct(plcT).Select(x => x.WholeName)); } @@ -332,7 +334,9 @@ namespace MewtocolNet { /// public static ParsedPlcName[] ToNameDecompose (this PlcType legacyT) { - return ParsedPlcName.LegacyPlcDeconstruct(legacyT); + if ((int)legacyT == 0) return Array.Empty(); + + return ParsedPlcName.PlcDeconstruct(legacyT); } diff --git a/MewtocolNet/Helpers/ParsedPlcName.cs b/MewtocolNet/Helpers/ParsedPlcName.cs index d4d266c..c1e9ea2 100644 --- a/MewtocolNet/Helpers/ParsedPlcName.cs +++ b/MewtocolNet/Helpers/ParsedPlcName.cs @@ -35,9 +35,9 @@ namespace MewtocolNet { /// public override string ToString() => WholeName; - internal static ParsedPlcName[] LegacyPlcDeconstruct (T legacyT) { + internal static ParsedPlcName[] PlcDeconstruct (PlcType plcT) { - string wholeStr = legacyT.ToString(); + string wholeStr = plcT.ToString(); var split = wholeStr.Replace("_OR_", "|").Split('|'); var reg = new Regex(@"(?[A-Za-z0-9]*)_(?[A-Za-z0-9]*)(?:__)?(?.*)"); diff --git a/MewtocolNet/IPlc.cs b/MewtocolNet/IPlc.cs index 6bc1c1b..1386b93 100644 --- a/MewtocolNet/IPlc.cs +++ b/MewtocolNet/IPlc.cs @@ -83,7 +83,7 @@ namespace MewtocolNet { /// Use this to await the first poll iteration after connecting, /// This also completes if the initial connection fails /// - Task AwaitFirstDataAsync(); + Task AwaitFirstDataCycleAsync(); /// /// Runs a single poller cycle manually, diff --git a/MewtocolNet/Logging/LoggerEnums.cs b/MewtocolNet/Logging/LoggerEnums.cs index a845218..6bd2d19 100644 --- a/MewtocolNet/Logging/LoggerEnums.cs +++ b/MewtocolNet/Logging/LoggerEnums.cs @@ -5,6 +5,10 @@ /// public enum LogLevel { + /// + /// Logs nothing + /// + None = -1, /// /// Logs only errors /// diff --git a/MewtocolNet/MewtocolFrameResponse.cs b/MewtocolNet/MewtocolFrameResponse.cs index a381336..0ff4547 100644 --- a/MewtocolNet/MewtocolFrameResponse.cs +++ b/MewtocolNet/MewtocolFrameResponse.cs @@ -14,6 +14,10 @@ namespace MewtocolNet { public string Error { get; private set; } + public static MewtocolFrameResponse Timeout => new MewtocolFrameResponse(403, "Request timed out"); + + public static MewtocolFrameResponse NotIntialized => new MewtocolFrameResponse(405, "PLC was not initialized"); + public MewtocolFrameResponse (string response) { Success = true; @@ -41,6 +45,16 @@ namespace MewtocolNet { } + /// + public static bool operator == (MewtocolFrameResponse c1, MewtocolFrameResponse c2) { + return c1.Equals(c2); + } + + /// + public static bool operator != (MewtocolFrameResponse c1, MewtocolFrameResponse c2) { + return !c1.Equals(c2); + } + } } diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index eec4492..3522607 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -159,7 +159,7 @@ namespace MewtocolNet { public virtual async Task ConnectAsync() => throw new NotImplementedException(); /// - public async Task AwaitFirstDataAsync() => await firstPollTask; + public async Task AwaitFirstDataCycleAsync() => await firstPollTask; /// public void Disconnect() { @@ -196,7 +196,7 @@ namespace MewtocolNet { if (await Task.WhenAny(tempResponse, Task.Delay(timeoutMs)) != tempResponse) { // timeout logic - return new MewtocolFrameResponse(403, "Timed out"); + return MewtocolFrameResponse.Timeout; } tcpMessagesSentThisCycle++; @@ -210,7 +210,7 @@ namespace MewtocolNet { try { - if (stream == null) return new MewtocolFrameResponse(405, "PLC not initialized"); + if (stream == null) return MewtocolFrameResponse.NotIntialized; if (useBcc) frame = $"{frame.BuildBCCFrame()}"; diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 9775b1d..409071a 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -1,3 +1,4 @@ +using MewtocolNet.Exceptions; using MewtocolNet.Logging; using MewtocolNet.Registers; using System; @@ -21,18 +22,42 @@ namespace MewtocolNet { /// A PLCInfo class public async Task GetPLCInfoAsync(int timeout = -1) { - var regexRT = new Regex(@"\%EE\$RT(?..)(?..)(?..)(?..)..(?..)(?....).*", RegexOptions.IgnoreCase); - - var regexEXRT = new Regex(@"\%EE\$EX00RT00(?..)(?..)..(?..)(?..)..(?..)(?....)(?..)(?..)(?.)(?....)(?....)(?....).*", RegexOptions.IgnoreCase); - var resRT = await SendCommandAsync("%EE#RT", timeoutMs: timeout); - if (!resRT.Success) return null; + + if (!resRT.Success) { + + //timeouts are ok and dont throw + if (resRT == MewtocolFrameResponse.Timeout) return null; + + throw new MewtocolException(resRT.Error); + + } var resEXRT = await SendCommandAsync("%EE#EX00RT00", timeoutMs: timeout); + //timeouts are ok and dont throw + if (!resRT.Success && resRT == MewtocolFrameResponse.Timeout) return null; + + PLCInfo plcInf; + + //dont overwrite, use first + if (!PLCInfo.TryFromRT(resRT.Response, out plcInf)) { + + throw new MewtocolException("The RT message could not be parsed"); + + } + + //overwrite first with EXRT + if (resEXRT.Success && !plcInf.TryExtendFromEXRT(resEXRT.Response)) { + + throw new MewtocolException("The EXRT message could not be parsed"); + + } + + PlcInfo = plcInf; + + return plcInf; - return null; - } #endregion diff --git a/MewtocolNet/MewtocolNet.csproj b/MewtocolNet/MewtocolNet.csproj index 2bb9118..c9ade86 100644 --- a/MewtocolNet/MewtocolNet.csproj +++ b/MewtocolNet/MewtocolNet.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0; + netstandard2.0; Mewtocol.NET 0.7.1 Felix Weiss diff --git a/MewtocolNet/PLCInfo.cs b/MewtocolNet/PLCInfo.cs index 939bd59..5334306 100644 --- a/MewtocolNet/PLCInfo.cs +++ b/MewtocolNet/PLCInfo.cs @@ -1,66 +1,111 @@ -namespace MewtocolNet { +using MewtocolNet.PublicEnums; +using System.Globalization; +using System.Text.RegularExpressions; +namespace MewtocolNet { - public enum CpuType { - - - - } - + /// + /// Holds various informations about the PLC + /// public struct PLCInfo { /// - /// Current error code of the PLC + /// The type of the PLC named by Panasonic /// - public string ErrorCode { get; internal set; } + public PlcType TypeCode { get; private set; } /// - /// Current station number of the PLC + /// Contains information about the PLCs operation modes as flags /// - public int StationNumber { get; internal set; } - - - } - - /// - /// Contains generic information about the plc - /// - public class PLCInfoOld - { + public OPMode OperationMode { get; private set; } /// - /// Contains information about the PLCs cpu + /// Hardware information flags about the PLC /// - public CpuInfo CpuInformation { get; set; } + public HWInformation HardwareInformation { get; private set; } + /// - /// Contains information about the PLCs operation modes + /// Program capacity in 1K steps /// - public PLCMode OperationMode { get; set; } + public int ProgramCapacity { get; private set; } + + /// + /// Version of the cpu + /// + public string CpuVersion { get; private set; } /// /// Current error code of the PLC /// - public string ErrorCode { get; set; } + public string SelfDiagnosticError { get; internal set; } + + internal bool TryExtendFromEXRT (string msg) { + + var regexEXRT = new Regex(@"\%EE\$EX00RT00(?..)(?..)..(?..)(?..)..(?..)(?....)(?..)(?..)(?.)(?....)(?....)(?....).*", RegexOptions.IgnoreCase); + var match = regexEXRT.Match(msg); + if(match.Success) { + + byte typeCodeByte = byte.Parse(match.Groups["mc"].Value, NumberStyles.HexNumber); + + this.TypeCode = (PlcType)typeCodeByte; + this.CpuVersion = match.Groups["ver"].Value; + this.HardwareInformation = (HWInformation)byte.Parse(match.Groups["hwif"].Value, NumberStyles.HexNumber); + + return true; + + } + + return false; + + } + + internal static bool TryFromRT (string msg, out PLCInfo inf) { + + var regexRT = new Regex(@"\%EE\$RT(?..)(?..)(?..)(?..)..(?..)(?....).*", RegexOptions.IgnoreCase); + var match = regexRT.Match(msg); + if (match.Success) { + + byte typeCodeByte = byte.Parse(match.Groups["cputype"].Value, NumberStyles.HexNumber); + + inf = new PLCInfo { + TypeCode = (PlcType)typeCodeByte, + CpuVersion = match.Groups["cpuver"].Value, + ProgramCapacity = int.Parse(match.Groups["cap"].Value), + SelfDiagnosticError = match.Groups["sdiag"].Value, + OperationMode = (OPMode)byte.Parse(match.Groups["op"].Value, NumberStyles.HexNumber), + }; + + return true; + + } + + inf = default(PLCInfo); + return false; + + } /// - /// Current station number of the PLC + /// Plc info when its not connected /// - public int StationNumber { get; set; } + public static PLCInfo None => new PLCInfo() { - /// - /// Generates a string containing some of the most important informations - /// - /// - public override string ToString() { + SelfDiagnosticError = "", + CpuVersion = "", + HardwareInformation = 0, + OperationMode = 0, + ProgramCapacity = 0, + TypeCode = 0, + + }; - return $"Type: {CpuInformation.Cputype},\n" + - $"Capacity: {CpuInformation.ProgramCapacity}k\n" + - $"CPU v: {CpuInformation.CpuVersion}\n" + - $"Station Num: {StationNumber}\n" + - $"--------------------------------\n" + - $"OP Mode: {(OperationMode.RunMode ? "Run" : "Prog")}\n" + - $"Error Code: {ErrorCode}"; + /// + public static bool operator == (PLCInfo c1, PLCInfo c2) { + return c1.Equals(c2); + } + /// + public static bool operator != (PLCInfo c1, PLCInfo c2) { + return !c1.Equals(c2); } } diff --git a/MewtocolNet/PLCMode.cs b/MewtocolNet/PLCMode.cs deleted file mode 100644 index 3662b2a..0000000 --- a/MewtocolNet/PLCMode.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; - -namespace MewtocolNet { - - /// - /// All modes - /// - public struct PLCMode { - - /// - /// PLC is running - /// - public bool RunMode { get; set; } - /// - /// PLC is in test - /// - public bool TestRunMode { get; set; } - /// - /// BreakExcecuting - /// - public bool BreakExcecuting { get; set; } - /// - /// BreakValid - /// - public bool BreakValid { get; set; } - /// - /// PLC output is enabled - /// - public bool OutputEnabled { get; set; } - /// - /// PLC runs step per step - /// - public bool StepRunMode { get; set; } - /// - /// Message executing - /// - public bool MessageExecuting { get; set; } - /// - /// PLC is in remote mode - /// - public bool RemoteMode { get; set; } - - /// - /// Gets operation mode from 2 digit hex number - /// - internal static PLCMode BuildFromHex(string _hexString) { - - string lower = Convert.ToString(Convert.ToInt32(_hexString.Substring(0, 1)), 2).PadLeft(4, '0'); - string higher = Convert.ToString(Convert.ToInt32(_hexString.Substring(1, 1)), 2).PadLeft(4, '0'); - string combined = lower + higher; - - var retMode = new PLCMode(); - - for (int i = 0; i < 8; i++) { - char digit = combined[i]; - bool state = false; - if (digit.ToString() == "1") - state = true; - switch (i) { - case 0: - retMode.RunMode = state; - break; - case 1: - retMode.TestRunMode = state; - break; - case 2: - retMode.BreakExcecuting = state; - break; - case 3: - retMode.BreakValid = state; - break; - case 4: - retMode.OutputEnabled = state; - break; - case 5: - retMode.StepRunMode = state; - break; - case 6: - retMode.MessageExecuting = state; - break; - case 7: - retMode.RemoteMode = state; - break; - } - } - - return retMode; - - } - - } - -} \ No newline at end of file diff --git a/MewtocolNet/PublicEnums/HWInformation.cs b/MewtocolNet/PublicEnums/HWInformation.cs new file mode 100644 index 0000000..6089eab --- /dev/null +++ b/MewtocolNet/PublicEnums/HWInformation.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MewtocolNet.PublicEnums { + + /// + /// Contains hardware information about the device as flags + /// + [Flags] + public enum HWInformation : byte { + + /// + /// Has user ROM + /// + UserROM = 1, + /// + /// Has IC card + /// + ICCard = 2, + /// + /// Has general purpose memory + /// + GeneralPurposeMemory = 4, + /// + /// Is CPU ultra high speed type + /// + UltraHighSpeed = 8, + + } + + /// + /// Descibes the operation mode of the device as flags + /// + [Flags] + public enum OPMode : byte { + + /// + /// No operation mode flag active + /// + None = 0, + /// + /// Is in RUN mode, otherwise its PROG Mode + /// + RunMode = 1, + /// + /// Is in test mode, otherwise ok + /// + TestMode = 2, + /// + /// Is BRK/1 step executed + /// + BreakPointPerOneStep = 4, + /// + /// Is BRK command enabled + /// + BreakEnabled = 16, + /// + /// Is outputting to external device + /// + ExternalOutput = 32, + /// + /// Is 1 step exec enabled + /// + OneStepExecEnabled = 64, + /// + /// Is a message displayed? + /// + MessageInstructionDisplayed = 128, + /// + /// Is in remote mode + /// + RemoteMode = 255, + + } + +}