Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
run: dotnet build cake/Build.csproj

- name: "Run cake script"
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'false' }}
env:
GH_TOKEN : ${{ secrets.GH_TOKEN }}
run: dotnet run --project cake/Build.csproj --do-test --do-pack --test-level 2 --framework net9.0
Expand All @@ -47,4 +48,4 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GH_USER: ${{ secrets.GH_USER }}
run: dotnet run --project cake/Build.csproj --do-publish-only --do-publish --do-publish-release --framework net9.0
run: dotnet run --project cake/Build.csproj --do-test --do-pack --test-level 2 --do-publish-only --do-publish --do-publish-release --framework net9.0
2 changes: 1 addition & 1 deletion build.ps1
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# run build
dotnet run --project cake/Build.csproj --framework net9.0 -- $args
exit $LASTEXITCODE;vm43001Jetpow3r
exit $LASTEXITCODE;
3 changes: 0 additions & 3 deletions cake/BuildParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,4 @@ public class BuildParameters

[Option('r', "do-publish-release", Required = false, Default = false, HelpText = "Publishes release on GH")]
public bool DoPublishRelease { get; set; }

[Option('o', "do-publish-only", Required = false, Default = false, HelpText = "Perfoms only publishing tasks from previously created artefacts.")]
public bool DoPublishOnly { get; set; }
}
31 changes: 0 additions & 31 deletions cake/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,6 @@ public sealed class CleanUpTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
if(context.BuildParameters.DoPublishOnly)
{
context.Log.Warning($"Skipping. Preforming publish only");
return;
}

context.DotNetClean(Path.Combine(context.ScrDir, "AXSharp.sln"), new DotNetCleanSettings() { Verbosity = context.BuildParameters.Verbosity });
context.CleaUpAllBinsAndObjs();
context.CleanDirectory(context.Artifacts);
Expand All @@ -84,12 +78,6 @@ public sealed class ProvisionTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
if (context.BuildParameters.DoPublishOnly)
{
context.Log.Warning($"Skipping. Preforming publish only");
return;
}

context.DotNetBuildSettings.MSBuildSettings.Properties.Add("NoWarn", new List<string>()
{ "1234;2345;8602;10012;8618;0162;8605;1416;3270;1504;8600;8618;" +
"CS0618;CS1591;BL0007;BL0005;CA1416;CA2200;CS0105;CS0108;CS0109;CS0162;CS0168;CS0169;CS219;CS0414;CS0436;CS0472;CS0618;CS1591;CS1998;CS8604;" +
Expand Down Expand Up @@ -132,13 +120,6 @@ public sealed class BuildTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{

if (context.BuildParameters.DoPublishOnly)
{
context.Log.Warning($"Skipping. Preforming publish only");
return;
}

context.DotNetBuild(Path.Combine(context.ScrDir, "AXSharp.compiler\\src\\ixc\\AXSharp.ixc.csproj"), context.DotNetBuildSettings);

var axprojects = new List<string>()
Expand Down Expand Up @@ -173,12 +154,6 @@ public sealed class TestsTask : FrostingTask<BuildContext>
// Tasks can be asynchronous
public override void Run(BuildContext context)
{
if (context.BuildParameters.DoPublishOnly)
{
context.Log.Warning($"Skipping. Preforming publish only");
return;
}

if (!context.BuildParameters.DoTest)
{
context.Log.Warning($"Skipping tests");
Expand Down Expand Up @@ -241,12 +216,6 @@ public sealed class CreateArtifactsTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
if (context.BuildParameters.DoPublishOnly)
{
context.Log.Warning($"Skipping. Preforming publish only");
return;
}

if (!context.BuildParameters.DoPack)
{
context.Log.Warning($"Skipping packaging.");
Expand Down
5 changes: 4 additions & 1 deletion cake/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
"commandName": "WSL2",
"distributionName": ""
},
"build": {
"commandName": "Project"
},
"PublishOnly": {
"commandName": "Project",
"commandLineArgs": "--do-publish-only --do-publish --do-publish-release",
"workingDirectory": "C:\\W\\Develop\\gh\\inxton\\ax\\axsharp\\cake"
}
}
}
}
25 changes: 12 additions & 13 deletions docfx/articles/blazor/LIBRARIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,34 +181,33 @@ Note: viewmodel properties and variables can be accessed with inherited `ViewMod

## Optimizing PLC Data Polling

The `RenderableComponentBase` class contains an overridable method, `AddToPolling`, which is primarily tasked with adding elements to the polling queue. However, given the automatic subscription of all inner elements within a given object to the polling queue by default, it becomes imperative to manage this operation more precisely, especially when dealing with large objects.
The `RenderableComponentBase` class contains an overridable method, `ConfigurePolling`, which is tasked with adding elements to the polling queue and **must be overridden** in derived components. By default, no polling is activated; it must be explicitly stated which primitives or twin objects should be added to the polling queue.

Overriding this method in derived classes allows for the customization of how many and which elements are added to the polling queue. This capability is crucial for controlling resources and optimizing performance by preventing the automatic addition of potentially large numbers of elements to the polling queue.

Whenever the `RenderableComponent` is disposed, the polling for that component is automatically released in the `Dispose` method.

>[!IMPORTANT]
>Whenever the `OnInitialized`, `OnInitializedAsync`, or `Dispose` methods are overridden, it is imperative that `base` is called to ensure the proper sequence of polling management.


Here is an example where the overridden method ensures that no elements are added to the polling queue:

```C#
public override void AddToPolling(ITwinElement element, int pollingInterval = 250)
public override void ConfigurePolling()
{
// Overriding with an empty method ensures no elements are added to the polling queue.
}
```

Contrastingly, in the following example, the overridden method only subscribes first-level primitive elements for polling, thus creating a more resource-efficient application:

```C#
public override void AddToPolling(ITwinElement element, int pollingInterval = 250)
public override void ConfigurePolling()
{
var sequencer = (AxoSequencer)element;
var firstLevelPrimitives = sequencer.GetValueTags().ToList();

firstLevelPrimitives.ForEach(p =>
{
p.StartPolling(pollingInterval, this);
PolledElements.Add(p);
});
this.StartPolling(this.Component.Counter, 1500);
this.StartPolling(this.Component.Counter2, 250);
}
```

This strategy allows for a more efficient use of resources by reducing the load of polled elements on the system.

For more details about polling [General Polling](../connectors/README.md#polling).
Expand Down
8 changes: 6 additions & 2 deletions docfx/articles/connectors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,18 @@ Each elementary/primitive/base type is represented by twin wrapper objects that
**Cyclic access** allows for fast, low-performance cost, two-way access to the PLC variables. Cyclic values are read and written in an optimized periodic loop. The controller twin object contains the entire PLC program, it does not discriminate between the variables and objects that are used by the consumer and those that are not. However, the Cyclic values are accessed via the communication interface only when:

- Twin connector is set to `Auto` subscription, which will set the variable into a cyclic read queue when `Cyclic` property is accessed in the consumer program.
- Twin connector is set to `Polling` subscription, and reading is activated by `StartPolling`.
- Twin connector is set to `Polling` subscription, and reading is activated by `StartPolling`. Polling can be stopped by calling the `StopPolling` method.

The polling mechanism keeps track of polling subscribers or holders and will only release the polling when the last subscriber calls the `StopPolling` method. It is good practice to call `StopPolling` when the holder/subscriber object is disposed.

Primitive Twins implement notification change when the cyclic property changes [INotifyPropertyChanged](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=net-7.0). This feature is particularly useful for visualization scenarios in presentation frameworks that support data binding with change notification (WPF, Blazor, WinForm).

> **WARNING**
> Cyclic access may result in degraded performance when the cyclic loop contains too many cyclically accessed primitive twins. Consider using `polling` instead of `automatic` subscription to balance the communication load.


Primitive Twins implement notification change when the cyclic property changes [INotifyPropertyChanged](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=net-7.0). This feature is particularly useful for visualization scenarios in presentation frameworks that support data binding with change notification (WPF, Blazor, WinForm).


~~~ C#
// Cyclic Read
/*
Expand Down
2 changes: 1 addition & 1 deletion docfx/articles/connectors/WebAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Entry.Plc.Connector.SetLoggerConfiguration(new LoggerConfiguration()

## Performance

Communication via WebAPI has inherent performance constraints based on the target system, request frequency, and payload size. The S71500.WebAPI connector tackles the 64kB limit for single requests by fragmenting them into manageable chunks. However, there is a restriction on the number of requests that can be sent to the controller simultaneously. The maximum threshold is four simultaneous requests, though this can be reduced. Any requests exceeding this limit will be queued and processed after a specified waiting period.
Communication via WebAPI has inherent performance constraints based on the target system, request frequency, and payload size. The S71500.WebAPI connector tackles the 128kB limit for single requests by fragmenting them into manageable chunks. However, there is a restriction on the number of requests that can be sent to the controller simultaneously. The maximum threshold is four simultaneous requests, though this can be reduced. Any requests exceeding this limit will be queued and processed after a specified waiting period.

> [!NOTE]
> It's worth noting that the controller might be communicating with other devices like HMI or OPC-UA, further intensifying the overall communication load.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
</ItemGroup>

<ItemGroup>
<Folder Include="RenderableContentControl\" />
<Folder Include="wwwroot\js\" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace AXSharp.Presentation.Blazor.Controls.RenderableContent
/// <summary>
/// Base class for complex components with only code-behind.
/// </summary>
public class RenderableComplexComponentBase<T> : RenderableComponentBase, IRenderableComplexComponentBase where T : ITwinElement
public abstract class RenderableComplexComponentBase<T> : RenderableComponentBase, IRenderableComplexComponentBase where T : ITwinElement
{
private T _component;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace AXSharp.Presentation.Blazor.Controls.RenderableContent
/// <summary>
/// Base class which implements methods to update UI when PLC values are changed.
/// </summary>
public partial class RenderableComponentBase : ComponentBase, IRenderableComponent, IDisposable
public abstract class RenderableComponentBase : ComponentBase, IRenderableComponent, IDisposable
{
/// <summary>
/// Gets or sets the RenderableContentControl that encapsulates this component.
Expand All @@ -38,7 +38,7 @@ public partial class RenderableComponentBase : ComponentBase, IRenderableCompone
/// </summary>
public virtual void Dispose()
{
RemovePolledElements();
StopPolling();
}

/// <summary>
Expand All @@ -51,21 +51,34 @@ public virtual void Dispose()
/// <summary>
/// Adds <see cref="element"/> to the polling queue.
/// >[!IMPORTANT] This method should be overriden in the derived class to limit the number of elements to be polled for large objects.
/// > All inner primitive types of the element will be added to the polling queue by default. When creating override remember to add the polled element to
/// > the <see cref="PolledElements"/> set that is needed for removal of the element from the polling queue once the component is disposed.
/// > None of inner primitive types of the element will be added to the polling queue by default.
/// > When creating override remember to add the polled element to the <see cref="PolledElements"/>
/// > set that is needed for removal of the element from the polling queue once the component is disposed.
/// </summary>
/// <param name="element">Element to be added to the polling queue.</param>
/// <param name="pollingInterval">Sets polling interval for the element.</param>
public virtual void AddToPolling(ITwinElement element, int pollingInterval = 250)
/// <example>
/// <code>
/// /// This will add all primitives from the component object to polling.
/// this.StartPolling(this.Component, pollingInterval);
/// </code>
/// </example>
public abstract void ConfigurePolling();

/// <summary>
/// Starts polling the element.
/// </summary>
/// <param name="element">Element to be polled.</param>
/// <param name="pollingInterval">Polling interval</param>
public void StartPolling(ITwinElement element, int pollingInterval = 250)
{
element.StartPolling(pollingInterval, this);
this.UpdateValuesOnChange(element);
PolledElements.Add(element);
}

/// <summary>
/// Removes elements added for polling from this component.
/// </summary>
public void RemovePolledElements()
public void StopPolling()
{
PolledElements.ToList().ForEach(p =>
{
Expand All @@ -78,9 +91,8 @@ public void RemovePolledElements()
/// <summary>
/// Method, which updates are primitive values of ITwinObject instance
/// <param name="element">ITwinObject instance.</param>
/// <param name="pollingInterval">Polling interval</param>
/// </summary>
private void UpdateValuesOnChange(ITwinObject element, int pollingInterval = 250)
private void UpdateValuesOnChange(ITwinObject element)
{
if (element != null)
{
Expand All @@ -95,29 +107,25 @@ private void UpdateValuesOnChange(ITwinObject element, int pollingInterval = 250
/// <summary>
/// Method, which updates primitive value.
/// <param name="tag">IValueTag instance.</param>
/// <param name="pollingInterval">Polling interval</param>
/// </summary>
private void UpdateValuesOnChange(OnlinerBase tag, int pollingInterval = 250)
private void UpdateValuesOnChange(OnlinerBase tag)
{
tag.PropertyChanged += new PropertyChangedEventHandler(HandlePropertyChanged);
}

/// <summary>
/// Method, which updates are primitive values of ITwinObject instance
/// <param name="element">ITwinElement instance.</param>
/// <param name="pollingInterval">Polling interval</param>
/// </summary>
public void UpdateValuesOnChange(ITwinElement element, int pollingInterval = 250)
private void UpdateValuesOnChange(ITwinElement element)
{
AddToPolling(element, pollingInterval);

switch (element)
{
case ITwinObject o:
UpdateValuesOnChange(o, pollingInterval);
UpdateValuesOnChange(o);
break;
case OnlinerBase b:
UpdateValuesOnChange(b, pollingInterval);
UpdateValuesOnChange(b);
break;
}
}
Expand Down Expand Up @@ -157,6 +165,7 @@ public void UpdateValuesOnChangeOutFocus(OnlinerBase tag)
tag.PropertyChanged += new PropertyChangedEventHandler(HandlePropertyChangedOnOutFocus);
}


protected void HandlePropertyChanged(object sender, PropertyChangedEventArgs a)
{
if (ShouldBeUpdated(sender as ITwinElement))
Expand All @@ -165,11 +174,17 @@ protected void HandlePropertyChanged(object sender, PropertyChangedEventArgs a)
}
}

/// <summary>
/// Method, which updates shadow primitive
/// </summary>
/// <param name="sender"></param>
/// <param name="a"></param>
protected void HandleShadowPropertyChanged(object sender, ValueChangedEventArgs a)
{
InvokeAsync(StateHasChanged);
}


protected virtual bool ShouldBeUpdated(ITwinElement element)
{
if (element == null)
Expand Down Expand Up @@ -202,7 +217,13 @@ protected void HandlePropertyChangedOnOutFocus(object sender, PropertyChangedEve
InvokeAsync(StateHasChanged);
}
}

}


protected override Task OnInitializedAsync()
{
ConfigurePolling();
return base.OnInitializedAsync();
}
}
}
Loading