Source gen F functions

This commit is contained in:
Felix Weiß
2023-07-25 16:07:19 +02:00
parent bf78156a9d
commit d24201b5d9
7 changed files with 265 additions and 41 deletions

View File

@@ -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<string, string[]>? ParametersIn { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, string[]>? 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<string, string[]>();
if (functionIns.ParametersOut == null)
functionIns.ParametersOut = new Dictionary<string, string[]>();
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}");
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -125,7 +125,7 @@ namespace MewtocolNet {
_onString = _onString.Replace("\r", "");
var res = new Regex(@"\%([0-9a-fA-F]{2})\$RD(?<data>.*)(?<csum>..)").Match(_onString);
var res = new Regex(@"\%([0-9a-fA-F]{2})\$(?:RD|RP)(?<data>.*)(?<csum>..)").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();
}
/// <summary>
/// Converts a hex string (AB01C1) to a byte array
/// </summary>

View File

@@ -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 {
/// <returns>The success state of the write operation</returns>
Task<bool> RestartProgramAsync();
/// <summary>
/// Reads the program from the connected plc
/// </summary>
/// <returns></returns>
Task<PlcBinaryProgram> ReadProgramAsync();
/// <summary>
/// Factory resets the PLC, this includes the current program
/// and data in the EEPROM

View File

@@ -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<PlcBinaryProgram> ReadProgramAsync () {
var steps = new List<byte[]>();
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,
};
}

View File

@@ -31,10 +31,13 @@
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>AutoTools.DocBuilder</_Parameter1>
</AssemblyAttribute>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>AutoTools.DocBuilder</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>AutoTools.ChmDataExtract</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>

View File

@@ -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<string, string> sysRegisters = new Dictionary<string, string> {
{ "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<string, string> stepFunctions = new Dictionary<string, string> {
{ "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;
}
}
}