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):
+ AddVariableToDiagram(context, ENodeType.Output)">
+ break;
+ case (EArea.Output):
+ AddVariableToDiagram(context, ENodeType.Input)">
+ break;
+ case (EArea.DataBlock):
+
+ AddVariableToDiagram(context, ENodeType.Input)">Read from
+ AddVariableToDiagram(context, ENodeType.Output)">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 @@
+
+
+