From 41f1d6987a61eea1590fdef340bd722ca210b9cc Mon Sep 17 00:00:00 2001 From: "Jasper De Keukelaere (imec)" Date: Tue, 10 Dec 2024 19:04:48 +0100 Subject: [PATCH] feat: add nodegraph init --- CHANGELOG.md | 10 + .../Components/Nodegraph.razor | 83 ++++ .../Components/Nodegraph.razor.css | 9 + .../Components/VariablesTable.razor | 465 ++++++++++++++++++ PLCsimAdvanced_Manager/MainLayout.razor | 6 + .../PLCsimAdvanced_Manager.csproj | 12 +- PLCsimAdvanced_Manager/Pages/Instance.razor | 3 + PLCsimAdvanced_Manager/Pages/_Host.cshtml | 7 + PLCsimAdvanced_Manager/Program.cs | 3 + .../Services/Nodegraph/BaseNodeModel.cs | 19 + .../Nodegraph/InputNode/InputNodeModel.cs | 23 + .../Nodegraph/InputNode/InputNodeWidget.razor | 52 ++ .../InputNode/InputNodeWidget.razor.css | 25 + .../Services/Nodegraph/NodegraphService.cs | 307 ++++++++++++ .../Nodegraph/NodegraphServiceFactory.cs | 19 + .../Nodegraph/OutputNode/OutputNodeModel.cs | 28 ++ .../OutputNode/OutputNodeWidget.razor | 50 ++ .../OutputNode/OutputNodeWidget.razor.css | 25 + .../Nodegraph/PortModels/InputPortModel.cs | 24 + .../Nodegraph/PortModels/OutputPortModel.cs | 23 + README.md | 5 +- docs/img/nodegraph.png | Bin 0 -> 65226 bytes 22 files changed, 1196 insertions(+), 2 deletions(-) create mode 100644 PLCsimAdvanced_Manager/Components/Nodegraph.razor create mode 100644 PLCsimAdvanced_Manager/Components/Nodegraph.razor.css create mode 100644 PLCsimAdvanced_Manager/Components/VariablesTable.razor create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/BaseNodeModel.cs create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeModel.cs create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeWidget.razor create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeWidget.razor.css create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/NodegraphService.cs create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/NodegraphServiceFactory.cs create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeModel.cs create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeWidget.razor create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeWidget.razor.css create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/PortModels/InputPortModel.cs create mode 100644 PLCsimAdvanced_Manager/Services/Nodegraph/PortModels/OutputPortModel.cs create mode 100644 docs/img/nodegraph.png diff --git a/CHANGELOG.md b/CHANGELOG.md index ced6385..e89695c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.3] - 2024-12-10 +This update adds a **NODEGRAPH** functionality to the instance view (Simit-alike). This is a very first beta version for the early adopters. Linking outputs to inputs and datablock values is possible. +![](docs/img/nodegraph.png) + +### Features +- One big feature: nodegraph! + +### Fix +- Version in code now automatically updates when building the app + ## [0.1.2] - 2024-10-10 Minor update, mainly annoying bugfix and some small performance improvements diff --git a/PLCsimAdvanced_Manager/Components/Nodegraph.razor b/PLCsimAdvanced_Manager/Components/Nodegraph.razor new file mode 100644 index 0000000..b8f586d --- /dev/null +++ b/PLCsimAdvanced_Manager/Components/Nodegraph.razor @@ -0,0 +1,83 @@ +@using Blazor.Diagrams +@using Blazor.Diagrams.Core +@using Blazor.Diagrams.Core.PathGenerators +@using Blazor.Diagrams.Core.Routers +@using Blazor.Diagrams.Options +@using Blazor.Diagrams.Components +@using Blazor.Diagrams.Components.Widgets +@using Blazor.Diagrams.Core.Geometry +@using Blazor.Diagrams.Core.Models +@using PLCsimAdvanced_Manager.Services.Nodegraph +@using PLCsimAdvanced_Manager.Services.Nodegraph.InputNode +@using PLCsimAdvanced_Manager.Services.Nodegraph.OutputNode +@inject NodegraphServiceFactory NodegraphServiceFactory + +
+ + + + + + + + + + Start simulation + Stop simulation + + @if (simulationRunning) + { + Stop simulation to change nodegraph + } + else + { + + + + + + + + + + + + + + } +
+ + +@code { + + [Parameter] public string InstanceName { get; set; } + + private NodegraphService _nodegraphService; + private BlazorDiagram Diagram; + + private bool simulationRunning; + + + protected override void OnInitialized() + { + _nodegraphService = NodegraphServiceFactory.GetOrCreateService(InstanceName); + Diagram = _nodegraphService.Diagram; + _nodegraphService.OnSimulationStarted += OnSimulationStarted; + _nodegraphService.OnSimulationStopped += OnSimulationStopped; + simulationRunning = _nodegraphService.IsSimulationRunning; + } + + private void OnSimulationStarted(object sender, EventArgs e) + { + simulationRunning = true; + StateHasChanged(); + } + + private void OnSimulationStopped(object sender, EventArgs e) + { + simulationRunning = false; + StateHasChanged(); + } + + +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Components/Nodegraph.razor.css b/PLCsimAdvanced_Manager/Components/Nodegraph.razor.css new file mode 100644 index 0000000..4f6ea78 --- /dev/null +++ b/PLCsimAdvanced_Manager/Components/Nodegraph.razor.css @@ -0,0 +1,9 @@ +.diagram-container{ + width: 100%; + height: 400px; + border: 1px solid lightgray; +} + +.diagram-container.border-yellow { + border: 4px solid #ff8400; + } diff --git a/PLCsimAdvanced_Manager/Components/VariablesTable.razor b/PLCsimAdvanced_Manager/Components/VariablesTable.razor new file mode 100644 index 0000000..aae0ec1 --- /dev/null +++ b/PLCsimAdvanced_Manager/Components/VariablesTable.razor @@ -0,0 +1,465 @@ +@using Blazor.Diagrams +@using Blazor.Diagrams.Core.Controls.Default +@using Blazor.Diagrams.Core.Geometry +@using PLCsimAdvanced_Manager.Services +@using PLCsimAdvanced_Manager.Services.Nodegraph.InputNode +@using PLCsimAdvanced_Manager.Services.Nodegraph.OutputNode +@using Size = MudBlazor.Size +@using Color = MudBlazor.Color +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@implements IDisposable +@inject ManagerFacade managerFacade + + +@if (SelectedInstance.OperatingState != EOperatingState.Off) +{ + + + Values + + + + + + Name + Type + + + @($"{context.Key}") + + + + @context.Name + + + @context.DataValue.Type + + + @switch (Area) + { + case (EArea.Input): + + break; + case (EArea.Output): + + break; + case (EArea.DataBlock): + + Read from + Write to + + break; + } + + + + No matching records found + + + Not found + + + + + +} +else +{ + Instance not registered +} + +@code { + + [Parameter] public string InstanceName { get; set; } + [Parameter] public EArea Area { get; set; } + [Parameter] public BlazorDiagram Diagram { get; set; } + private IInstance SelectedInstance; + + + private bool InstanceRegistered = false; + + + private SDataValueByName[] _DataValueByNames; + + + protected override async Task OnInitializedAsync() + { + OnSelectInstance(InstanceName); + } + + private void OnSelectInstance(string name) + { + SelectedInstance = SimulationRuntimeManager.CreateInterface(name); + SelectedInstance.UpdateTagList(); + + + if (SelectedInstance.OperatingState != EOperatingState.Off) + { + InstanceRegistered = true; + setDataValueByNames(); + } + } + + + // ------------------------------- + private IEnumerable pagedData; + private MudTable table; + + private int totalItems; + private string searchString = null; + + + private Task> ServerReload(TableState state) + { + var _DataValueByNamesToGet = _DataValueByNames?.Where(element => + { + if (string.IsNullOrWhiteSpace(searchString)) + return true; + if (element.Name.Contains(searchString, StringComparison.OrdinalIgnoreCase)) + return true; + if (element.DataValue.Type.ToString().Contains(searchString, StringComparison.OrdinalIgnoreCase)) + return true; + if ($"{element.Name} {element.DataValue.Type} {element.DataValue.ToString()}".Contains(searchString)) + return true; + return false; + }) + .Where(element => Diagram.Nodes.All(node => node.Title != element.Name)) + .ToArray(); + if (_DataValueByNamesToGet != null) + { + totalItems = _DataValueByNamesToGet.Count(); + + + if (!InstanceRegistered) + { + OnSelectInstance(SelectedInstance.Name); + return Task.FromResult(new TableData()); + } + + + pagedData = _DataValueByNamesToGet.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArray(); + } + + return Task.FromResult(new TableData() { TotalItems = totalItems, Items = pagedData }); + } + + private void OnSearch(string text) + { + searchString = text; + // table.ReloadServerData(); + } + + private void setDataValueByNames() + { + if (SelectedInstance == null) + { + Snackbar.Add("Issue with reading data for the given instance", Severity.Error); + return; + } + + SelectedInstance.UpdateTagList(); + + InvokeAsync(StateHasChanged); + + _DataValueByNames = SelectedInstance.TagInfos.Where(e => e.Area == Area && e.PrimitiveDataType != EPrimitiveDataType.Struct) + .Select(e => new SDataValueByName { Name = e.Name, DataValue = new SDataValue { Type = e.PrimitiveDataType } }) + .ToArray(); + } + + public object parseData(SDataValue dataValue) + { + switch (dataValue.Type) + { + case EPrimitiveDataType.Unspecific: + return "Unspecific type, value not avaliable"; + case EPrimitiveDataType.Struct: + return "Struct type, not implemented"; // not yet implemented for now + case EPrimitiveDataType.Bool: + return dataValue.Bool; + case EPrimitiveDataType.Int8: + return dataValue.Int8; + case EPrimitiveDataType.Int16: + return dataValue.Int16; + case EPrimitiveDataType.Int32: + return dataValue.Int32; + case EPrimitiveDataType.Int64: + return dataValue.Int64; + case EPrimitiveDataType.UInt8: + return dataValue.UInt8; + case EPrimitiveDataType.UInt16: + return dataValue.UInt16; + case EPrimitiveDataType.UInt32: + return dataValue.UInt32; + case EPrimitiveDataType.UInt64: + return dataValue.UInt64; + case EPrimitiveDataType.Float: + return dataValue.Float; + case EPrimitiveDataType.Double: + return dataValue.Double; + case EPrimitiveDataType.Char: + return dataValue.Char; + case EPrimitiveDataType.WChar: + return dataValue.WChar; + default: + throw new ArgumentOutOfRangeException(); + } + } + + + public void AddVariableToDiagram(SDataValueByName dataValueByName, ENodeType nodeType) + { + Point position; + if (Diagram.Nodes.Count == 0) + { + position = new Point(100, 200); + } + else + { + var oldPos = Diagram.Nodes.Last().Position; + position = new Point(oldPos.X + 20, oldPos.Y + 20); + } + + + if (nodeType == ENodeType.Input) + { + AddInputNodeToDiagram(dataValueByName, position); + } + else if (nodeType == ENodeType.Output) + { + AddOutputNodeToDiagram(dataValueByName, position); + } + + + InvokeAsync(StateHasChanged); + table.ReloadServerData(); + } + + public void Dispose() + { + SelectedInstance?.Dispose(); + Snackbar?.Dispose(); + } + + private void AddInputNodeToDiagram(SDataValueByName dataValueByName, Point position) + { + InputNodeModel node; + switch (dataValueByName.DataValue.Type) + { + case EPrimitiveDataType.Unspecific: + break; + case EPrimitiveDataType.Struct: + break; + case EPrimitiveDataType.Bool: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Int8: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Int16: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Int32: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Int64: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.UInt8: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.UInt16: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.UInt32: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.UInt64: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Float: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Double: + node = Diagram.Nodes.Add(new InputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Char: + break; + case EPrimitiveDataType.WChar: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void AddOutputNodeToDiagram(SDataValueByName dataValueByName, Point position) + { + OutputNodeModel node; + switch (dataValueByName.DataValue.Type) + { + case EPrimitiveDataType.Unspecific: + break; + case EPrimitiveDataType.Struct: + break; + case EPrimitiveDataType.Bool: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Int8: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Int16: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Int32: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Int64: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.UInt8: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.UInt16: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.UInt32: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.UInt64: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Float: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Double: + node = Diagram.Nodes.Add(new OutputNodeModel(position: position) + { + Title = dataValueByName.Name + }); + Diagram.Controls.AddFor(node).Add(new RemoveControl(0.5, 0)); + + break; + case EPrimitiveDataType.Char: + break; + case EPrimitiveDataType.WChar: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + + public enum ENodeType + { + Input, + Output + } + +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/MainLayout.razor b/PLCsimAdvanced_Manager/MainLayout.razor index 4298048..457ebcb 100644 --- a/PLCsimAdvanced_Manager/MainLayout.razor +++ b/PLCsimAdvanced_Manager/MainLayout.razor @@ -58,6 +58,12 @@ { _open = !_open; } + + protected override void OnInitialized() + { + base.OnInitialized(); + version = ThisAssembly.Git.BaseTag; + } string version = "0.1.1"; bool newVersionAvailable = false; diff --git a/PLCsimAdvanced_Manager/PLCsimAdvanced_Manager.csproj b/PLCsimAdvanced_Manager/PLCsimAdvanced_Manager.csproj index 593cd8a..bf4dd98 100644 --- a/PLCsimAdvanced_Manager/PLCsimAdvanced_Manager.csproj +++ b/PLCsimAdvanced_Manager/PLCsimAdvanced_Manager.csproj @@ -8,6 +8,7 @@ win-x64 + 12 @@ -16,6 +17,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -25,10 +30,11 @@ + - + .dockerignore @@ -42,6 +48,10 @@ + + <_ContentIncludedByDefault Remove="Services\Nodegraph\InOutNode\InOutNodeWidget.razor" /> + + diff --git a/PLCsimAdvanced_Manager/Pages/Instance.razor b/PLCsimAdvanced_Manager/Pages/Instance.razor index 025f9b4..f72d34f 100644 --- a/PLCsimAdvanced_Manager/Pages/Instance.razor +++ b/PLCsimAdvanced_Manager/Pages/Instance.razor @@ -322,6 +322,9 @@ + + + diff --git a/PLCsimAdvanced_Manager/Pages/_Host.cshtml b/PLCsimAdvanced_Manager/Pages/_Host.cshtml index 74871c6..819e064 100644 --- a/PLCsimAdvanced_Manager/Pages/_Host.cshtml +++ b/PLCsimAdvanced_Manager/Pages/_Host.cshtml @@ -16,6 +16,10 @@ + + + + @@ -37,5 +41,8 @@ + + + \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Program.cs b/PLCsimAdvanced_Manager/Program.cs index 95ae787..279d0fc 100644 --- a/PLCsimAdvanced_Manager/Program.cs +++ b/PLCsimAdvanced_Manager/Program.cs @@ -7,6 +7,7 @@ using MudBlazor.Services; using PLCsimAdvanced_Manager.Services; using PLCsimAdvanced_Manager.Services.Logger; +using PLCsimAdvanced_Manager.Services.Nodegraph; using PLCsimAdvanced_Manager.Services.Persistence; using PLCsimAdvanced_Manager.Shared; @@ -40,6 +41,8 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + //hosted services builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); // TODO -> split hosted services and singleton part diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/BaseNodeModel.cs b/PLCsimAdvanced_Manager/Services/Nodegraph/BaseNodeModel.cs new file mode 100644 index 0000000..d71ef6e --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/BaseNodeModel.cs @@ -0,0 +1,19 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +namespace PLCsimAdvanced_Manager.Services.Nodegraph; + +public abstract class BaseNodeModel(Point position) : NodeModel(position) +{ + public abstract void Calculate(); +} + +public class BaseNodeModel(Point position) : BaseNodeModel(position) +{ + public T? Value { get; set; } + + public override void Calculate() + { + /// + } +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeModel.cs b/PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeModel.cs new file mode 100644 index 0000000..675540c --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeModel.cs @@ -0,0 +1,23 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using PLCsimAdvanced_Manager.Services.Nodegraph.PortModel; + +namespace PLCsimAdvanced_Manager.Services.Nodegraph.InputNode; + +public abstract class InputNodeModel(Point position) : BaseNodeModel(position) +{ +} +public class InputNodeModel : InputNodeModel +{ + public T? Value { get; set; } + + public InputNodeModel(Point position) : base(position) + { + AddPort(new OutputPortModel(this)); + } + + public override void Calculate() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeWidget.razor b/PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeWidget.razor new file mode 100644 index 0000000..bb44755 --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeWidget.razor @@ -0,0 +1,52 @@ +@typeparam T +@using Blazor.Diagrams.Core.Models +@using Blazor.Diagrams.Components.Renderers + +
+
+
@Node.Title
+
+
+ @Node.Value?.GetType() +
+ @foreach (var port in Node.Ports) + { + + } +
+ + +@code { + [Parameter] public InputNodeModel Node { get; set; } + + private string portColor; + + + protected override void OnInitialized() + { + portColor = GetPortColor(); + } + + private string GetPortColor() + { + return typeof(T) switch + { + Type t when t == typeof(bool) => "background-color:#6C757D ", + Type t when t == typeof(byte) => "background-color:#A4C3B2 ", + Type t when t == typeof(sbyte) => "background-color:#C5A880 ", + Type t when t == typeof(short) => "background-color:#B7B8A3 ", + Type t when t == typeof(ushort) => "background-color:#899DA4 ", + Type t when t == typeof(int) => "background-color:#D4A5A5 ", + Type t when t == typeof(uint) => "background-color:#B5CDA3 ", + Type t when t == typeof(long) => "background-color:#A3C9D9 ", + Type t when t == typeof(ulong) => "background-color:#D9C3A3 ", + Type t when t == typeof(float) => "background-color:#A3B9C9 ", + Type t when t == typeof(double) => "background-color:#C9A3B9 ", + Type t when t == typeof(decimal) => "background-color:#A3D9C9 ", + Type t when t == typeof(char) => "background-color:#D9A3A3 ", + Type t when t == typeof(string) => "background-color:#B3B3A3 ", + _ => "background-color:#cccccc" + }; + } + +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeWidget.razor.css b/PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeWidget.razor.css new file mode 100644 index 0000000..aad34fd --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/InputNode/InputNodeWidget.razor.css @@ -0,0 +1,25 @@ +.card{ + width: 80px; + border: 1px solid #ccc; + border-radius: 10px; + padding: 1px; + background-color:#f9f9f9; +} + +.card.selected{ + border-color: #4949bd; + /*background-color: #e0e0ff;*/ +} + +::deep .diagram-port{ + width: 15px; + height: 15px; + background-color: #0a53be; + border-top-left-radius: 50%; + border-top-right-radius: 50%; + border-bottom-left-radius: 50%; + border-bottom-right-radius: 50%; + position: absolute; + right: -7px; + top: 10px; +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/NodegraphService.cs b/PLCsimAdvanced_Manager/Services/Nodegraph/NodegraphService.cs new file mode 100644 index 0000000..2012716 --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/NodegraphService.cs @@ -0,0 +1,307 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Routers; +using Blazor.Diagrams.Options; +using MatBlazor; +using PLCsimAdvanced_Manager.Services.Nodegraph; +using PLCsimAdvanced_Manager.Services.Nodegraph.InputNode; +using PLCsimAdvanced_Manager.Services.Nodegraph.OutputNode; +using PLCsimAdvanced_Manager.Services.Nodegraph.PortModel; +using Siemens.Simatic.Simulation.Runtime; + +namespace PLCsimAdvanced_Manager.Services.Nodegraph; + +public class NodegraphService +{ + public BlazorDiagram Diagram { get; set; } = new BlazorDiagram(new BlazorDiagramOptions() + { + AllowMultiSelection = true, + Zoom = + { + Enabled = false, + }, + Links = + { + DefaultRouter = new NormalRouter(), + DefaultPathGenerator = new SmoothPathGenerator() + }, + }); + + private IInstance SelectedInstance; + private List sorted = new List(); + private HashSet visited = new HashSet(); + private SDataValueByName[] inputnodes; + private SDataValueByName[] outputnodes; + private Timer _timer; + public event EventHandler OnSimulationStarted; + public event EventHandler OnSimulationStopped; + public bool IsSimulationRunning; + + + public NodegraphService(string plcInstanceName) + { + Diagram.RegisterComponent, InputNodeWidget>(); + Diagram.RegisterComponent, InputNodeWidget>(); + Diagram.RegisterComponent, InputNodeWidget>(); + Diagram.RegisterComponent, InputNodeWidget>(); + Diagram.RegisterComponent, InputNodeWidget>(); + Diagram.RegisterComponent, InputNodeWidget>(); + Diagram.RegisterComponent, InputNodeWidget>(); + Diagram.RegisterComponent, InputNodeWidget>(); + Diagram.RegisterComponent, InputNodeWidget>(); + Diagram.RegisterComponent, InputNodeWidget>(); + + Diagram.RegisterComponent, OutputNodeWidget>(); + Diagram.RegisterComponent, OutputNodeWidget>(); + Diagram.RegisterComponent, OutputNodeWidget>(); + Diagram.RegisterComponent, OutputNodeWidget>(); + Diagram.RegisterComponent, OutputNodeWidget>(); + Diagram.RegisterComponent, OutputNodeWidget>(); + Diagram.RegisterComponent, OutputNodeWidget>(); + Diagram.RegisterComponent, OutputNodeWidget>(); + Diagram.RegisterComponent, OutputNodeWidget>(); + Diagram.RegisterComponent, OutputNodeWidget>(); + + SelectedInstance = SimulationRuntimeManager.CreateInterface(plcInstanceName); + + _timer = new Timer(_ => GraphExecution(), null, Timeout.Infinite, 10); + + } + + private void graphCompilation() + { + // remove nodes that have no links + Diagram.Nodes.Where(e => !e.PortLinks.Any()).ToList().ForEach(e => Diagram.Nodes.Remove(e)); + + // Get PLC output~nodegraph input nodes + var nodegraphInputNodes = Diagram.Nodes + .Where(node => node is InputNodeModel) + .ToList(); + var outputDataValueByName = SelectedInstance.TagInfos + .Where(e => (e.Area == EArea.Output || e.Area == EArea.DataBlock) && e.PrimitiveDataType != EPrimitiveDataType.Struct) + .Select(e => new SDataValueByName + { Name = e.Name, DataValue = new SDataValue { Type = e.PrimitiveDataType } }) + .ToArray(); + + inputnodes = nodegraphInputNodes + .Select(node => outputDataValueByName.FirstOrDefault(data => data.Name == node.Title)) + .ToArray(); + + // PLC input~nodegraph output nodes + var nodegraphOutputNodes = Diagram.Nodes + .Where(node => node is OutputNodeModel) + .ToList(); + var inputDataValueByName = SelectedInstance.TagInfos + .Where(e => (e.Area == EArea.Input || e.Area == EArea.DataBlock) && e.PrimitiveDataType != EPrimitiveDataType.Struct) + .Select(e => new SDataValueByName + { Name = e.Name, DataValue = new SDataValue { Type = e.PrimitiveDataType } }) + .ToArray(); + outputnodes = nodegraphOutputNodes + .Select(node => inputDataValueByName.FirstOrDefault(data => data.Name == node.Title)) + .ToArray(); + + foreach (var node in nodegraphInputNodes) + { + visited.Add(node.Id); + } + + foreach (var node in Diagram.Nodes) + { + Visit(node as BaseNodeModel); + } + } + + private void Visit(BaseNodeModel node) + { + if (visited.Contains(node.Id)) + return; + + visited.Add(node.Id); + var inputPorts = node.Ports.OfType().ToList(); + foreach (var dependency in inputPorts) + { + var sourceNode = Diagram.Links[0].Source as SinglePortAnchor; + Visit(sourceNode.Port.Parent as BaseNodeModel); + } + + sorted.Add(node); + } + + private void GraphExecution() + { + // 1. read input nodes + // 2. execute graph + // 3. write output nodes + + SelectedInstance.ReadSignals(ref inputnodes); + foreach (var inputnode in inputnodes) + { + var node = Diagram.Nodes.FirstOrDefault(e => e.Title == inputnode.Name); + if (node is BaseNodeModel baseNode) + { + if (baseNode is InputNodeModel boolNode) + { + boolNode.Value = inputnode.DataValue.Bool; + } + else if (baseNode is InputNodeModel byteNode) + { + byteNode.Value = inputnode.DataValue.UInt8; + } + else if (baseNode is InputNodeModel shortNode) + { + shortNode.Value = inputnode.DataValue.Int16; + } + else if (baseNode is InputNodeModel intNode) + { + intNode.Value = inputnode.DataValue.Int32; + } + else if (baseNode is InputNodeModel longNode) + { + longNode.Value = inputnode.DataValue.Int64; + } + else if (baseNode is InputNodeModel ushortNode) + { + ushortNode.Value = inputnode.DataValue.UInt16; + } + else if (baseNode is InputNodeModel uintNode) + { + uintNode.Value = inputnode.DataValue.UInt32; + } + else if (baseNode is InputNodeModel ulongNode) + { + ulongNode.Value = inputnode.DataValue.UInt64; + } + else if (baseNode is InputNodeModel floatNode) + { + floatNode.Value = inputnode.DataValue.Float; + } + else if (baseNode is InputNodeModel doubleNode) + { + doubleNode.Value = inputnode.DataValue.Double; + } + else + { + throw new InvalidOperationException($"Unsupported type: {baseNode.GetType()}"); + } + } + } + + foreach (var node in sorted) + { + var baseNode = node as BaseNodeModel; + baseNode.Calculate(); + // node. calculate values + } + + for (int i = 0; i < outputnodes.Length; i++) + { + var node = Diagram.Nodes.FirstOrDefault(e => e.Title == outputnodes[i].Name); + if (node is BaseNodeModel baseNode) + { + if (baseNode is OutputNodeModel boolNode) + { + outputnodes[i].DataValue.Bool = boolNode.Value; + } + else if (baseNode is OutputNodeModel byteNode) + { + outputnodes[i].DataValue.UInt8 = byteNode.Value; + } + else if (baseNode is OutputNodeModel shortNode) + { + outputnodes[i].DataValue.Int16 = shortNode.Value; + } + else if (baseNode is OutputNodeModel intNode) + { + outputnodes[i].DataValue.Int32 = intNode.Value; + } + else if (baseNode is OutputNodeModel longNode) + { + outputnodes[i].DataValue.Int64 = longNode.Value; + } + else if (baseNode is OutputNodeModel ushortNode) + { + outputnodes[i].DataValue.UInt16 = ushortNode.Value; + } + else if (baseNode is OutputNodeModel uintNode) + { + outputnodes[i].DataValue.UInt32 = uintNode.Value; + } + else if (baseNode is OutputNodeModel ulongNode) + { + outputnodes[i].DataValue.UInt64 = ulongNode.Value; + } + else if (baseNode is OutputNodeModel floatNode) + { + outputnodes[i].DataValue.Float = floatNode.Value; + } + else if (baseNode is OutputNodeModel doubleNode) + { + outputnodes[i].DataValue.Double = doubleNode.Value; + } + else + { + throw new InvalidOperationException($"Unsupported type: {baseNode.GetType()}"); + } + } + } + + SelectedInstance.WriteSignals(ref outputnodes); + } + + private void LockDiagram() + { + Diagram.Nodes.ToList().ForEach(node => node.Locked = true); + Diagram.Links.ToList().ForEach(link => link.Locked = true); + } + + private void UnlockDiagram() + { + Diagram.Nodes.ToList().ForEach(node => node.Locked = false); + Diagram.Links.ToList().ForEach(link => link.Locked = false); + } + + public void ExecuteSimulation() + { + graphCompilation(); + if (Diagram.Nodes.Count==0) + { + return; + } + + + LockDiagram(); + PlcStartSetup(); + _timer.Change(0, 100); + IsSimulationRunning = true; + OnSimulationStarted?.Invoke(this, EventArgs.Empty); + } + + public void StopSimulation() + { + _timer.Change(Timeout.Infinite, Timeout.Infinite); + UnlockDiagram(); + IsSimulationRunning = false; + OnSimulationStopped?.Invoke(this, EventArgs.Empty); + } + + public void Dispose() + { + _timer?.Dispose(); + } + + private async void PlcStartSetup() + { + if (SelectedInstance.OperatingState == EOperatingState.Off) + { + SelectedInstance.PowerOn(); + SelectedInstance.Run(); + } + else if (SelectedInstance.OperatingState == EOperatingState.Stop) + { + SelectedInstance.Run(); + } + } +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/NodegraphServiceFactory.cs b/PLCsimAdvanced_Manager/Services/Nodegraph/NodegraphServiceFactory.cs new file mode 100644 index 0000000..964ce51 --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/NodegraphServiceFactory.cs @@ -0,0 +1,19 @@ +using System.Collections.Concurrent; +using Blazor.Diagrams.Core; + +namespace PLCsimAdvanced_Manager.Services.Nodegraph; + +public class NodegraphServiceFactory +{ + private readonly ConcurrentDictionary _services = new ConcurrentDictionary(); + + public NodegraphService GetOrCreateService(string plcInstanceName) + { + return _services.GetOrAdd(plcInstanceName, id => new NodegraphService(id)); + } + + public void RemoveService(string plcInstanceName) + { + _services.TryRemove(plcInstanceName, out _); + } +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeModel.cs b/PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeModel.cs new file mode 100644 index 0000000..6d97b9e --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeModel.cs @@ -0,0 +1,28 @@ +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using PLCsimAdvanced_Manager.Services.Nodegraph.InputNode; +using PLCsimAdvanced_Manager.Services.Nodegraph.PortModel; + +namespace PLCsimAdvanced_Manager.Services.Nodegraph.OutputNode; + +public abstract class OutputNodeModel(Point position) : BaseNodeModel(position) +{ +} +public class OutputNodeModel : OutputNodeModel +{ + public T? Value { get; set; } + + public OutputNodeModel(Point position) : base(position) + { + AddPort(new InputPortModel(this)); + } + + public override void Calculate() + { + var source = PortLinks.First().Source as SinglePortAnchor; + if (source == null) + return; + Value = (source.Port.Parent as InputNodeModel).Value; + + } +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeWidget.razor b/PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeWidget.razor new file mode 100644 index 0000000..869ed3a --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeWidget.razor @@ -0,0 +1,50 @@ +@typeparam T +@using Blazor.Diagrams.Core.Models +@using Blazor.Diagrams.Components.Renderers + +
+
+
@Node.Title
+
+
+ @Node.Value?.GetType() +
+ @foreach (var port in Node.Ports) + { + + } +
+ + +@code { + [Parameter] public OutputNodeModel Node { get; set; } + private string portColor; + + protected override void OnInitialized() + { + portColor = GetPortColor(); + } + + private string GetPortColor() + { + return typeof(T) switch + { + Type t when t == typeof(bool) => "background-color:#6C757D ", + Type t when t == typeof(byte) => "background-color:#A4C3B2 ", + Type t when t == typeof(sbyte) => "background-color:#C5A880 ", + Type t when t == typeof(short) => "background-color:#B7B8A3 ", + Type t when t == typeof(ushort) => "background-color:#899DA4 ", + Type t when t == typeof(int) => "background-color:#D4A5A5 ", + Type t when t == typeof(uint) => "background-color:#B5CDA3 ", + Type t when t == typeof(long) => "background-color:#A3C9D9 ", + Type t when t == typeof(ulong) => "background-color:#D9C3A3 ", + Type t when t == typeof(float) => "background-color:#A3B9C9 ", + Type t when t == typeof(double) => "background-color:#C9A3B9 ", + Type t when t == typeof(decimal) => "background-color:#A3D9C9 ", + Type t when t == typeof(char) => "background-color:#D9A3A3 ", + Type t when t == typeof(string) => "background-color:#B3B3A3 ", + _ => "background-color:#cccccc" + }; + } + +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeWidget.razor.css b/PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeWidget.razor.css new file mode 100644 index 0000000..3adcf3a --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/OutputNode/OutputNodeWidget.razor.css @@ -0,0 +1,25 @@ +.card{ + width: 80px; + border: 1px solid #ccc; + border-radius: 10px; + padding: 1px; + background-color:#f9f9f9; +} + +.card.selected{ + border-color: #4949bd; + /*background-color: #e0e0ff;*/ +} + +::deep .diagram-port{ + width: 15px; + height: 15px; + background-color: #0a53be; + border-top-left-radius: 50%; + border-top-right-radius: 50%; + border-bottom-left-radius: 50%; + border-bottom-right-radius: 50%; + position: absolute; + left: -7px; + top: 10px; +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/PortModels/InputPortModel.cs b/PLCsimAdvanced_Manager/Services/Nodegraph/PortModels/InputPortModel.cs new file mode 100644 index 0000000..4131b57 --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/PortModels/InputPortModel.cs @@ -0,0 +1,24 @@ +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace PLCsimAdvanced_Manager.Services.Nodegraph.PortModel; + +public abstract class InputPortModel(NodeModel parent) + : Blazor.Diagrams.Core.Models.PortModel(parent, PortAlignment.Left, null, null); + +public class InputPortModel(NodeModel parent): Blazor.Diagrams.Core.Models.PortModel(parent, PortAlignment.Left, null, null) +{ + public T? Value { get; set; } + + + + public override bool CanAttachTo(ILinkable other) + { + if (!base.CanAttachTo(other)) + return false; + if (Links.Count>0) + return false; + + return other is OutputPortModel; + } +} \ No newline at end of file diff --git a/PLCsimAdvanced_Manager/Services/Nodegraph/PortModels/OutputPortModel.cs b/PLCsimAdvanced_Manager/Services/Nodegraph/PortModels/OutputPortModel.cs new file mode 100644 index 0000000..56db6ea --- /dev/null +++ b/PLCsimAdvanced_Manager/Services/Nodegraph/PortModels/OutputPortModel.cs @@ -0,0 +1,23 @@ +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace PLCsimAdvanced_Manager.Services.Nodegraph.PortModel; + +public abstract class OutputPortModel(NodeModel parent) + : Blazor.Diagrams.Core.Models.PortModel(parent, PortAlignment.Right, null, null); +public class OutputPortModel(NodeModel paretn): Blazor.Diagrams.Core.Models.PortModel(paretn, PortAlignment.Right, null, null) +{ + public T? Value { get; set; } + + public override bool CanAttachTo(ILinkable other) + { + if (!base.CanAttachTo(other)) + return false; + + if (other.Links.Count>0) + return false; + + return other is InputPortModel; + } + +} \ No newline at end of file diff --git a/README.md b/README.md index 15b38c2..11c8a48 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,10 @@ Features: - [x] **Advanced network setting**. Being able to set every interface of the instance to an interface of the host machine - [x] Create **snapshots** and restore them - [x] Option for **auto start** instances on startup of PLCsim Advanced Manager. Either just register or completely start the instance when starting the manager application. (see instance settings) +- [x] A **nodegraph** to link variables and do basic virtual commissioning + Future feature ideas: -- [ ] Easy **virtual commissioning** by e.g. setting buttons and lights in the UI to the PLC variables - [ ] **Traces** on the variables for analysis - [ ] **Desktop version** so no webbrowser is needed for a local setup - [ ] Connect to **remote PLCsim Advance APIs** @@ -35,6 +36,8 @@ Future feature ideas: ![](docs/img/instancePage.png) +![](docs/img/nodegraph.png) + # Quickstart diff --git a/docs/img/nodegraph.png b/docs/img/nodegraph.png new file mode 100644 index 0000000000000000000000000000000000000000..377895bc4700f7012146cc9c87b943a4792752d2 GIT binary patch literal 65226 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 Qqz<#{T@A$&g&%(YUp}RW)&Kwi literal 0 HcmV?d00001