diff --git a/AutoTools.ChmDataExtract/Program.cs b/AutoTools.ChmDataExtract/Program.cs index 7f1dcae..9dd635d 100644 --- a/AutoTools.ChmDataExtract/Program.cs +++ b/AutoTools.ChmDataExtract/Program.cs @@ -1,11 +1,13 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Reflection; +using System.Runtime.Serialization.Formatters.Binary; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using HtmlAgilityPack; using MewtocolNet; +using MewtocolNet.Helpers; namespace AutoTools.ChmDataExtract; @@ -120,6 +122,12 @@ internal class Program { public string Description { get; set; } = null!; + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary? ParametersIn { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary? ParametersOut { get; set; } + } static void Main(string[] args) => Task.Run(AsyncMain).Wait(); @@ -176,7 +184,7 @@ internal class Program { FPFunction functionIns = new FPFunction(); - //Console.Write($"Var: {rowName, -50}"); + Console.Write($"Var: {rowName, -20}"); var href = itemRow?.GetAttributeValue("href", null); @@ -190,6 +198,75 @@ internal class Program { var noteSection = docSub.DocumentNode.SelectSingleNode("//section/div[contains(@class,'note note')]"); var xrefRedundant = noteSection?.SelectSingleNode("p/a[contains(@class,'xref')]"); var xrefNodeContent = noteSection?.SelectSingleNode("p/span"); + + //get params in / out + var inOutDefinitionNodes = docSub.DocumentNode.SelectNodes("//p[contains(@class,'p inoutput')]"); + + if (inOutDefinitionNodes != null) { + + foreach (var ioTypeNode in inOutDefinitionNodes) { + + var nodeInOutType = ioTypeNode.InnerText.SanitizeLinebreakFormatting(); + + Console.Write($"{nodeInOutType}: "); + + var currentSibling = ioTypeNode; + + while (true) { + + if (currentSibling.NextSibling == null) break; + currentSibling = currentSibling.NextSibling; + + if (currentSibling.HasClass("inoutput")) { + break; + } + + var paramNodes = currentSibling.SelectNodes("dt"); + + if (paramNodes == null) continue; + + foreach (var paramNode in paramNodes) { + + var paramName = paramNode.SelectSingleNode("span[1]")?.InnerText?.SanitizeBracketFormatting(); + var paramTypes = paramNode.SelectSingleNode("span[2]")?.InnerText?.SanitizeBracketFormatting(); + + if (paramName != null && paramTypes != null) { + + if (functionIns.ParametersIn == null) + functionIns.ParametersIn = new Dictionary(); + + if (functionIns.ParametersOut == null) + functionIns.ParametersOut = new Dictionary(); + + Console.Write($"{paramName} {paramTypes}"); + + var splitParamNames = paramName.Split(", "); + + foreach (var splitName in splitParamNames) { + + if (nodeInOutType == "Input") { + + if (functionIns.ParametersIn.ContainsKey(splitName)) break; + functionIns.ParametersIn.Add(splitName, paramTypes.SanitizeBracketFormatting().Split(", ")); + + } else { + + if (functionIns.ParametersOut.ContainsKey(splitName)) break; + functionIns.ParametersOut.Add(splitName, paramTypes.SanitizeBracketFormatting().Split(", ")); + + } + + } + + } + + } + + } + + } + + } HtmlNode? descrSection = null; @@ -213,7 +290,7 @@ internal class Program { if(descrText != null) { - descrText = descrText.Replace("\r", "").Replace("\n", "").Trim(); + descrText = descrText.SanitizeLinebreakFormatting(); functionIns.Description = descrText; @@ -225,7 +302,8 @@ internal class Program { } - functions.Add(rowName, functionIns); + functions.Add(rowName, functionIns); + Console.WriteLine(); //compatibility matrix //for (int i = 1; i < columns?.Count - 1; i++) { @@ -242,7 +320,8 @@ internal class Program { var funcsJson = JsonSerializer.Serialize(functions, new JsonSerializerOptions { WriteIndented = true }); - Console.WriteLine(funcsJson); + File.WriteAllText("./function_names.json", funcsJson); + } @@ -346,12 +425,6 @@ internal class Program { } - foreach (var item in addressExceptions.DistinctBy(x => x.ExceptionTitle)) { - - Console.WriteLine($"{item.ForSysRegister} - {item.ExceptionTitle}"); - - } - } } diff --git a/Examples.ProgramReadWrite/Program.cs b/Examples.ProgramReadWrite/Program.cs index 3375e1a..2cf4381 100644 --- a/Examples.ProgramReadWrite/Program.cs +++ b/Examples.ProgramReadWrite/Program.cs @@ -1,10 +1,30 @@ -namespace Examples.ProgramReadWrite; +using MewtocolNet; +using MewtocolNet.Logging; + +namespace Examples.ProgramReadWrite; internal class Program { - - static void Main(string[] args) { - MewtocolNet.ProgramParsing.PlcBinaryProgram.ParseFromFile(@"C:\Users\feli1\Documents\Test\prog4.fp").AnalyzeProgram(); + static void Main(string[] args) => Task.Run(AsyncMain).Wait(); + + //MewtocolNet.ProgramParsing.PlcBinaryProgram.ParseFromFile(@"C:\Users\fwe\Documents\sps\FPXH_C30_Test1.fp").AnalyzeProgram(); + + static async Task AsyncMain () { + + Logger.LogLevel = LogLevel.Error; + + using (var plc = Mewtocol.Ethernet("192.168.115.210").Build()) { + + await plc.ConnectAsync(); + var prog = await plc.ReadProgramAsync(); + + if (prog != null) { + + prog.AnalyzeProgram(); + + } + + } } diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index e6af4e6..2466c8c 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -125,7 +125,7 @@ namespace MewtocolNet { _onString = _onString.Replace("\r", ""); - var res = new Regex(@"\%([0-9a-fA-F]{2})\$RD(?.*)(?..)").Match(_onString); + var res = new Regex(@"\%([0-9a-fA-F]{2})\$(?:RD|RP)(?.*)(?..)").Match(_onString); if (res.Success) { string val = res.Groups["data"].Value; @@ -159,7 +159,7 @@ namespace MewtocolNet { } - public static string Ellipsis(this string str, int maxLength) { + internal static string Ellipsis(this string str, int maxLength) { if (string.IsNullOrEmpty(str) || str.Length <= maxLength) return str; @@ -168,6 +168,20 @@ namespace MewtocolNet { } + internal static string SanitizeLinebreakFormatting (this string str) { + + str = str.Replace("\r", "").Replace("\n", "").Trim(); + + return Regex.Replace(str, @"\s+", " "); + + } + + internal static string SanitizeBracketFormatting(this string str) { + + return str.Replace("(", "").Replace(")", "").Trim(); + + } + /// /// Converts a hex string (AB01C1) to a byte array /// diff --git a/MewtocolNet/IPlc.cs b/MewtocolNet/IPlc.cs index e1015f5..2697ec4 100644 --- a/MewtocolNet/IPlc.cs +++ b/MewtocolNet/IPlc.cs @@ -1,4 +1,5 @@ -using MewtocolNet.RegisterBuilding; +using MewtocolNet.ProgramParsing; +using MewtocolNet.RegisterBuilding; using MewtocolNet.Registers; using System; using System.Collections.Generic; @@ -97,6 +98,12 @@ namespace MewtocolNet { /// The success state of the write operation Task RestartProgramAsync(); + /// + /// Reads the program from the connected plc + /// + /// + Task ReadProgramAsync(); + /// /// Factory resets the PLC, this includes the current program /// and data in the EEPROM diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 7e32e71..5c15481 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -1,8 +1,10 @@ using MewtocolNet.Logging; +using MewtocolNet.ProgramParsing; using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; +using System.Text; using System.Threading.Tasks; namespace MewtocolNet { @@ -109,8 +111,54 @@ namespace MewtocolNet { await SetOperationModeAsync(false); //reset plc - await SendCommandAsync($"%EE#0F"); - await SendCommandAsync($"%EE#21"); + await SendCommandAsync($"%{GetStationNumber()}#0F"); + await SendCommandAsync($"%{GetStationNumber()}#21"); + + } + + public async Task ReadProgramAsync () { + + var steps = new List(); + + int i = 0; + int stepsPerReq = 50; + while(i < int.MaxValue) { + + var sb = new StringBuilder($"%{GetStationNumber()}#RP"); + var stp1 = (i * stepsPerReq); + var stp2 = ((i + 1) * stepsPerReq) - 1; + + sb.Append(stp1.ToString().PadLeft(5, '0')); + sb.Append(stp2.ToString().PadLeft(5, '0')); + + var res = await SendCommandAsync(sb.ToString()); + + if (res.Success) { + + var bytes = res.Response.ParseDTRawStringAsBytes(); + var foundEndPattern = bytes.SearchBytePattern(new byte[] { 0xFA, 0xF8, 0xFF, 0xFF }); + + for (int j = 0; j < bytes.Length; j += 2) { + var split = bytes.Skip(j).Take(2).ToArray(); + if (split[0] == 0xFF && split[1] == 0xFF) break; + steps.Add(split); + } + + if (foundEndPattern != -1) { + + break; + + } + + } + + i++; + + } + + return new PlcBinaryProgram { + rawSteps = steps, + }; } diff --git a/MewtocolNet/MewtocolNet.csproj b/MewtocolNet/MewtocolNet.csproj index cca51a0..b7d1b09 100644 --- a/MewtocolNet/MewtocolNet.csproj +++ b/MewtocolNet/MewtocolNet.csproj @@ -31,10 +31,13 @@ - - - <_Parameter1>AutoTools.DocBuilder - + + + <_Parameter1>AutoTools.DocBuilder + + + <_Parameter1>AutoTools.ChmDataExtract + diff --git a/MewtocolNet/ProgramParsing/PlcBinaryProgram.cs b/MewtocolNet/ProgramParsing/PlcBinaryProgram.cs index 7688461..fd31e56 100644 --- a/MewtocolNet/ProgramParsing/PlcBinaryProgram.cs +++ b/MewtocolNet/ProgramParsing/PlcBinaryProgram.cs @@ -3,6 +3,7 @@ using System; using System.IO; using System.Collections.Generic; using System.Linq; +using System.Globalization; namespace MewtocolNet.ProgramParsing { @@ -15,18 +16,35 @@ namespace MewtocolNet.ProgramParsing { { 0xFAF8, "ED" }, { 0xFDF8, "RET" }, //return { 0xF6F8, "CALL" }, - + { 0xEEF8, "SET" }, + { 0x21CC, "OT R501" }, - { 0x21AC, "ST R501" }, - { 0xF7FF, "ST R9010" }, + //{ 0x21AC, "ST R501" }, + //{ 0xF7FF, "ST R9010" }, }; - // ST R50_1 21 AC => WR system area start was set to 50 - // ST R57_1 91 AC => WR system area start was set to 57 - // ST R901_3 F7 FF 53 A9 - // ST R901_C F7 FF 5C A9 - // AN/ R900_B F7 FF 4B 49 + static readonly Dictionary sysRegisters = new Dictionary { + { "R9009", "sys_bIsCarry" }, + { "R900A", "sys_bIsGreaterThan" }, + { "R900B", "sys_bIsEqual" }, + { "R900C", "sys_bIsLessThan" }, + { "R900D", "sys_bIsAuxiliaryTimerElapsed" }, + + { "R9010", "sys_bTrue" }, + { "R9011", "sys_bFalse" }, + { "R9012", "sys_bScanPulse" }, + { "R9013", "sys_bIsFirstScan" }, + { "R9014", "sys_bIsNotFirstScan" }, + { "R9015", "sys_bIsFirstScanOfSfcStep" }, + { "R9018", "sys_bPulse10ms" }, + { "R9019", "sys_bPulse20ms" }, + { "R901A", "sys_bPulse100ms" }, + { "R901B", "sys_bPulse200ms" }, + { "R901C", "sys_bPulse1s" }, + { "R901D", "sys_bPulse2s" }, + { "R901E", "sys_bPulse1min" }, + }; static readonly Dictionary stepFunctions = new Dictionary { { "F0", "MV" }, @@ -36,10 +54,15 @@ namespace MewtocolNet.ProgramParsing { { "F5", "BTM" }, { "F8", "DMV2" }, { "F11", "COPY" }, - { "F61", "DCMP" }, - { "F246", "CALL" }, + { "F61", "DCMP" } }; + // ST R50_1 21 AC => WR system area start was set to 50 + // ST R57_1 91 AC => WR system area start was set to 57 + // ST R901_3 F7 FF 53 A9 + // ST R901_C F7 FF 5C A9 + // AN/ R900_B F7 FF 4B 49 + const int STEP_BYTE_LEN = 2; const int PROG_AUTHOR_INDEX = 57; @@ -105,27 +128,57 @@ namespace MewtocolNet.ProgramParsing { Console.Write($"{i,3} => {stepBytesString} "); var stepKey = BitConverter.ToUInt16(step.Reverse().ToArray(), 0); + byte[] nextStep = null; + + if(i + 1 < rawSteps.Count - 1) + nextStep = rawSteps[i + 1]; if (stepCommands.ContainsKey(stepKey)) { Console.Write($"{stepCommands[stepKey]}"); - } else if (stepKey == 0xFFF8) { + } else if (nextStep != null && step[0] == 0xF7 && step[1] == 0xFF && nextStep[1] == 0xA9) { - //custom function that goes over FF, the F instruction number is calced by - //the next step first byte plus 190 + //ST step + + var area = nextStep[0].ToString("X2").Substring(0, 1); + var specialArea = nextStep[0].ToString("X2").Substring(1, 1); - var nextStep = rawSteps[i + 1]; - var stepID = nextStep[0] + 190; + var stepID = 896 + int.Parse(area, NumberStyles.HexNumber); + var stCondition = $"R{stepID}{specialArea}"; - string funcName = GetFunctionName($"F{stepID}"); - Console.Write(funcName); + Console.Write($"ST {GetSysRegisterName(stCondition)}"); + + } else if (nextStep != null && step[0] == 0xF7 && step[1] == 0xFF && nextStep[1] == 0x49) { + + //AN/ step + + var area = nextStep[0].ToString("X2").Substring(0, 1); + var specialArea = nextStep[0].ToString("X2").Substring(1, 1); + + var stepID = 896 + int.Parse(area, NumberStyles.HexNumber); + var stCondition = $"R{stepID}{specialArea}"; + + Console.Write($"AN/ {GetSysRegisterName(stCondition)}"); } else if (step[1] == 0xF8) { - string funcName = GetFunctionName($"F{step[0]}"); + int functionID = 0; + + if (step[0] == 0xFF) { + //custom function that goes over FF, the F instruction number is calced by + //the next step first byte plus 190 + functionID = nextStep[0] + 190; + } else { + functionID = step[0]; + } + + string funcName = GetFunctionName($"F{functionID}"); Console.Write(funcName); + //get the params and outs of the function + + } //if (stepBytesString.StartsWith("F")) { @@ -147,6 +200,12 @@ namespace MewtocolNet.ProgramParsing { } + private string GetSysRegisterName (string fpAddress) { + + return sysRegisters.ContainsKey(fpAddress) ? $"{fpAddress} ({sysRegisters[fpAddress]})" : fpAddress; + + } + } }