diff --git a/src/data/app/ix-blazor/librarytemplate.blazor/librarytemplate.blazor.csproj b/src/data/app/ix-blazor/librarytemplate.blazor/librarytemplate.blazor.csproj
index ae43f4aa7..db293891e 100644
--- a/src/data/app/ix-blazor/librarytemplate.blazor/librarytemplate.blazor.csproj
+++ b/src/data/app/ix-blazor/librarytemplate.blazor/librarytemplate.blazor.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/data/app/src/Examples/AxoDataExchangeExample.st b/src/data/app/src/Examples/AxoDataExchangeExample.st
index dcabf238d..30475e352 100644
--- a/src/data/app/src/Examples/AxoDataExchangeExample.st
+++ b/src/data/app/src/Examples/AxoDataExchangeExample.st
@@ -1,3 +1,4 @@
+USING AXOpen.Core;
USING AXOpen;
using AXOpen.Data;
@@ -6,17 +7,28 @@ NAMESPACE AxoDataExchangeExample
//
CLASS PUBLIC Context EXTENDS AXOpen.Core.AxoContext
VAR PUBLIC
- DataManager : AxoProcessDataManager;
+ //Controlled unit that needs to manipulate with data
+ ControlledUnit : ControlledUnit;
+ //DataManager instance
+ DataManager : AxoProcessDataManager;
+
+ //Testing usage of the manager
+ ManagerUsage : ManagerUsage;
END_VAR
METHOD OVERRIDE Main
- DataManager.Run(THIS);
+
+ // DataManager.Run(THIS); - is called iside of controlle unit
+ ControlledUnit.ProcessData := REF(DataManager);//Set REF to DataManager (reason depth of data structure)
+ ControlledUnit.Run(THIS);// run logic of controlled unit
+
+ ManagerUsage.Use(DataManager);
END_METHOD
END_CLASS
//
//
- CLASS AxoProcessDataManager EXTENDS AXOpen.Data.AxoDataExchange
+ CLASS PUBLIC AxoProcessDataManager EXTENDS AXOpen.Data.AxoDataExchange
VAR PUBLIC
{#ix-generic:TOnline}
{#ix-generic:TPlain as POCO}
@@ -27,7 +39,7 @@ NAMESPACE AxoDataExchangeExample
//
//
- CLASS AxoProcessData EXTENDS AXOpen.Data.AxoDataEntity
+ CLASS PUBLIC AxoProcessData EXTENDS AXOpen.Data.AxoDataEntity
VAR PUBLIC
{#ix-set:AttributeName = "Some string"}
SomeString : STRING;
@@ -38,16 +50,16 @@ NAMESPACE AxoDataExchangeExample
//
//
- CLASS UseManager
- VAR
+ CLASS PUBLIC ManagerUsage
+ VAR PUBLIC
+ _id : STRING;
_create : BOOL;
- _read : BOOL;
_update : BOOL;
+ _read : BOOL;
_delete : BOOL;
- _id : STRING;
END_VAR
- METHOD Use
+ METHOD PUBLIC Use
VAR_IN_OUT
DataManager : AxoProcessDataManager;
END_VAR
@@ -79,13 +91,25 @@ NAMESPACE AxoDataExchangeExample
//
//
- CLASS CU EXTENDS AXOpen.Core.AxoObject
+ CLASS PUBLIC ControlledUnit EXTENDS AXOpen.Core.AxoObject
VAR PUBLIC
+ //Reference to ProcessDataManager
ProcessData : REF_TO AxoProcessDataManager;
+ //Automatic sequence. Contains logic for automatic operations of this unit.
+ AutomatSequence : AutomatSequence;
END_VAR
+ METHOD PUBLIC Run
+ VAR_INPUT
+ _parent : IAxoContext;
+ END_VAR
+ THIS.Initialize(_parent);
+ THIS.Execute();
+ END_METHOD
+
METHOD PRIVATE Execute
- ProcessData^.Run(THIS);
+ ProcessData^.Run(THIS); // cyclic call of AxoProcessDataManager
+ AutomatSequence.Run(THIS, ProcessData);
END_METHOD
END_CLASS
//
@@ -93,18 +117,21 @@ NAMESPACE AxoDataExchangeExample
//
CLASS PUBLIC AutomatSequence EXTENDS AXOpen.Core.AxoSequencerContainer
VAR PRIVATE
+ // private reference is used in Main()
ProcessData : REF_TO AxoProcessDataManager;
END_VAR
METHOD INTERNAL Run
VAR_INPUT
+ _parent : IAxoObject;
_processData : REF_TO AxoProcessDataManager;
END_VAR
+
ProcessData := _processData;
END_METHOD
METHOD OVERRIDE Main
- ;
+ ;//cyclic logic
END_METHOD
END_CLASS
//
diff --git a/src/data/app/src/Examples/AxoDataFragmentExchangeExample.st b/src/data/app/src/Examples/AxoDataFragmentExchangeExample.st
index a86b8c4cd..de9275c38 100644
--- a/src/data/app/src/Examples/AxoDataFragmentExchangeExample.st
+++ b/src/data/app/src/Examples/AxoDataFragmentExchangeExample.st
@@ -2,32 +2,43 @@ USING AXOpen.Core;
USING AXOpen.Data;
NAMESPACE AxoDataFramentsExchangeExample
-
+
//
- CLASS Context EXTENDS AXOpen.Core.AxoContext
+ CLASS PUBLIC Context EXTENDS AXOpen.Core.AxoContext
VAR PUBLIC
- DataManager : AxoProcessDataManager;
+ //Controlled unit that needs to manipulate with data
+ ControlledUnit : ControlledUnit;
+ //DataManager instance
+ DataManager : AxoProcessDataManager;
+
+ //Testing usage of the manager
+ ManagerUsage : ManagerUsage;
END_VAR
- METHOD PROTECTED OVERRIDE Main
- DataManager.Run(THIS);
+ METHOD OVERRIDE Main
+
+ // DataManager.Run(THIS); - is called iside of controlle unit
+ ControlledUnit.ProcessData := REF(DataManager);//Set REF to DataManager (reason depth of data structure)
+ ControlledUnit.Run(THIS);// run logic of controlled unit
+
+ ManagerUsage.Use(DataManager);
END_METHOD
END_CLASS
//
//
- CLASS AxoProcessDataManager
+ CLASS PUBLIC AxoProcessDataManager
EXTENDS AXOpen.Data.AxoDataFragmentExchange
VAR PUBLIC
{#ix-attr:[AXOpen.Data.AxoDataFragmentAttribute]}
- SharedHeader : SharedDataHeaderManger;
+ SharedHeader : SharedDataHeaderManager;
{#ix-attr:[AXOpen.Data.AxoDataFragmentAttribute]}
- Station_1 : Station_1_ProcessDataManger;
+ Station_1 : Station_1_ProcessDataManager;
END_VAR
END_CLASS
//
- CLASS SharedDataHeaderManger
+ CLASS PUBLIC SharedDataHeaderManager
EXTENDS AXOpen.Data.AxoDataExchange
VAR PUBLIC
{#ix-generic:TOnline}
@@ -39,7 +50,7 @@ NAMESPACE AxoDataFramentsExchangeExample
END_VAR
END_CLASS
- CLASS SharedDataHeaderData
+ CLASS PUBLIC SharedDataHeaderData
EXTENDS AXOpen.Data.AxoDataEntity
VAR PUBLIC
{#ix-set:AttributeName = "Some string"}
@@ -49,7 +60,7 @@ NAMESPACE AxoDataFramentsExchangeExample
END_VAR
END_CLASS
- CLASS Station_1_ProcessDataManger
+ CLASS PUBLIC Station_1_ProcessDataManager
EXTENDS AXOpen.Data.AxoDataExchange
VAR PUBLIC
{#ix-generic:TOnline}
@@ -61,7 +72,7 @@ NAMESPACE AxoDataFramentsExchangeExample
END_VAR
END_CLASS
- CLASS Station_1_Data EXTENDS AXOpen.Data.AxoDataEntity
+ CLASS PUBLIC Station_1_Data EXTENDS AXOpen.Data.AxoDataEntity
VAR PUBLIC
{#ix-set:AttributeName = "Some string"}
SomeString : STRING;
@@ -70,4 +81,93 @@ NAMESPACE AxoDataFramentsExchangeExample
END_VAR
END_CLASS
+ //
+ CLASS PUBLIC ManagerUsage
+ VAR PUBLIC
+ _id : STRING;
+ _create : BOOL;
+ _update : BOOL;
+ _read : BOOL;
+ _delete : BOOL;
+ END_VAR
+
+ METHOD PUBLIC Use
+ VAR_IN_OUT
+ DataManager : AxoProcessDataManager;
+ END_VAR
+ IF(_create) THEN
+ IF(DataManager.Create(_id).IsDone()) THEN
+ _create := FALSE;
+ END_IF;
+ END_IF;
+
+ IF(_read) THEN
+ IF(DataManager.Read(_id).IsDone()) THEN
+ _read := FALSE;
+ END_IF;
+ END_IF;
+
+ IF(_update) THEN
+ IF(DataManager.Update(_id).IsDone()) THEN
+ _update := FALSE;
+ END_IF;
+ END_IF;
+
+ IF(_delete) THEN
+ IF(DataManager.Delete(_id).IsDone()) THEN
+ _delete := FALSE;
+ END_IF;
+ END_IF;
+ END_METHOD
+ END_CLASS
+ //
+
+
+
+ //
+ CLASS PUBLIC ControlledUnit EXTENDS AXOpen.Core.AxoObject
+ VAR PUBLIC
+ //Reference to ProcessDataManager
+ ProcessData : REF_TO AxoProcessDataManager;
+ //Automatic sequence. Contains logic for automatic operations of this unit.
+ AutomatSequence : AutomatSequence;
+ END_VAR
+
+ METHOD PUBLIC Run
+ VAR_INPUT
+ _parent : IAxoContext;
+ END_VAR
+ THIS.Initialize(_parent);
+ THIS.Execute();
+ END_METHOD
+
+ METHOD PRIVATE Execute
+ ProcessData^.Run(THIS); // cyclic call of AxoProcessDataManager
+ AutomatSequence.Run(THIS, ProcessData);
+ END_METHOD
+ END_CLASS
+ //
+
+ //
+ CLASS PUBLIC AutomatSequence EXTENDS AXOpen.Core.AxoSequencerContainer
+ VAR PRIVATE
+ // private reference is used in Main()
+ ProcessData : REF_TO AxoProcessDataManager;
+ END_VAR
+
+ METHOD INTERNAL Run
+ VAR_INPUT
+ _parent : IAxoObject;
+ _processData : REF_TO AxoProcessDataManager;
+ END_VAR
+
+ ProcessData := _processData;
+ END_METHOD
+
+ METHOD OVERRIDE Main
+ ;//cyclic logic
+ END_METHOD
+ END_CLASS
+ //
+
END_NAMESPACE
\ No newline at end of file
diff --git a/src/data/app/src/Examples/AxoDataPersistentExchangeExample.st b/src/data/app/src/Examples/AxoDataPersistentExchangeExample.st
new file mode 100644
index 000000000..4d299ffbc
--- /dev/null
+++ b/src/data/app/src/Examples/AxoDataPersistentExchangeExample.st
@@ -0,0 +1,161 @@
+USING AXOpen;
+using AXOpen.Data;
+
+NAMESPACE AxoDataPersistentExchangeExample
+
+ //
+ CLASS PUBLIC Context EXTENDS AXOpen.Core.AxoContext
+ VAR PUBLIC
+ //persistent manager instance
+ DataManager : AXOpen.Data.AxoDataPersistentExchange;
+ //Testing usage of the manager
+ ManagerUsage : ManagerUsage;
+ // Object that propertie will be tracked by persistent manager
+ PersistentRootObject : PersistentRootObject;
+ END_VAR
+
+ METHOD OVERRIDE Main
+ DataManager.Run(THIS);
+ ManagerUsage.Use(DataManager);
+ END_METHOD
+ END_CLASS
+ //
+
+
+ //
+ CLASS PUBLIC ManagerUsage EXTENDS AxOpen.Core.AxoObject
+ VAR PUBLIC
+ _persistentGroupId : STRING;
+ _update : BOOL;
+ _read : BOOL;
+ _updateAll : BOOL;
+ _readAll : BOOL;
+ _exist : BOOL;
+ _recordExist : BOOL;
+ END_VAR
+
+ METHOD PUBLIC Use
+ VAR_IN_OUT
+ DataManager : AXOpen.Data.AxoDataPersistentExchange;
+ END_VAR
+
+ IF(_read) THEN
+ IF(DataManager.InvokeRead(THIS,_persistentGroupId)) THEN
+ ;
+ END_IF;
+
+ IF(DataManager.IsReadDone(THIS)) THEN
+ _read := FALSE;
+ END_IF;
+ END_IF;
+
+ IF(_update) THEN
+ IF(DataManager.InvokeUpdate(THIS,_persistentGroupId)) THEN
+ ;
+ END_IF;
+
+ IF(DataManager.IsUpdateDone(THIS)) THEN
+ _update := FALSE;
+ END_IF;
+ END_IF;
+
+ IF(_readAll) THEN
+ IF(DataManager.InvokeReadAll(THIS)) THEN
+ ;
+ END_IF;
+ IF(DataManager.IsReadAllDone(THIS)) THEN
+ _readAll := FALSE;
+ END_IF;
+ END_IF;
+
+ IF(_updateAll) THEN
+ IF(DataManager.InvokeUpdateAll(THIS)) THEN
+ ;
+ END_IF;
+
+ IF(DataManager.IsUpdateAllDone(THIS)) THEN
+ _updateAll := FALSE;
+ END_IF;
+ END_IF;
+
+ IF(_exist) THEN
+ IF(DataManager.InvokeEntityExist(THIS, _persistentGroupId)) THEN
+ ;
+ END_IF;
+
+ IF(DataManager.IsEntityExistDone(THIS)) THEN
+ _exist := FALSE;
+ _recordExist := DataManager.Operation._exist;
+ END_IF;
+ END_IF;
+ END_METHOD
+ END_CLASS
+ //
+
+ //
+ CLASS PUBLIC PersistentRootObject
+ VAR PUBLIC
+ NotPersistentVariable : BOOL;
+
+ {#ix-attr:[Persistent()]}
+ {#ix-set:AttributeName = "PersistentVariable_1 (in default pg.))"}
+ PersistentVariable_1 : INT; // will be saved to 'default' persistent group
+
+ {#ix-attr:[Persistent("default","1")]} // will be saved to 'default' and '1' persistent group
+ {#ix-set:AttributeName = "PersistentVariable_2 (in default & 1 pg.)"}
+ PersistentVariable_2 : INT;
+
+ {#ix-attr:[Container(Layout.Stack)]}
+ {#ix-attr:[Group(GroupLayout.GroupBox)]}
+ {#ix-set:AttributeName = "PropertyWithPersistentMember"}
+ PropertyWithPersistentMember : ObjectWithPersistentMember;
+ END_VAR
+ END_CLASS
+
+ CLASS PUBLIC ObjectWithPersistentMember
+ VAR PUBLIC
+ NotPersistentVariable : INT;
+
+ {#ix-attr:[Persistent("2")]}
+ {#ix-attr:[Container(Layout.Stack)]}
+ {#ix-attr:[Group(GroupLayout.GroupBox)]}
+ {#ix-set:AttributeName = "InitializedPrimitives (in 2 pg.)"}
+ InitializedPrimitives : InitializedPrimitives;
+ END_VAR
+ END_CLASS
+
+ CLASS PUBLIC InitializedPrimitives
+ VAR PUBLIC
+ myBOOL : BOOL := true;
+ myBYTE : BYTE := BYTE#255;
+ myWORD : WORD := WORD#60000;
+ myDWORD : DWORD := DWORD#16#ABCD_EF01;
+ myLWORD : LWORD := LWORD#18446744073709551615;
+ mySINTMin : SINT := SINT#-128;
+ mySINTMax : SINT := SINT#127;
+ myINT : INT := INT#32767;
+ myDINT : DINT := DINT#2147483647;
+ myLINT : LINT := LINT#9223372036854775807;
+ myUSINT : USINT := USINT#255;
+ myUINT : UINT := UINT#65535;
+ myUDINT : UDINT := UDINT#4294967295;
+ myULINT : ULINT := ULINT#18446744073709551615;
+ myREAL : REAL := REAL#+3.402823e+38;
+ myLREAL : LREAL := LREAL#1.79769313486231e+308;
+ myTIME : TIME := TIME#2147483647ms;
+ myLTIME : LTIME := LTIME#100000d2h4m8s16ms32ns;
+ myDATE : DATE := DATE#2262-04-11;
+ myLDATE : LDATE := LDATE#2262-04-11;
+ myTIME_OF_DAY : TIME_OF_DAY := TIME_OF_DAY#23:59:59.999999999;
+ myLTIME_OF_DAY : LTIME_OF_DAY := LTIME_OF_DAY#23:59:59.999999999;
+ myDATE_AND_TIME : DATE_AND_TIME := DATE_AND_TIME#2262-04-11-23:47:16.854775807;
+ myLDATE_AND_TIME : LDATE_AND_TIME := LDATE_AND_TIME#2262-04-11-23:47:16.854775807;
+ myCHAR : CHAR := CHAR#255;
+ myWCHAR : WCHAR := WCHAR#65535;
+ mySTRING : STRING := 'o\zxcvbnm,./asdfghjkl;\\qwertyuiop[]1234567890-~!@#^&*()_+';
+ myWSTRING : WSTRING := WSTRING#"o\zxcvbnm,./asdfghjkl;'\qwertyuiop[]1234567890-~!@#^&*()_+";
+ END_VAR
+ END_CLASS
+ //
+
+END_NAMESPACE
\ No newline at end of file
diff --git a/src/data/app/src/configuration.st b/src/data/app/src/configuration.st
index 40ccfb02b..095033109 100644
--- a/src/data/app/src/configuration.st
+++ b/src/data/app/src/configuration.st
@@ -1,10 +1,11 @@
USING AXOpen.Data;
CONFIGURATION MyConfiguration
- TASK Main(Interval := T#1000ms, Priority := 1);
+ TASK Main(Interval := T#10ms, Priority := 1);
PROGRAM P1 WITH Main: MyProgram;
VAR_GLOBAL
- AxoDataExchangeManager : AxoDataExchangeExample.AxoProcessDataManager;
- AxoDataFramentsExchangeManager : AxoDataFramentsExchangeExample.AxoProcessDataManager;
+ AxoDataExchangeContext : AxoDataExchangeExample.Context;
+ AxoDataFragmentsExchangeContext : AxoDataFramentsExchangeExample.Context;
+ AxoDataPersistentContext : AxoDataPersistentExchangeExample.Context;
END_VAR
END_CONFIGURATION
diff --git a/src/data/app/src/program.st b/src/data/app/src/program.st
index 1244086d9..41ac85241 100644
--- a/src/data/app/src/program.st
+++ b/src/data/app/src/program.st
@@ -1,7 +1,12 @@
PROGRAM MyProgram
VAR_EXTERNAL
- AxoDataExchangeManager : AxoDataExchangeExample.AxoProcessDataManager;
- AxoDataFramentsExchangeManager : AxoDataFramentsExchangeExample.AxoProcessDataManager;
+ AxoDataExchangeContext : AxoDataExchangeExample.Context;
+ AxoDataFragmentsExchangeContext : AxoDataFramentsExchangeExample.Context;
+ AxoDataPersistentContext : AxoDataPersistentExchangeExample.Context;
END_VAR
- ;
+
+ AxoDataPersistentContext.Run();
+ AxoDataFragmentsExchangeContext.Run();
+ AxoDataExchangeContext.Run();
+
END_PROGRAM
diff --git a/src/data/ctrl/src/AxoDataFragmentExchange.st b/src/data/ctrl/src/AxoDataFragmentExchange.st
index 896f46f86..bb08baa19 100644
--- a/src/data/ctrl/src/AxoDataFragmentExchange.st
+++ b/src/data/ctrl/src/AxoDataFragmentExchange.st
@@ -1,7 +1,4 @@
USING AXOpen.Core;
-USING AXOpen.Core;
-USING AXOpen.Core;
-
NAMESPACE AXOpen.Data
///
/// Provides base class for any composite/fragmetes data exchange combining one or more AxoDataExchange object.
diff --git a/src/data/ctrl/src/AxoDataPersistentCurdTask.st b/src/data/ctrl/src/AxoDataPersistentCurdTask.st
new file mode 100644
index 000000000..81ad35f9b
--- /dev/null
+++ b/src/data/ctrl/src/AxoDataPersistentCurdTask.st
@@ -0,0 +1,44 @@
+USING AXOpen.Core;
+NAMESPACE AXOpen.Data
+ ///
+ /// Provides remote execution for CRUD operations.
+ /// > [!NOTE]
+ /// > This is an extension of task see the documentation for details about implementation in .NET.
+ ///
+ {#ix-set:AttributeName = "<#Data operation#>"}
+ CLASS AxoDataPersistentCurdTask EXTENDS AxOpen.Core.AxoRemoteTask IMPLEMENTS IAxoEntityExistTaskState
+ VAR PUBLIC
+ ///
+ /// Gets or sets the type of CRUD operation to be perfomed.
+ ///
+ CrudOperation : ePersistentOperation;
+ DataEntityIdentifier : STRING[254];
+ _exist : BOOL;
+ END_VAR
+
+ ///
+ /// Invokes this task.
+ ///
+ /// Data entity identifier
+ /// Operation to perfom.
+ METHOD PUBLIC Invoke : IAxoTaskState
+ VAR_INPUT
+ identifier : STRING[254];
+ operation : ePersistentOperation;
+ END_VAR
+ CrudOperation := operation;
+ DataEntityIdentifier := identifier;
+ Invoke := SUPER.Invoke();
+ END_METHOD
+
+ METHOD PUBLIC Exist : BOOL
+ Exist := _exist;
+ END_METHOD
+
+ END_CLASS
+
+ TYPE
+ ePersistentOperation : (Read, Update, EntityExist, UpdateAll, ReadAll);
+ END_TYPE
+
+END_NAMESPACE
diff --git a/src/data/ctrl/src/AxoDataPersistentExchange.st b/src/data/ctrl/src/AxoDataPersistentExchange.st
new file mode 100644
index 000000000..0128ae8d2
--- /dev/null
+++ b/src/data/ctrl/src/AxoDataPersistentExchange.st
@@ -0,0 +1,408 @@
+USING AXOpen.Core;
+NAMESPACE AXOpen.Data
+ CLASS PUBLIC AxoDataPersistentExchange EXTENDS AXOpen.Core.AxoObject
+ VAR PUBLIC
+ Operation : AxoDataPersistentCurdTask;
+ END_VAR
+
+ VAR CONSTANT
+ DEFAULT_ID : STRING := 'default';
+ END_VAR
+
+ VAR
+ _IsFirstReadAllDone : BOOL := FALSE;
+
+ // concurrent invoke handling...
+ LastCallerIdentity : ULINT;
+ LastCycleCount : ULINT;
+ LastOperation : ePersistentOperation;
+
+ END_VAR
+
+ ///
+ /// This method determines if the current call is concurrent or not.
+ /// It takes an AXOpen.Core.IAxoObject as input and returns a BOOL.
+ /// The logic involves checking the identity of the caller and comparing it with the last caller identity,
+ /// along with the operation's readiness and cycle count checks.
+ ///
+ /// IAxoObject for identifying the caller in the context of concurrent calls
+ METHOD PRIVATE IsNotConcurrentCall : BOOL
+ VAR_INPUT
+ caller : AXOpen.Core.IAxoObject;
+ END_VAR
+ VAR
+ newCallerIdentity : ULINT;
+ newCycleCount : ULINT;
+ operationState : eAxoTaskState;
+ END_VAR
+
+ IsNotConcurrentCall := false;
+ newCallerIdentity := caller.GetIdentity();
+ newCycleCount := THIS.GetContext().OpenCycleCount();
+
+ IF newCallerIdentity = ULINT#0 THEN // IF IDENTITY IS NOT SET DO NOT ALLOW CALL
+ RETURN;
+ END_IF;
+
+ IF newCallerIdentity = LastCallerIdentity THEN
+ IsNotConcurrentCall := true;
+ LastCycleCount := newCycleCount;
+ RETURN;
+ ELSE
+ operationState := Operation.GetState();
+ IF (operationState = eAxoTaskState#Ready) OR (operationState = eAxoTaskState#Done) THEN
+ IF LastCycleCount > newCycleCount THEN // counter overflow
+ IsNotConcurrentCall := true;
+ LastCycleCount := newCycleCount;
+ LastCallerIdentity := newCallerIdentity;
+ Operation.Restore();
+
+ ELSIF (LastCycleCount + ULINT#2) < newCycleCount THEN
+ IsNotConcurrentCall := true;
+ LastCycleCount := newCycleCount;
+ LastCallerIdentity := newCallerIdentity;
+ Operation.Restore();
+ END_IF;
+ END_IF;
+ END_IF;
+
+ END_METHOD
+
+ ///
+ /// Runs intialization and cyclical handling of this AxoDataPersistentExchange.
+ ///
+ /// Parent of this object
+ METHOD PUBLIC Run
+ VAR_INPUT
+ parent : IAxoObject;
+ END_VAR
+
+ if _context_ = NULL then
+ THIS.Initialize(parent);
+ Operation.Initialize(THIS);
+ end_if;
+
+ Operation.Execute();
+
+ IF NOT _IsFirstReadAllDone THEN
+ IF Operation.IsInitialized AND (Operation.Identity <> ULINT#0) THEN // MUST BE INITIALIZED AND WITH IDENTITY
+ IF Operation.IsReady() THEN // read all after startup
+ Operation.Invoke('', ePersistentOperation#ReadAll);
+ LastOperation := ePersistentOperation#ReadAll;
+
+ ELSIF Operation.IsDone() AND (LastOperation = ePersistentOperation#ReadAll) THEN
+ _IsFirstReadAllDone := TRUE;
+ END_IF;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// Runs intialization and cyclical handling of this AxoDataPersistentExchange.
+ ///
+ /// Root context of this object
+ METHOD PUBLIC Run
+ VAR_INPUT
+ context : IAxoContext;
+ END_VAR
+
+ IF _context_ = NULL THEN
+ THIS.Initialize(context);
+ Operation.Initialize(THIS);
+ END_IF;
+
+ Operation.Execute();
+
+ IF NOT _IsFirstReadAllDone THEN
+ IF Operation.IsInitialized AND (Operation.Identity <> ULINT#0) THEN // MUST BE INITIALIZED AND WITH IDENTITY
+ IF Operation.IsReady() THEN // read all after startup
+ Operation.Invoke('', ePersistentOperation#ReadAll);
+ LastOperation := ePersistentOperation#ReadAll;
+
+ ELSIF Operation.IsDone() AND (LastOperation = ePersistentOperation#ReadAll) THEN
+ _IsFirstReadAllDone := TRUE;
+ END_IF;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// Executes the restore Operation for the AxoDataPersistentExchange component.
+ ///
+ METHOD PUBLIC Restore
+ Operation.Restore();
+ END_METHOD
+
+ ///
+ /// Return last result of EntitiyExist Operation. True = Record Exist.
+ ///
+ METHOD PUBLIC EntityExistResult : BOOL
+ EntityExistResult := Operation._exist;
+ END_METHOD
+
+
+ ///
+ /// This method attempts to invoke a read operation.
+ /// It checks if the call is concurrent and, if not,
+ /// invokes the read operation on the Operation object.
+ ///
+ /// IAxoObject for identifying the caller in the context of concurrent calls
+ /// The data identifier for the persistent group.
+ METHOD PUBLIC InvokeRead : BOOL
+ VAR_INPUT
+ caller : IAxoObject;
+ identifier : STRING[254];
+ END_VAR
+
+ InvokeRead := false;
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(caller) THEN
+ InvokeRead := Operation.Invoke(identifier, ePersistentOperation#Read).IsBusy();
+ LastOperation := ePersistentOperation#Read;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// This method attempts to invoke a read operation.
+ /// It checks if the call is concurrent and, if not,
+ /// invokes the read operation on the Operation object.
+ ///
+ /// IAxoObject for identifying the caller in the context of concurrent calls
+ METHOD PUBLIC InvokeReadDefault : BOOL
+ VAR_INPUT
+ caller : IAxoObject;
+ END_VAR
+ InvokeReadDefault := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(caller) THEN
+ InvokeReadDefault := Operation.Invoke(DEFAULT_ID, ePersistentOperation#Read).IsBusy();
+ LastOperation := ePersistentOperation#Read;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// This method attempts to invoke a update operation.
+ /// It checks if the call is concurrent and, if not,
+ /// invokes the update operation on the Operation object.
+ ///
+ /// IAxoObject for identifying the caller in the context of concurrent calls
+ /// The data identifier for the persistent group.
+ METHOD PUBLIC InvokeUpdate : BOOL
+ VAR_INPUT
+ Caller : IAxoObject;
+ identifier : STRING[254];
+ END_VAR
+ InvokeUpdate := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(Caller) THEN
+ InvokeUpdate := Operation.Invoke(identifier, ePersistentOperation#Update).IsBusy();
+ LastOperation := ePersistentOperation#Update;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// This method attempts to invoke a update operation.
+ /// It checks if the call is concurrent and, if not,
+ /// invokes the update operation on the Operation object.
+ ///
+ /// IAxoObject for identifying the caller in the context of concurrent calls
+ METHOD PUBLIC InvokeUpdateDefault : BOOL
+ VAR_INPUT
+ Caller : IAxoObject;
+ END_VAR
+ InvokeUpdateDefault := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(Caller) THEN
+ InvokeUpdateDefault := Operation.Invoke(DEFAULT_ID, ePersistentOperation#Update).IsBusy();
+ LastOperation := ePersistentOperation#Update;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// This method attempts to invoke a updateAll operation.
+ /// It checks if the call is concurrent and, if not,
+ /// invokes the update operation on the Operation object.
+ ///
+ /// IAxoObject for identifying the caller in the context of concurrent calls
+ /// The data identifier for the persistent group.
+ METHOD PUBLIC InvokeUpdateAll : BOOL
+ VAR_INPUT
+ Caller : IAxoObject;
+ END_VAR
+ InvokeUpdateAll := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(Caller) THEN
+ InvokeUpdateAll := Operation.Invoke('', ePersistentOperation#UpdateAll).IsBusy();
+ LastOperation := ePersistentOperation#UpdateAll;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// This method attempts to invoke a readAll operation.
+ /// It checks if the call is concurrent and, if not,
+ /// invokes the readAll operation on the Operation object.
+ ///
+ /// IAxoObject for identifying the caller in the context of concurrent calls
+ /// The data identifier for the persistent group.
+ METHOD PUBLIC InvokeReadAll : BOOL
+ VAR_INPUT
+ Caller : IAxoObject;
+ END_VAR
+ InvokeReadAll := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(Caller) THEN
+ InvokeReadAll := Operation.Invoke('', ePersistentOperation#ReadAll).IsBusy();
+ LastOperation := ePersistentOperation#ReadAll;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// This method attempts to invoke a update operation.
+ /// It checks if the call is concurrent and, if not,
+ /// invokes the update operation on the Operation object.
+ ///
+ /// IAxoObject for identifying the caller in the context of concurrent calls
+ /// The data identifier for the persistent group.
+ METHOD PUBLIC InvokeEntityExist : BOOL
+ VAR_INPUT
+ Caller : IAxoObject;
+ identifier : STRING[254];
+ END_VAR
+
+ InvokeEntityExist := false;
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(Caller) THEN
+ InvokeEntityExist := Operation.Invoke(identifier, ePersistentOperation#EntityExist).IsBusy();
+ LastOperation := ePersistentOperation#EntityExist;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+
+ ///
+ /// Determines whether a read operation has been completed.
+ ///
+ /// The object representing the caller of the method, used to check for concurrent calls.
+ /// Boolean indicating whether the read operation was completed.
+ METHOD PUBLIC IsReadDone : BOOL
+ VAR_INPUT
+ caller : IAxoObject;
+ END_VAR
+ IsReadDone := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(caller) THEN
+ IF LastOperation = ePersistentOperation#Read THEN
+ IsReadDone := Operation.IsDone();
+ END_IF;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// Determines whether a update operation has been completed.
+ ///
+ /// The object representing the caller of the method, used to check for concurrent calls.
+ /// Boolean indicating whether the read operation was completed.
+ METHOD PUBLIC IsUpdateDone : BOOL
+ VAR_INPUT
+ caller : IAxoObject;
+ END_VAR
+ IsUpdateDone := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(caller) THEN
+ IF LastOperation = ePersistentOperation#Update THEN
+ IsUpdateDone := Operation.IsDone();
+ END_IF;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// Determines whether a updateAll operation has been completed.
+ ///
+ /// The object representing the caller of the method, used to check for concurrent calls.
+ /// Boolean indicating whether the updateAll operation was completed.
+ METHOD PUBLIC IsUpdateAllDone : BOOL
+ VAR_INPUT
+ caller : IAxoObject;
+ END_VAR
+ IsUpdateAllDone := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(caller) THEN
+ IF LastOperation = ePersistentOperation#UpdateAll THEN
+ IsUpdateAllDone := Operation.IsDone();
+ END_IF;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// Determines whether a readAll operation has been completed.
+ ///
+ /// The object representing the caller of the method, used to check for concurrent calls.
+ /// Boolean indicating whether the readAll operation was completed.
+ METHOD PUBLIC IsReadAllDone : BOOL
+ VAR_INPUT
+ caller : IAxoObject;
+ END_VAR
+ IsReadAllDone := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(caller) THEN
+ IF LastOperation = ePersistentOperation#ReadAll THEN
+ IsReadAllDone := Operation.IsDone();
+ END_IF;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// Determines whether a entityExist operation has been completed.
+ ///
+ /// The object representing the caller of the method, used to check for concurrent calls.
+ /// Boolean indicating whether the entityExist operation was completed.
+ METHOD PUBLIC IsEntityExistDone : BOOL
+ VAR_INPUT
+ caller : IAxoObject;
+ END_VAR
+ IsEntityExistDone := false;
+
+ IF _IsFirstReadAllDone THEN
+ IF THIS.IsNotConcurrentCall(caller) THEN
+ IF LastOperation = ePersistentOperation#EntityExist THEN
+ IsEntityExistDone := Operation.IsDone();
+ END_IF;
+ END_IF;
+ END_IF;
+ END_METHOD
+
+ ///
+ /// Checks if the first read operation has been completed.
+ ///
+ /// Boolean indicating whether the first read operation is done.
+ METHOD PUBLIC IsFirstReadDone : BOOL
+ IsFirstReadDone := _IsFirstReadAllDone;
+ END_METHOD
+
+ //!!! INTERNAL USAGE ONLY !!!
+ METHOD INTERNAL SetFirstReadDone : BOOL
+ _IsFirstReadAllDone := TRUE;
+ END_METHOD
+
+ END_CLASS
+
+END_NAMESPACE
\ No newline at end of file
diff --git a/src/data/ctrl/test/AxoDataPersistentExchangeTaskTests.st b/src/data/ctrl/test/AxoDataPersistentExchangeTaskTests.st
new file mode 100644
index 000000000..2abb29d43
--- /dev/null
+++ b/src/data/ctrl/test/AxoDataPersistentExchangeTaskTests.st
@@ -0,0 +1,67 @@
+USING AXOpen.Core;
+USING AXOpen.Rtc;
+USING AxUnit;
+
+NAMESPACE AXOpen.Data
+ NAMESPACE AxoDataPersistentExchangeTaskTests
+ CLASS TestContext IMPLEMENTS IAxoContext
+ VAR PROTECTED
+ _openCounter : ULINT;
+ _closeCounter : ULINT;
+ _identityCounter : ULINT;
+ END_VAR
+ METHOD PUBLIC Open : ULINT
+ _openCounter := _openCounter + ULINT#1;
+ END_METHOD
+
+ METHOD PUBLIC Close : ULINT
+ _closeCounter := _closeCounter + ULINT#1;
+ END_METHOD
+
+ METHOD PUBLIC OpenCycleCount : ULINT
+ OpenCycleCount := _openCounter;
+ END_METHOD
+
+ METHOD PUBLIC ClosedCycleCount : ULINT
+ ClosedCycleCount := _closeCounter;
+ END_METHOD
+
+ METHOD PUBLIC CreateIdentity : ULINT
+ _identityCounter := _identityCounter + ULINT#1;
+ CreateIdentity := _identityCounter;
+ END_METHOD
+
+ METHOD PUBLIC GetRtc : IAxoRtc ; END_METHOD
+
+ METHOD PUBLIC InjectRtc VAR_INPUT Rtc : IAxoRtc; END_VAR ; END_METHOD
+
+ VAR PRIVATE
+ NULL_LOGGER : _NULL_LOGGER;
+ END_VAR
+ METHOD PUBLIC GetLogger : AXOpen.Logging.IAxoLogger GetLogger := NULL_LOGGER; END_METHOD
+ METHOD PUBLIC InjectLogger VAR_INPUT _logger : AXOpen.Logging.IAxoLogger; END_VAR ; END_METHOD
+ END_CLASS
+
+ {TestFixture}
+ CLASS AxoDataPersistentExchangeTask_Tests
+ VAR PROTECTED
+ _context : TestContext;
+ _dataExchangeTask : AxoDataPersistentCurdTask;
+ END_VAR
+
+ {Test}
+ METHOD PUBLIC InvokeWithIdentifier_sets_identifier
+ VAR
+ expected : STRING[254];
+ END_VAR
+ _dataExchangeTask.Initialize(_context);
+ _context.Open();
+ expected := 'UpdateIdentifier';
+ _dataExchangeTask.Invoke(expected);
+ _context.Close();
+ AxUnit.Assert.Equal(_dataExchangeTask.DataEntityIdentifier, expected);
+ END_METHOD
+
+ END_CLASS
+ END_NAMESPACE
+END_NAMESPACE
diff --git a/src/data/ctrl/test/AxoDataPersistentExchangeTests.st b/src/data/ctrl/test/AxoDataPersistentExchangeTests.st
new file mode 100644
index 000000000..913925dcc
--- /dev/null
+++ b/src/data/ctrl/test/AxoDataPersistentExchangeTests.st
@@ -0,0 +1,569 @@
+USING AXOpen.Core;
+USING AXOpen.Rtc;
+USING AxUnit;
+
+NAMESPACE AXOpen.Data
+ NAMESPACE AxoDataPersistentExchangeTests
+ CLASS TestContext IMPLEMENTS IAxoContext
+ VAR PROTECTED
+ _openCounter : ULINT;
+ _closeCounter : ULINT;
+ _identityCounter : ULINT;
+ END_VAR
+
+ VAR PRIVATE
+ NULL_LOGGER : _NULL_LOGGER;
+ NULL_RTC : _NULL_RTC;
+ _rtc : IAxoRtc;
+ END_VAR
+
+ METHOD PUBLIC Open : ULINT
+ _openCounter := _openCounter + ULINT#1;
+ END_METHOD
+
+ METHOD PUBLIC Close : ULINT
+ _closeCounter := _closeCounter + ULINT#1;
+ END_METHOD
+
+ METHOD PUBLIC OpenCycleCount : ULINT
+ OpenCycleCount := _openCounter;
+ END_METHOD
+
+ METHOD PUBLIC ClosedCycleCount : ULINT
+ ClosedCycleCount := _closeCounter;
+ END_METHOD
+
+ METHOD PUBLIC CreateIdentity : ULINT
+ _identityCounter := _identityCounter + ULINT#1;
+ CreateIdentity := _identityCounter;
+ END_METHOD
+
+ METHOD PUBLIC GetRtc : IAxoRtc
+ IF(_rtc <> NULL) THEN
+ GetRtc := _rtc;
+ ELSE
+ GetRtc := NULL_RTC;
+ END_IF;
+ END_METHOD
+
+ METHOD PUBLIC InjectRtc
+ VAR_INPUT
+ Rtc : IAxoRtc;
+ END_VAR
+ _rtc := Rtc;
+ END_METHOD
+
+ METHOD PUBLIC GetLogger : AXOpen.Logging.IAxoLogger GetLogger := NULL_LOGGER; END_METHOD
+ METHOD PUBLIC InjectLogger VAR_INPUT _logger : AXOpen.Logging.IAxoLogger; END_VAR ; END_METHOD
+ END_CLASS
+
+ CLASS AxoRtcMock IMPLEMENTS IAxoRtc
+ VAR PRIVATE
+ _NowUTC : LDATE_AND_TIME;
+ END_VAR
+
+ METHOD INTERNAL SetNowUTC : LDATE_AND_TIME
+ VAR_INPUT
+ Set : LDATE_AND_TIME;
+ END_VAR;
+ _NowUTC := Set;
+ END_METHOD
+
+ METHOD PUBLIC NowUTC : LDATE_AND_TIME
+ NowUTC := _NowUTC;
+ END_METHOD
+ END_CLASS
+
+
+ {TestFixture}
+ CLASS AxoDataExchange_Tests
+ VAR PROTECTED
+ _context : TestContext;
+ _rtc : AxoRtcMock;
+
+ _dataExchange : AxoDataPersistentExchange;
+
+ caller_1 : AxoObject;
+ caller_2 : AxoObject;
+ END_VAR
+
+ METHOD PUBLIC RunContextMultipleltipleTimes
+ VAR_INPUT
+ cycleCount : INT;
+ end_var
+ VAR
+ iteration : INT;
+ END_VAR
+ FOR iteration := 0 TO cycleCount DO
+ _context.Open();
+ _dataExchange.Run(_context);
+ _context.Close();
+ END_FOR;
+ END_METHOD
+
+
+ {Test}
+ METHOD PUBLIC InvokeRead_NotInvoked_WhenFirstReadDoneNotFinish
+ VAR
+ IdentifierCaller_1 : STRING[254];
+
+ ResultCaller_1 :BOOL;
+
+ startTime : LDATE_AND_TIME;
+ END_VAR
+ // initializtion
+ startTime := LDATE_AND_TIME#2023-11-11-11:11:11.111;
+ _rtc.SetNowUTC(startTime);
+ _context.InjectRtc(_rtc);
+ caller_1.Initialize(_context);
+ caller_1.Identity := _context.CreateIdentity();
+ IdentifierCaller_1 := '1';
+ _dataExchange.Operation.IsInitialized := true; //simulate intit from .net side
+
+ // needs to simulate + 2 cycles
+ THIS.RunContextMultipleltipleTimes(3);
+
+ //------------------------ INVOKE READ ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1);
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, '');
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+
+ _context.Close();
+
+ //------------------------ INVOKE READ ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1);
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, '');
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+
+ _context.Close();
+
+ //------------------------ INVOKE READ ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1);
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, '');
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+
+ _context.Close();
+ END_METHOD
+
+ {Test}
+ METHOD PUBLIC InvokeRead_Invoked_WhenFirstReadDoneFinish
+ VAR
+ IdentifierCaller_1 : STRING[254];
+
+ ResultCaller_1 :BOOL;
+
+ startTime : LDATE_AND_TIME;
+ END_VAR
+ // initializtion
+ startTime := LDATE_AND_TIME#2023-11-11-11:11:11.111;
+ _rtc.SetNowUTC(startTime);
+ _context.InjectRtc(_rtc);
+ caller_1.Initialize(_context);
+ caller_1.Identity := _context.CreateIdentity();
+ IdentifierCaller_1 := '1';
+ _dataExchange.Operation.IsInitialized := true; //simulate intit from .net side
+
+ // needs to simulate + 2 cycles
+ THIS.RunContextMultipleltipleTimes(3);
+
+ //------------------------ INVOKE READ BLOCKED ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1);
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, '');
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+
+ _context.Close();
+
+
+ //------------------------ INVOKE READ ENABLED ------------------------
+
+ _dataExchange.SetFirstReadDone(); // firs read done simulation
+
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1); // kicking
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+
+ _context.Close();
+
+ //------------------------ INVOKE READ ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1); // BUSY => INVOKED
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,TRUE);
+
+ _context.Close();
+
+ END_METHOD
+
+ {Test}
+ METHOD PUBLIC InvokeRead_NotInvoked_CallerHasNoIdentity
+ VAR
+ IdentifierCaller_1 : STRING[254];
+
+ ResultCaller_1 :BOOL;
+
+ startTime : LDATE_AND_TIME;
+ END_VAR
+ // initializtion
+ startTime := LDATE_AND_TIME#2023-11-11-11:11:11.111;
+ _rtc.SetNowUTC(startTime);
+ _context.InjectRtc(_rtc);
+ caller_1.Initialize(_context);
+ caller_1.Identity := ulint#0;
+ IdentifierCaller_1 := '1';
+ _dataExchange.Operation.IsInitialized := true; //simulate intit from .net side
+ _dataExchange.SetFirstReadDone();
+
+ // needs to simulate + 2 cycles
+ THIS.RunContextMultipleltipleTimes(3);
+
+ //------------------------ INVOKE READ ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1);
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, '');
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+
+ _context.Close();
+
+ //------------------------ INVOKE READ ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1);
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, '');
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+
+ _context.Close();
+
+ //------------------------ INVOKE READ ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1);
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, '');
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+
+ _context.Close();
+ END_METHOD
+
+ {Test}
+ METHOD PUBLIC InvokeRead_Invoked_CallerHasIdentity
+ VAR
+ IdentifierCaller_1 : STRING[254];
+
+ ResultCaller_1 :BOOL;
+
+ startTime : LDATE_AND_TIME;
+ END_VAR
+ // initializtion
+ startTime := LDATE_AND_TIME#2023-11-11-11:11:11.111;
+ _rtc.SetNowUTC(startTime);
+ _context.InjectRtc(_rtc);
+ caller_1.Initialize(_context);
+ caller_1.Identity := _context.CreateIdentity();
+ IdentifierCaller_1 := '1';
+ _dataExchange.Operation.IsInitialized := true; //simulate intit from .net side
+ _dataExchange.SetFirstReadDone();
+
+ // needs to simulate + 2 cycles
+ THIS.RunContextMultipleltipleTimes(3);
+
+ //------------------------ INVOKE READ ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1); // kicking
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+
+ _context.Close();
+
+ //------------------------ INVOKE READ ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsReady());
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1); // BUSY => INVOKED
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,TRUE);
+
+ _context.Close();
+
+ END_METHOD
+
+ {Test}
+ METHOD PUBLIC ConcurentCall_Update_test
+ VAR
+ IdentifierCaller_1 : STRING[254];
+ IdentifierCaller_2 : STRING[254];
+
+ ResultCaller_1 :BOOL;
+ ResultCaller_2 :BOOL;
+
+ startTime : LDATE_AND_TIME;
+ END_VAR
+ // initializtion
+ startTime := LDATE_AND_TIME#2023-11-11-11:11:11.111;
+ _rtc.SetNowUTC(startTime);
+ _context.InjectRtc(_rtc);
+ caller_1.Initialize(_context);
+ caller_2.Initialize(_context);
+ caller_1.Identity := _context.CreateIdentity();
+ caller_2.Identity := _context.CreateIdentity();
+ IdentifierCaller_1 := '1';
+ IdentifierCaller_2 := '2';
+ _dataExchange.Operation.IsInitialized := true; //simulate intit from .net side
+ _dataExchange.SetFirstReadDone();
+
+ // needs to simulate + 2 cycles
+ THIS.RunContextMultipleltipleTimes(3);
+
+ //------------------------ INVOKE READ CONCURRENT ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsBusy());
+
+ ResultCaller_1 := _dataExchange.InvokeUpdate(caller_1, IdentifierCaller_1); // KICKING
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsReady());
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsBusy());
+
+ ResultCaller_2 := _dataExchange.InvokeUpdate(caller_2, IdentifierCaller_2); // WAITING
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+ AxUnit.Assert.Equal(ResultCaller_2,FALSE);
+
+ _context.Close();
+ //------------------------ INVOKE SUCCESEED - caller 1. ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsReady());
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsBusy());
+
+ ResultCaller_1 := _dataExchange.InvokeUpdate(caller_1, IdentifierCaller_1); // BUSY - OK
+ ResultCaller_2 := _dataExchange.InvokeUpdate(caller_2, IdentifierCaller_2); // WAITING
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,TRUE);
+ AxUnit.Assert.Equal(ResultCaller_2,FALSE);
+
+ _dataExchange.Operation.DoneSignature := _dataExchange.Operation.StartSignature; // remote task done
+
+ _context.Close();
+ //------------------------ DONE - caller 1. ------------------------
+ _context.Open();
+ _dataExchange.Run(_context); // SET DONE STATUS
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsDone());
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsReady());
+
+ ResultCaller_1 := _dataExchange.IsUpdateDone(caller_1); // is DONE
+ ResultCaller_2 := _dataExchange.InvokeUpdate(caller_2, IdentifierCaller_2); // still WAITING
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,TRUE);
+ AxUnit.Assert.Equal(ResultCaller_2,FALSE);
+ _context.Close();
+ //------------------------ +1 cycle gap between calls ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ //caller 1 finished not colled any more...
+ ResultCaller_2 := _dataExchange.InvokeUpdate(caller_2, IdentifierCaller_2); // still WAITING
+ AxUnit.Assert.Equal(FALSE, ResultCaller_2);
+ _context.Close();
+ //------------------------ +2 cycle gap between calls -----------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ ResultCaller_2 := _dataExchange.InvokeUpdate(caller_2, IdentifierCaller_2); // still WAITING
+ AxUnit.Assert.Equal(FALSE, ResultCaller_2);
+ _context.Close();
+ //------------------------ Kicking for caller 2 ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ ResultCaller_2 := _dataExchange.InvokeUpdate(caller_2, IdentifierCaller_2); // Kicking for caller 2
+ AxUnit.Assert.Equal(FALSE, ResultCaller_2);
+
+ _context.Close();
+ //------------------------ INVOKE SUCCESEED - caller 2. ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ ResultCaller_1 := _dataExchange.InvokeUpdate(caller_1, IdentifierCaller_1); // will wait
+ ResultCaller_2 := _dataExchange.InvokeUpdate(caller_2, IdentifierCaller_2); // invoke success
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_2);
+ AxUnit.Assert.Equal(FALSE, ResultCaller_1);
+ AxUnit.Assert.Equal(TRUE, ResultCaller_2);
+
+ _dataExchange.Operation.DoneSignature := _dataExchange.Operation.StartSignature; // remote task done
+ _context.Close();
+ //------------------------ DONE - caller 2. ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ ResultCaller_1 := _dataExchange.InvokeUpdate(caller_1, IdentifierCaller_1); // will wait
+ ResultCaller_2 := _dataExchange.IsUpdateDone(caller_2);
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_2);
+ AxUnit.Assert.Equal(FALSE, ResultCaller_1);
+ AxUnit.Assert.Equal(TRUE, ResultCaller_2);
+
+ _context.Close();
+
+ END_METHOD
+
+ {Test}
+ METHOD PUBLIC ConcurentCall_Read_test
+ VAR
+ IdentifierCaller_1 : STRING[254];
+ IdentifierCaller_2 : STRING[254];
+
+ ResultCaller_1 :BOOL;
+ ResultCaller_2 :BOOL;
+
+ startTime : LDATE_AND_TIME;
+ END_VAR
+ // initializtion
+ startTime := LDATE_AND_TIME#2023-11-11-11:11:11.111;
+ _rtc.SetNowUTC(startTime);
+ _context.InjectRtc(_rtc);
+ caller_1.Initialize(_context);
+ caller_2.Initialize(_context);
+ caller_1.Identity := _context.CreateIdentity();
+ caller_2.Identity := _context.CreateIdentity();
+ IdentifierCaller_1 := '1';
+ IdentifierCaller_2 := '2';
+ _dataExchange.Operation.IsInitialized := true; //simulate intit from .net side
+ _dataExchange.SetFirstReadDone();
+
+ // needs to simulate + 2 cycles
+ THIS.RunContextMultipleltipleTimes(3);
+
+ //------------------------ INVOKE CONCURRENT ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsReady());
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsBusy());
+
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1); // KICKING
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsReady());
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsBusy());
+
+ ResultCaller_2 := _dataExchange.InvokeRead(caller_2, IdentifierCaller_2); // WAITING
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,FALSE);
+ AxUnit.Assert.Equal(ResultCaller_2,FALSE);
+
+ _context.Close();
+ //------------------------ INVOKE SUCCESEED - caller 1. ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsReady());
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsBusy());
+
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1); // BUSY - OK
+ ResultCaller_2 := _dataExchange.InvokeRead(caller_2, IdentifierCaller_2); // WAITING
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,TRUE);
+ AxUnit.Assert.Equal(ResultCaller_2,FALSE);
+
+ _dataExchange.Operation.DoneSignature := _dataExchange.Operation.StartSignature; // remote task done
+
+ _context.Close();
+ //------------------------ DONE - caller 1. ------------------------
+ _context.Open();
+ _dataExchange.Run(_context); // SET DONE STATUS
+
+ AxUnit.Assert.Equal(TRUE, _dataExchange.Operation.IsDone());
+ AxUnit.Assert.Equal(FALSE, _dataExchange.Operation.IsReady());
+
+ ResultCaller_1 := _dataExchange.IsReadDone(caller_1); // is DONE
+ ResultCaller_2 := _dataExchange.InvokeRead(caller_2, IdentifierCaller_2); // still WAITING
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_1);
+ AxUnit.Assert.Equal(ResultCaller_1,TRUE);
+ AxUnit.Assert.Equal(ResultCaller_2,FALSE);
+ _context.Close();
+ //------------------------ +1 cycle gap between calls ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ //caller 1 finished not colled any more...
+ ResultCaller_2 := _dataExchange.InvokeRead(caller_2, IdentifierCaller_2); // still WAITING
+ AxUnit.Assert.Equal(FALSE, ResultCaller_2);
+ _context.Close();
+ //------------------------ +2 cycle gap between calls -----------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ ResultCaller_2 := _dataExchange.InvokeRead(caller_2, IdentifierCaller_2); // still WAITING
+ AxUnit.Assert.Equal(FALSE, ResultCaller_2);
+ _context.Close();
+ //------------------------ Kicking read for caller 2 ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ ResultCaller_2 := _dataExchange.InvokeRead(caller_2, IdentifierCaller_2); // Kicking for caller 2
+ AxUnit.Assert.Equal(FALSE, ResultCaller_2);
+
+ _context.Close();
+ //------------------------ INVOKE SUCCESEED - caller 2. ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1); // will wait
+ ResultCaller_2 := _dataExchange.InvokeRead(caller_2, IdentifierCaller_2); // invoke success
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_2);
+ AxUnit.Assert.Equal(FALSE, ResultCaller_1);
+ AxUnit.Assert.Equal(TRUE, ResultCaller_2);
+
+ _dataExchange.Operation.DoneSignature := _dataExchange.Operation.StartSignature; // remote task done
+ _context.Close();
+ //------------------------ DONE - caller 2. ------------------------
+ _context.Open();
+ _dataExchange.Run(_context);
+ ResultCaller_1 := _dataExchange.InvokeRead(caller_1, IdentifierCaller_1); // will wait
+ ResultCaller_2 := _dataExchange.IsReadDone(caller_2);
+
+ AxUnit.Assert.Equal(_dataExchange.Operation.DataEntityIdentifier, IdentifierCaller_2);
+ AxUnit.Assert.Equal(FALSE, ResultCaller_1);
+ AxUnit.Assert.Equal(TRUE, ResultCaller_2);
+
+ _context.Close();
+
+ END_METHOD
+
+
+ END_CLASS
+ END_NAMESPACE
+END_NAMESPACE
diff --git a/src/data/docs/AxoDataPersistentExchange.md b/src/data/docs/AxoDataPersistentExchange.md
new file mode 100644
index 000000000..884b77048
--- /dev/null
+++ b/src/data/docs/AxoDataPersistentExchange.md
@@ -0,0 +1,57 @@
+# AxoDataPersistentExchange
+
+Persistent data exchange allows the grouping of multiple primitive variables or properties assigned by an attribute into tag lists, on which repository operations can be performed.
+
+> [!IMPORTANT]
+> The main goal is to store the values of selected variables that are not on the same level in the program structure. It is important to retain them in case of a program restart. Therefore, this storage is suitable for scenarios such as remembering "only the Identifier" of process data settings or technology data, which can be loaded from another repository or source at startup.
+
+## Getting started
+
+### Label the requested variable as Persistent
+
+Anywhere in the structured code, use the persistent attribute `AXOpen.Data.PersistentAttribute("PersistentGroupName")` to mark a variable as persistent.
+
+[!code-smalltalk[](../app/src/Examples/AxoDataPersistentExchangeExample.st?name=PersistentAttribute)]
+
+### AX Snippet for attribute
+
+```
+ "attritubePersistentProperty":
+ {
+ "prefix": ["attPersistent","persistent"],
+ "scope": "st",
+ "body":[
+ "{#ix-attr:[AXOpen.Data.PersistentAttribute(\"\")]}",
+ "$0"
+ ],
+ "description": "The variable will be flagged as persistent, and the persistent data exchange will handle CRUD operations."
+ }
+```
+### Create an instance of the exchange manager
+
+Create an instance of the manager and call it in the Context.
+
+[!code-smalltalk[](../app/src/Examples/AxoDataPersistentExchangeExample.st?name=ContextDeclaration)]
+
+> [!NOTE]
+> Note that you can use multiple instances of the persistent manager, which can operate on different root objects that are initialized on the .NET side.
+
+### Usage in the controller
+
+In the case of saving variables to a repository, call the `InvokeUpdate()` method, which returns `true` if the invocation is successful. To monitor the completion status, use the `IsUpdateDone()` method.
+
+Other operations like `InvokeRead`, `InvokeUpdateAll`, `InvokeReadAll`, and `InvokeEntityExist` follow the same principle. These methods accept an `IAxoObject`, which uses the identity of the object to prevent concurrent calls. The object executing the first call is prioritized, and subsequent calls with a different caller will wait until the first caller has finished.
+
+
+> [!WARNING]
+> If the record does not exist, the loading task will end with an error. Therefore, it is necessary to ensure that the record exists, either by manual saving or by generating a new record.
+
+[!code-smalltalk[](../app/src/Examples/AxoDataPersistentExchangeExample.st?name=Usage)]
+
+### Data exchange initialization in .NET
+
+At this point, we have everything ready in the PLC.
+
+An instance of the Persistent Manager requires additional parameters for initialization. It needs to set up a repository where the data will be saved. The next parameter is the root object of the PLC tree from which it begins collecting persistent variables.
+
+[!code-csharp[](../app/ix-blazor/librarytemplate.blazor/Program.cs?name=SetUpAxoDataPersistentExchange)]
diff --git a/src/data/docs/COLUMNS.md b/src/data/docs/COLUMNS.md
index bc59f5b0d..0241d9749 100644
--- a/src/data/docs/COLUMNS.md
+++ b/src/data/docs/COLUMNS.md
@@ -2,11 +2,11 @@
There is a possibility to add custom columns if it is needed. You must add `AXOpen.Data.ColumnData` view as a child in `DataView`. The `BindingValue` must be set in `ColumnData` and contains a string representing the attribute name of custom columns. If you want to add a custom header name, you can set the name in `HeaderName` attribute. Also, there is an attribute to make the column not clickable, which is clickable by default. The example using all attributes:
-[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Testing.razor?name=CustomColumns)]
+[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Rendering.razor?name=CustomColumns)]
When adding data view manually, you will need to create ViewModel:
-[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Testing.razor?name=CustomColumnsCode)]
+[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Rendering.razor?name=CustomColumnsCode)]
> [!NOTE]
> When creating ViewModel, don't forget to provide AlertDialogService and AuthenticationProvider.
diff --git a/src/data/docs/EXPORT.md b/src/data/docs/EXPORT.md
index a29e02215..c6bb41ead 100644
--- a/src/data/docs/EXPORT.md
+++ b/src/data/docs/EXPORT.md
@@ -2,7 +2,7 @@
If you want to be able to export data, you must add `CanExport` attribute with `true` value. Like this:
-[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Testing.razor?name=Export)]
+[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Rendering.razor?name=Export)]
With this option, buttons for export and import data will appear. After clicking on the export button, the `.zip` file will be created, which contains all existing records. If you want to import data, you must upload `.zip` file with an equal data structure as we get in the export file.
diff --git a/src/data/docs/MODALVIEW.md b/src/data/docs/MODALVIEW.md
index 898231fa1..3cd7f8cc0 100644
--- a/src/data/docs/MODALVIEW.md
+++ b/src/data/docs/MODALVIEW.md
@@ -2,6 +2,6 @@
The Detail View is default shown like modal view. That means if you click on some record, the modal window with a detail view will be shown. If necessary, this option can be changed with `ModalDetailView` attribute. This change will show a detail view under the record table. Example with `ModalDetailView` attribute:
-[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Testing.razor?name=Modal)]
+[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Rendering.razor?name=Modal)]
![Not Modal detail view](assets/NotModalDetailView.png)
diff --git a/src/data/docs/VISUALIZATION.md b/src/data/docs/VISUALIZATION.md
index 71bdd1403..9ac393d5d 100644
--- a/src/data/docs/VISUALIZATION.md
+++ b/src/data/docs/VISUALIZATION.md
@@ -4,12 +4,12 @@
With `Command` presentation type, options exist for adding, editing, and deleting records.
-[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Testing.razor?name=CommandView)]
+[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Rendering.razor?name=CommandView)]
![Command](assets/Command.png)
If you use `Status` presentation type, data will be only displayed and cannot be manipulated.
-[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Testing.razor?name=StatusView)]
+[!code-smalltalk[](../app/ix-blazor/librarytemplate.blazor/Pages/Rendering.razor?name=StatusView)]
![Status](assets/Status.png)
\ No newline at end of file
diff --git a/src/data/docs/toc.yml b/src/data/docs/toc.yml
index 0122f5745..e29513f82 100644
--- a/src/data/docs/toc.yml
+++ b/src/data/docs/toc.yml
@@ -5,6 +5,8 @@
href: AXODATAEXCHANGE.md
- name: Data fragment exchange
href: AXODATAFRAGMENTEXCHANGE.md
+ - name: Data Persistent exchange
+ href: AxoDataPersistentExchange.md
- name: Usage
href: USAGE.md
- name: Security
diff --git a/src/data/src/AXOpen.Data/DataPersistentExchange/Attribute/PersistentAttribute.cs b/src/data/src/AXOpen.Data/DataPersistentExchange/Attribute/PersistentAttribute.cs
new file mode 100644
index 000000000..98d916803
--- /dev/null
+++ b/src/data/src/AXOpen.Data/DataPersistentExchange/Attribute/PersistentAttribute.cs
@@ -0,0 +1,37 @@
+namespace AXOpen.Data
+{
+ using System.Linq;
+
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
+ public class PersistentAttribute
+ : Attribute
+ {
+ ///
+ /// Creates new instance of
+ ///
+ public PersistentAttribute(params string[] groups)
+ {
+ var filterGroup = groups.Where(x => !string.IsNullOrEmpty(x) ).ToArray();
+
+ if (filterGroup == null)
+ Groups = new string[1] { "default" };
+ else
+ {
+ if (filterGroup.Length == 0)
+ {
+ Groups = new string[1] { "default" };
+ }
+ else
+ {
+ Groups = filterGroup;
+
+ }
+ }
+ }
+
+ ///
+ /// List of persistent groups on that it will be saved.
+ ///
+ public IEnumerable Groups { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/data/src/AXOpen.Data/DataPersistentExchange/AxoDataPersistentExchange.cs b/src/data/src/AXOpen.Data/DataPersistentExchange/AxoDataPersistentExchange.cs
new file mode 100644
index 000000000..33ebb324d
--- /dev/null
+++ b/src/data/src/AXOpen.Data/DataPersistentExchange/AxoDataPersistentExchange.cs
@@ -0,0 +1,416 @@
+using AXOpen.Base.Data;
+using AXSharp.Connector;
+
+namespace AXOpen.Data
+{
+ public partial class AxoDataPersistentExchange
+ {
+ ///
+ /// ROOT OBJECT
+ ///
+ private ITwinObject _root;
+
+ private const string DEFAULT_IDENTIFIER = "default";
+
+ ///
+ /// tracked all persistent tagss
+ ///
+ private List allTags = new();
+
+ ///
+ /// tags sorted in groups
+ ///
+ private Dictionary> tagsInGroups = new();
+
+ ///
+ /// Gets the list of collected group names from the tags grouped by their assigned groups.
+ ///
+ public List CollectedGroups
+ {
+ get
+ {
+ return tagsInGroups.Keys.ToList();
+ }
+ }
+
+ ///
+ /// repository that stored tag values
+ ///
+ private IRepository Repository;
+
+ #region ReadWrite to/from controller/PLC
+
+ ///
+ /// Reads tags from the PLC for a given group.
+ ///
+ /// The name of the group to read tags for.
+ /// Returns true if the read operation is successful; otherwise, false.
+ private async Task ReadTagsFromPlc(string group)
+ {
+ var tagsToRead = tagsInGroups[group];
+ if (tagsToRead != null)
+ {
+ await _root.GetConnector().ReadBatchAsync(tagsToRead);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Writes a batch of tag values to the PLC.
+ ///
+ /// The list of primitive tags to write.
+ /// Returns true if the write operation is successful; otherwise, false.
+ private async Task WriteTags(List primitives)
+ {
+ await _root.GetConnector().WriteBatchAsync(primitives);
+ return true;
+ }
+
+ #endregion ReadWrite to/from controller/PLC
+
+ #region Main Handling Method - Read Write
+
+ ///
+ /// Writes a persistent group of tags from the repository to the PLC.
+ ///
+ /// The group name of the tags to be written.
+ /// Returns true if the write operation is successful; otherwise, false.
+ public async Task WritePersistentGroupFromRepository(string group)
+ {
+ var recordFromRepo = Repository.Read(group);
+
+ List tagsToWrite = new List();
+
+ foreach (var tagFromRepo in recordFromRepo.Tags)
+ {
+ var ConnectedTags = allTags.Where(p => p.Symbol == tagFromRepo.Symbol);
+
+ if (!ConnectedTags.Any()) continue;
+
+ var ConnectedTag = ConnectedTags.First();
+
+ if (ConnectedTag == null) continue;
+
+ #pragma warning disable CS0612
+ ConnectedTag.SetTagCyclicValueUsingLethargicWrite(tagFromRepo);
+ #pragma warning restore CS0612
+
+ tagsToWrite.Add(ConnectedTag);
+ }
+
+ await WriteTags(tagsToWrite);
+ return true;
+ }
+
+ ///
+ /// Writes all persistent tags from the repository to the PLC.
+ ///
+ /// The group name of the tags to be written.
+ /// Returns true if the write operation is successful; otherwise, false.
+ public async Task WriteAllPersistentGroupsFromRepositoryToPlc()
+ {
+ List tagsToWrite = new List();
+ foreach (var groupName in this.CollectedGroups)
+ {
+ var recordFromRepo = Repository.Read(groupName);
+ AddTagsFromRecordToWrittenList(tagsToWrite, recordFromRepo);
+ }
+ await WriteTags(tagsToWrite);
+ return true;
+ }
+
+ private void AddTagsFromRecordToWrittenList(List tagsToWrite, PersistentRecord recordFromRepo)
+ {
+ foreach (var tagFromRepo in recordFromRepo.Tags)
+ {
+ var ConnectedTags = allTags.Where(p => p.Symbol == tagFromRepo.Symbol);
+
+ if (!ConnectedTags.Any()) continue;
+
+ var ConnectedTag = ConnectedTags.First();
+
+ if (ConnectedTag == null) continue;
+
+ ConnectedTag.SetTagCyclicValueUsingLethargicWrite(tagFromRepo);
+
+ tagsToWrite.Add(ConnectedTag);
+ }
+ }
+
+
+ ///
+ /// Updates a persistent group of tags to the repository after reading from the PLC.
+ ///
+ /// The group name of the persistent tags to be updated.
+ /// Returns true if the update operation is successful; otherwise, false.
+ public async Task UpdatePersistentGroupFromPlcToRepository(string persistentGroupName)
+ {
+ await ReadTagsFromPlc(persistentGroupName);
+ return UpdateReadedTagsToRepository(persistentGroupName);
+ }
+
+ private bool UpdateReadedTagsToRepository(string persistentGroupName)
+ {
+
+ var primitivesTagsInGroup = tagsInGroups[persistentGroupName];
+
+ if (primitivesTagsInGroup == null)
+ return false;
+
+ List NewTagValues = new List();
+
+ foreach (var tag in primitivesTagsInGroup)
+ {
+ var t = tag.AsNewTagObject();
+
+ NewTagValues.Add(t);
+ }
+
+ bool exist = Repository.Exists(persistentGroupName);
+
+ if (exist)
+ {
+ var recordFromRepository = Repository.Read(persistentGroupName);
+
+ foreach (var newtagValue in NewTagValues)
+ {
+ var TagsFromRepository = recordFromRepository.Tags.Where(p => p.Symbol == newtagValue.Symbol);
+
+ if (TagsFromRepository.Count() != 1)
+ {
+ recordFromRepository.Tags.RemoveAll(t => t.Symbol == newtagValue.Symbol);
+ recordFromRepository.Tags.Add(newtagValue);
+ continue;
+ }
+
+ var tagFromRepository = TagsFromRepository.First();
+
+ if (tagFromRepository.Value == null)
+ {
+ tagFromRepository.Value = newtagValue.Value;
+ }
+ else
+ {
+ Type repotype = tagFromRepository.Value.GetType();
+ Type tagtype = newtagValue.Value.GetType();
+
+ if (repotype == tagtype)
+ {
+ tagFromRepository.Value = newtagValue.Value;
+ }
+ else
+ {
+ recordFromRepository.Tags.Remove(tagFromRepository);
+ recordFromRepository.Tags.Add(newtagValue);
+ }
+ }
+ }
+
+ Repository.Update(persistentGroupName, recordFromRepository);
+ }
+ else
+ {
+ Repository.Create(persistentGroupName, new PersistentRecord()
+ {
+ DataEntityId = persistentGroupName,
+ Tags = NewTagValues
+ });
+ }
+ return true;
+ }
+
+
+ ///
+ /// Updates a persistent group of tags to the repository after reading from the PLC.
+ ///
+ /// The group name of the persistent tags to be updated.
+ /// Returns true if the update operation is successful; otherwise, false.
+ public async Task UpdateAllPersistentGroupsToRepository()
+ {
+ await _root.GetConnector().ReadBatchAsync(allTags); // read all tags from PLC
+
+ foreach (var groupName in this.CollectedGroups)
+ {
+ UpdateReadedTagsToRepository(groupName);
+ }
+
+ return true;
+ }
+
+
+ #endregion Main Handling Method - Read Write
+
+ #region Data Exchange Implementation
+
+ ///
+ /// Initializes the remote data exchange by setting up the persistent root object and repository.
+ ///
+ /// The root object for the data exchange.
+ /// The repository to store the persistent records.
+ public async Task InitializeRemoteDataExchange(ITwinObject persistetnRootObject, IRepository repository)
+ {
+ this._root = persistetnRootObject;
+ Repository = repository;
+
+ this.CollectPersistentTags(this._root);
+
+ await InitializeRemoteDataExchange();
+ }
+
+ ///
+ /// Performs the actual initialization of the remote data exchange.
+ ///
+ public async Task InitializeRemoteDataExchange()
+ {
+ Operation.InitializeExclusively(Handle);
+ //await this.WriteAsync();
+ }
+
+ ///
+ /// Deinitializes the remote data exchange.
+ ///
+ public async Task DeInitializeRemoteDataExchange()
+ {
+ Operation.DeInitialize();
+ //await this.WriteAsync();
+ }
+
+ private async Task Handle()
+ {
+ await Operation.ReadAsync();
+ var operation = (ePersistentOperation)Operation.CrudOperation.LastValue;
+ var identifier = Operation.DataEntityIdentifier.LastValue;
+ if (string.IsNullOrEmpty(identifier))
+ {
+ identifier = DEFAULT_IDENTIFIER; // default persistent group
+ }
+
+ switch (operation)
+ {
+ case ePersistentOperation.Read:
+ await this.WritePersistentGroupFromRepository(identifier);
+ break;
+
+ case ePersistentOperation.Update:
+ await this.UpdatePersistentGroupFromPlcToRepository(identifier);
+ break;
+
+ case ePersistentOperation.ReadAll:
+
+ if (!Repository.Exists(DEFAULT_IDENTIFIER)) // repo is empty
+ {
+ await this.UpdateAllPersistentGroupsToRepository(); // create records from online
+ }
+
+ await this.WriteAllPersistentGroupsFromRepositoryToPlc();
+
+ break;
+
+ case ePersistentOperation.UpdateAll:
+ await this.UpdateAllPersistentGroupsToRepository();
+ break;
+
+ case ePersistentOperation.EntityExist:
+ var result = await this.RemoteEntityExist(identifier);
+ await Operation._exist.SetAsync(result);
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ ///
+ public async Task RemoteRead(string identifier)
+ {
+ try
+ {
+ var record = Repository.Read(identifier);
+ this.WritePersistentGroupFromRepository(identifier);
+
+ return true;
+ }
+ catch (Exception exception)
+ {
+ throw;
+ }
+ }
+
+ ///
+ public async Task RemoteEntityExist(string identifier)
+ {
+ return Repository.Exists(identifier);
+ }
+
+ #endregion Data Exchange Implementation
+
+ #region Collect persistent tags
+
+ private void CollectPersistentTags(ITwinElement member)
+ {
+ AddPersistentMember(member);
+ }
+
+ private void AddPersistentMember(ITwinElement member)
+ {
+ if (member.HasAttribute())
+ {
+ if (member is ITwinPrimitive)
+ {
+ InsertPersistentPrimitive(member as ITwinPrimitive);
+ }
+ else
+ {
+ InsertPersistentMember(member as ITwinObject);
+ }
+ }
+ if (member is ITwinObject)
+ {
+ foreach (var item in (member as ITwinObject).GetKids())
+ {
+ AddPersistentMember(item);
+ }
+ }
+ }
+
+ private void InsertPersistentMember(ITwinObject member)
+ {
+ var toGroups = member.GetAttribute().Groups;
+
+ var alltags = member.RetrievePrimitives();
+
+ foreach (var item in alltags)
+ {
+ InsertPersistentPrimitive(item, toGroups);
+ }
+ }
+
+ private void InsertPersistentPrimitive(ITwinPrimitive primitive)
+ {
+ InsertPersistentPrimitive(primitive, primitive.GetAttribute().Groups);
+ }
+
+ private void InsertPersistentPrimitive(ITwinPrimitive primitive, IEnumerable toGroups)
+ {
+ foreach (var toGroup in toGroups)
+ {
+ if (tagsInGroups.TryGetValue(toGroup, out List list))
+ {
+ list.Add(primitive);
+ allTags.Add(primitive);
+ }
+ else
+ {
+ tagsInGroups[toGroup] = new List { primitive };
+ allTags.Add(primitive);
+ }
+ }
+ }
+
+ #endregion Collect persistent tags
+ }
+}
\ No newline at end of file
diff --git a/src/data/src/AXOpen.Data/DataPersistentExchange/PersistentRecord.cs b/src/data/src/AXOpen.Data/DataPersistentExchange/PersistentRecord.cs
new file mode 100644
index 000000000..2b0cfe915
--- /dev/null
+++ b/src/data/src/AXOpen.Data/DataPersistentExchange/PersistentRecord.cs
@@ -0,0 +1,14 @@
+using AXOpen.Base.Data;
+using System.Collections.Generic;
+
+namespace AXOpen.Data
+{
+ public class PersistentRecord : IBrowsableDataObject
+ {
+ private string _DataEntityId = "";
+ public string DataEntityId { get => _DataEntityId; set => _DataEntityId = value; }
+ public dynamic RecordId { set; get; }
+
+ public List Tags = new();
+ }
+}
\ No newline at end of file
diff --git a/src/data/src/AXOpen.Data/DataPersistentExchange/TagObject.cs b/src/data/src/AXOpen.Data/DataPersistentExchange/TagObject.cs
new file mode 100644
index 000000000..1db566271
--- /dev/null
+++ b/src/data/src/AXOpen.Data/DataPersistentExchange/TagObject.cs
@@ -0,0 +1,9 @@
+namespace AXOpen.Data
+{
+ public class TagObject
+ {
+ public string Symbol { get; set; }
+ public string ValueType { get; set; }
+ public dynamic Value { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/data/src/AXOpen.Data/DataPersistentExchange/TwinPrimitiveExtension.cs b/src/data/src/AXOpen.Data/DataPersistentExchange/TwinPrimitiveExtension.cs
new file mode 100644
index 000000000..5a5e42741
--- /dev/null
+++ b/src/data/src/AXOpen.Data/DataPersistentExchange/TwinPrimitiveExtension.cs
@@ -0,0 +1,74 @@
+using AXSharp.Connector;
+
+namespace AXOpen.Data
+{
+ public static class TwinPrimitiveExtension
+ {
+ public const string CyclicPropertyName = "Cyclic";
+ public const string LethargicWriteMethodName = "LethargicWrite";
+
+ ///
+ /// Creates a new from an .
+ ///
+ /// The twin primitive to create a tag object from.
+ /// A new instance.
+ public static TagObject AsNewTagObject(this ITwinPrimitive primitive)
+ {
+ var tagObject = new TagObject();
+ var onlinerType = primitive.GetType();
+ var propertyInfo = onlinerType.GetProperty(CyclicPropertyName);
+
+ tagObject.ValueType = propertyInfo.PropertyType.FullName;
+ tagObject.Symbol = primitive.Symbol;
+ tagObject.Value = propertyInfo.GetValue(primitive);
+
+ return tagObject;
+ }
+
+ ///
+ /// Sets the value of a twin primitive using the 'LethargicWrite' method to avoid inconsistencies in cyclic writing.
+ ///
+ ///
+ /// Writing through the Cyclic property adds the variable to cyclic writing, which may lead to inconsistencies.
+ /// Therefore, this method uses 'LethargicWrite' to set the value.
+ ///
+ /// The twin primitive to update.
+ /// The tag object containing the value to set.
+ /// Thrown if the 'Cyclic' property or 'LethargicWrite' method is not found.
+ public static void SetTagCyclicValueUsingLethargicWrite(this ITwinPrimitive primitive, TagObject tagFromRepo)
+ {
+ var lethargicWriteMethodInfo = primitive.GetType().GetMethod(LethargicWriteMethodName);
+
+ if (lethargicWriteMethodInfo != null)
+ {
+ var cyclicPropertyInfo = primitive.GetType().GetProperty(CyclicPropertyName);
+ if (cyclicPropertyInfo == null)
+ {
+ throw new ArgumentException($"Property Cyclic was not found on tag {tagFromRepo.Symbol}!");
+ }
+
+ var castedValue = Convert.ChangeType(tagFromRepo.Value, cyclicPropertyInfo.PropertyType);
+ lethargicWriteMethodInfo.Invoke(primitive, new[] { castedValue });
+ }
+ else
+ {
+ throw new ArgumentException($"Method {LethargicWriteMethodName} was not found on tag {tagFromRepo.Symbol}!");
+ }
+ }
+
+ //public static void SetTagCyclicValue(this ITwinPrimitive primitive, TagObject tagFromRepo)
+ //{
+ // var cyclicPropertyInfo = primitive.GetType().GetProperty(CyclicPropertyName);
+
+ // if (cyclicPropertyInfo != null && cyclicPropertyInfo.CanWrite)
+ // {
+ // var castedValue = Convert.ChangeType(tagFromRepo.Value, cyclicPropertyInfo.PropertyType);
+ // cyclicPropertyInfo.SetValue(primitive, castedValue);
+ // }
+ // else
+ // {
+ // throw new ArgumentException($"Property Cyclic was not found on tag {tagFromRepo.Symbol}!");
+ // }
+ //}
+ }
+}
\ No newline at end of file
diff --git a/src/data/src/repositories/Json/RepositoryExtensions.cs b/src/data/src/repositories/Json/RepositoryExtensions.cs
index 69f4e6e6c..8c6c90bd1 100644
--- a/src/data/src/repositories/Json/RepositoryExtensions.cs
+++ b/src/data/src/repositories/Json/RepositoryExtensions.cs
@@ -3,11 +3,11 @@
using AXOpen.Data;
using AXOpen.Data.Json;
-namespace Ix.Repository.Json
+namespace AXOpen.Data.Json
{
public static class Repository
{
- public static IRepository Factory(JsonRepositorySettings parameters) where T : IBrowsableDataObject
+ public static IRepository Factory(this JsonRepositorySettings parameters) where T : IBrowsableDataObject
{
try
{
diff --git a/src/data/src/repositories/MongoDb/Mongo/FloatTruncationSerializer.cs b/src/data/src/repositories/MongoDb/Mongo/FloatTruncationSerializer.cs
index 60580ed40..da1e36b5a 100644
--- a/src/data/src/repositories/MongoDb/Mongo/FloatTruncationSerializer.cs
+++ b/src/data/src/repositories/MongoDb/Mongo/FloatTruncationSerializer.cs
@@ -28,4 +28,20 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
context.Writer.WriteDouble(Math.Round(value,10));
}
}
+
+
+ public class DateOnlySerializer : SerializerBase
+ {
+ public override DateOnly Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
+ {
+ var date = BsonSerializer.Deserialize(context.Reader);
+ return DateOnly.FromDateTime(date);
+ }
+
+ public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateOnly value)
+ {
+ BsonSerializer.Serialize(context.Writer, value.ToDateTime(new TimeOnly(0, 0)));
+ }
+ }
+
}
diff --git a/src/data/src/repositories/MongoDb/Mongo/MongoDbRepositorySettings.cs b/src/data/src/repositories/MongoDb/Mongo/MongoDbRepositorySettings.cs
index 6bba995ab..c273b1c98 100644
--- a/src/data/src/repositories/MongoDb/Mongo/MongoDbRepositorySettings.cs
+++ b/src/data/src/repositories/MongoDb/Mongo/MongoDbRepositorySettings.cs
@@ -172,6 +172,8 @@ private static void SetupSerialisationAndMapping()
{
BsonSerializer.RegisterSerializer(typeof(UInt64), new UInt64Serializer(BsonType.Int64, new RepresentationConverter(true, false)));
BsonSerializer.RegisterSerializer(typeof(UInt32), new UInt32Serializer(BsonType.Int64, new RepresentationConverter(true, false)));
+
+ BsonSerializer.RegisterSerializer(new DateOnlySerializer());
BsonSerializer.RegisterSerializer(DateTimeSerializer.LocalInstance);
BsonSerializer.RegisterSerializer(typeof(float), new FloatTruncationSerializer());
var objectSerializer = new ObjectSerializer(type => ObjectSerializer.DefaultAllowedTypes(type) || type.FullName.Contains("System.DateOnly"));
diff --git a/src/data/src/repositories/MongoDb/RepositoryExtensions.cs b/src/data/src/repositories/MongoDb/RepositoryExtensions.cs
index bdcf1eb04..66dc83e78 100644
--- a/src/data/src/repositories/MongoDb/RepositoryExtensions.cs
+++ b/src/data/src/repositories/MongoDb/RepositoryExtensions.cs
@@ -6,7 +6,21 @@ namespace AXOpen.Data.MongoDb
{
public static class Repository
{
- public static IRepository Factory(MongoDbRepositorySettings parameters) where T : IBrowsableDataObject
+ private static string MongoConnectionString { get; set; }
+ private static string MongoDatabaseName { get; set; }
+ private static MongoDbCredentials Credentials { get; set; }
+
+ public static void InitializeFactory(string mongoConnectionString = "mongodb://localhost:27017",
+ string mongoDatabaseName = "AxOpenData",
+ string user = "user",
+ string userpw = "userpwd")
+ {
+ MongoConnectionString = mongoConnectionString;
+ MongoDatabaseName = mongoDatabaseName;
+ Credentials = new MongoDbCredentials(MongoDatabaseName, user, userpw);
+ }
+
+ public static IRepository Factory(this MongoDbRepositorySettings parameters) where T : IBrowsableDataObject
{
try
{
@@ -14,10 +28,26 @@ public static IRepository Factory(MongoDbRepositorySettings parameters)
}
catch (Exception ex)
{
-
throw new Exception($"Creation of MongoDb repository failed. Check number, type and value of parameters. For detail see inner exception.", ex);
}
+ }
+ public static IRepository Factory(string collectionName) where T : IBrowsableDataObject
+ {
+ try
+ {
+ var settings = new MongoDbRepositorySettings(
+ connectionString: MongoConnectionString,
+ databaseName: MongoDatabaseName,
+ collectionName: collectionName,
+ credentials: Credentials);
+
+ return new MongoDbRepository(settings);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception($"Creation of MongoDb repository failed. Check number, type and value of parameters. For detail see inner exception.", ex);
+ }
}
}
}
diff --git a/src/data/tests/AXOpen.Data.Exporters.ExcelTests/FragmentData/ExcelFragmentTests.cs b/src/data/tests/AXOpen.Data.Exporters.ExcelTests/FragmentData/ExcelFragmentTests.cs
index f695306eb..895a0f9d5 100644
--- a/src/data/tests/AXOpen.Data.Exporters.ExcelTests/FragmentData/ExcelFragmentTests.cs
+++ b/src/data/tests/AXOpen.Data.Exporters.ExcelTests/FragmentData/ExcelFragmentTests.cs
@@ -40,7 +40,7 @@ public async void ExportFragmentTest()
await sut.Set.Set.ComesFrom.SetAsync(10);
await sut.Set.Set.GoesTo.SetAsync(20);
await sut.Manip.Set.CounterDelay.SetAsync(20);
- sut.RemoteCreate("hey remote create");
+ await sut.RemoteCreate("hey remote create");
var shared = sut.Set.DataRepository.Read("hey remote create");
Assert.Equal(10, shared.ComesFrom);
@@ -80,12 +80,12 @@ public async void ExportComplexFragmentTest()
await sut.Set.Set.ComesFrom.SetAsync(10);
await sut.Set.Set.GoesTo.SetAsync(11);
await sut.Manip.Set.CounterDelay.SetAsync(12);
- sut.RemoteCreate("first");
+ await sut.RemoteCreate("first");
await sut.Set.Set.ComesFrom.SetAsync(20);
await sut.Set.Set.GoesTo.SetAsync(21);
await sut.Manip.Set.CounterDelay.SetAsync(22);
- sut.RemoteCreate("second");
+ await sut.RemoteCreate("second");
var shared = sut.Set.DataRepository.Read("first");
Assert.Equal(10, shared.ComesFrom);
diff --git a/src/data/tests/AXOpen.Data.Tests/PersistentExchange/AxoDataPersistentExchangeTests.cs b/src/data/tests/AXOpen.Data.Tests/PersistentExchange/AxoDataPersistentExchangeTests.cs
new file mode 100644
index 000000000..df2ce28f7
--- /dev/null
+++ b/src/data/tests/AXOpen.Data.Tests/PersistentExchange/AxoDataPersistentExchangeTests.cs
@@ -0,0 +1,238 @@
+using AXOpen.Data.InMemory;
+using AXSharp.Connector;
+using Microsoft.VisualBasic;
+using NSubstitute;
+
+namespace AXOpen.Data.Persistent.Tests
+{
+ public class AxoDataPersistentExchangeTests
+ {
+ public class OnlineMockData : AxoDataPersistentExchangeExample.PersistentRootObject
+ {
+ public OnlineMockData(ITwinObject parent, string readableTail, string symbolTail) : base(parent, readableTail, symbolTail)
+ {
+ }
+ }
+
+ [Fact()]
+ public async void CreateInRepositoryTest()
+ {
+ const string PersistentGroupName = "default";
+
+ var parent = NSubstitute.Substitute.For();
+ parent.GetConnector().Returns(AXSharp.Connector.ConnectorAdapterBuilder.Build().CreateDummy().GetConnector(null));
+
+ var data = new OnlineMockData(parent, "a", "b");
+ var repo = new InMemoryRepository();
+ //var sut = new AXOpen.Data.AxoDataPersistentExchange(parent, "perExchange", "PerExchange");
+ //sut.InitializeRemoteDataExchange(data, repo);
+
+ PersistentRecord pr = new AXOpen.Data.PersistentRecord() { DataEntityId = PersistentGroupName };
+
+ pr.Tags.Add(new TagObject() { Symbol = data.PersistentVariable_1.Symbol, Value = 10 });
+ pr.Tags.Add(new TagObject() { Symbol = data.PersistentVariable_2.Symbol, Value = 20 });
+ pr.Tags.Add(new TagObject() { Symbol = data.NotPersistentVariable.Symbol, Value = true });
+
+ repo.Create(PersistentGroupName, pr);
+
+ Assert.Equal(1, repo.Count);
+ Assert.Equal(PersistentGroupName, repo.Queryable.First().DataEntityId);
+ Assert.Equal(3, repo.Queryable.First().Tags.Count());
+
+ Assert.Equal(data.PersistentVariable_1.Symbol, repo.Queryable.First().Tags[0].Symbol);
+ Assert.Equal(data.PersistentVariable_2.Symbol, repo.Queryable.First().Tags[1].Symbol);
+ Assert.Equal(data.NotPersistentVariable.Symbol, repo.Queryable.First().Tags[2].Symbol);
+
+ Assert.Equal(10, repo.Queryable.First().Tags[0].Value);
+ Assert.Equal(20, repo.Queryable.First().Tags[1].Value);
+ Assert.Equal(true, repo.Queryable.First().Tags[2].Value);
+ }
+
+ [Fact()]
+ public async void ReadFromRepositoryTest()
+ {
+ const string PersistentGroupName = "default";
+ const string PersistentGroupName_1 = "1";
+
+ var parent = NSubstitute.Substitute.For();
+ parent.GetConnector().Returns(AXSharp.Connector.ConnectorAdapterBuilder.Build().CreateDummy().GetConnector(null));
+
+ var data = new OnlineMockData(parent, "a", "b");
+ var repo = new InMemoryRepository();
+ // var sut = new AXOpen.Data.AxoDataPersistentExchange(parent, "perExchange", "PerExchange");
+ // await sut.InitializeRemoteDataExchange(data, repo);
+
+ PersistentRecord pr = new AXOpen.Data.PersistentRecord() { DataEntityId = PersistentGroupName };
+ pr.Tags.Add(new TagObject() { Symbol = data.PersistentVariable_1.Symbol, Value = 10 });
+ pr.Tags.Add(new TagObject() { Symbol = data.PersistentVariable_2.Symbol, Value = 20 });
+ pr.Tags.Add(new TagObject() { Symbol = data.NotPersistentVariable.Symbol, Value = false });
+
+ repo.Create(PersistentGroupName, pr);
+
+ PersistentRecord pr_1 = new AXOpen.Data.PersistentRecord() { DataEntityId = PersistentGroupName_1 };
+ pr.Tags.Add(new TagObject() { Symbol = data.PersistentVariable_1.Symbol, Value = 110 });
+ pr.Tags.Add(new TagObject() { Symbol = data.PersistentVariable_2.Symbol, Value = 120 });
+ pr.Tags.Add(new TagObject() { Symbol = data.NotPersistentVariable.Symbol, Value = true });
+
+ repo.Create(PersistentGroupName_1, pr_1);
+
+ var recordFromRepo = repo.Read(PersistentGroupName);
+
+ Assert.Equal(2, repo.Count);
+ Assert.Equal(PersistentGroupName, recordFromRepo.DataEntityId);
+ Assert.False(recordFromRepo.Tags.Where(p => p.Symbol == data.NotPersistentVariable.Symbol).First().Value);
+ Assert.Equal(10, recordFromRepo.Tags.Where(p => p.Symbol == data.PersistentVariable_1.Symbol).First().Value);
+ Assert.Equal(20, recordFromRepo.Tags.Where(p => p.Symbol == data.PersistentVariable_2.Symbol).First().Value);
+ }
+
+ [Fact()]
+ public async void RemoteRead_ShouldWriteFromRepositoryToController()
+ {
+ return;
+ // TODO : needs to be fix DummyConnector for lethargicWrite usage
+
+ const string PersistentGroupName = "default";
+
+ var parent = NSubstitute.Substitute.For();
+ parent.GetConnector().Returns(AXSharp.Connector.ConnectorAdapterBuilder.Build().CreateDummy().GetConnector(null));
+
+ var data = new OnlineMockData(parent, "a", "b");
+ var repo = new InMemoryRepository();
+ var sut = new AXOpen.Data.AxoDataPersistentExchange(parent, "perExchange", "PerExchange");
+ sut.InitializeRemoteDataExchange(data, repo);
+
+ PersistentRecord pr = new AXOpen.Data.PersistentRecord() { DataEntityId = PersistentGroupName };
+
+ pr.Tags.Add(new TagObject() { Symbol = data.PersistentVariable_1.Symbol, Value = 10 });
+ pr.Tags.Add(new TagObject() { Symbol = data.PersistentVariable_2.Symbol, Value = 20 });
+ pr.Tags.Add(new TagObject() { Symbol = data.NotPersistentVariable.Symbol, Value = false });
+
+ repo.Create(PersistentGroupName, pr);
+
+ data.NotPersistentVariable.Cyclic = true;
+ data.PersistentVariable_1.Cyclic = 100;
+ data.PersistentVariable_2.Cyclic = 100;
+
+ await data.WriteAsync();
+
+ await sut.WritePersistentGroupFromRepository(PersistentGroupName);
+
+ Assert.False(data.NotPersistentVariable.Cyclic);
+ Assert.Equal(10, data.PersistentVariable_1.Cyclic);
+ Assert.Equal(20, data.PersistentVariable_2.Cyclic);
+ }
+
+ [Fact()]
+ public async void RemoteUpadate_ShouldWriteToRpositoryFromController()
+ {
+ const string PersistentGroupName = "default";
+
+ var parent = NSubstitute.Substitute.For();
+ parent.GetConnector().Returns(AXSharp.Connector.ConnectorAdapterBuilder.Build().CreateDummy().GetConnector(null));
+
+ var data = new OnlineMockData(parent, "a", "b");
+ var repo = new InMemoryRepository();
+ var sut = new AXOpen.Data.AxoDataPersistentExchange(parent, "perExchange", "PerExchange");
+
+ await sut.InitializeRemoteDataExchange(data, repo);
+
+ data.NotPersistentVariable.Cyclic = true;
+ data.PersistentVariable_1.Cyclic = 10;
+ data.PersistentVariable_2.Cyclic = 20;
+ await data.WriteAsync();
+
+ await sut.UpdatePersistentGroupFromPlcToRepository(PersistentGroupName);
+
+ PersistentRecord pr = repo.Read(PersistentGroupName);
+
+ var TagValue_PersistentVariable_1 = pr.Tags.Where(p => p.Symbol == data.PersistentVariable_1.Symbol).First().Value;
+ var TagValue_PersistentVariable_2 = pr.Tags.Where(p => p.Symbol == data.PersistentVariable_2.Symbol).First().Value;
+ var TagValue_NotPersistentVariable_Exist = pr.Tags.Where(p => p.Symbol == data.NotPersistentVariable.Symbol).Any();
+
+ Assert.False(TagValue_NotPersistentVariable_Exist);
+ Assert.Equal(10, TagValue_PersistentVariable_1);
+ Assert.Equal(20, TagValue_PersistentVariable_2);
+ }
+
+ [Fact()]
+ public async void CollectPersistentVariablesTest()
+ {
+ const string DefaultPersistentGroupName = "default";
+ const string PersistentGroupName_1 = "1";
+ const string PersistentGroupName_2 = "2";
+
+ var parent = NSubstitute.Substitute.For();
+ parent.GetConnector().Returns(AXSharp.Connector.ConnectorAdapterBuilder.Build().CreateDummy().GetConnector(null));
+
+ var data = new OnlineMockData(parent, "a", "b");
+ var repo = new InMemoryRepository();
+ var sut = new AXOpen.Data.AxoDataPersistentExchange(parent, "perExchange", "PerExchange");
+
+ await sut.InitializeRemoteDataExchange(data, repo);
+
+ var groups = sut.CollectedGroups;
+
+ Assert.Equal(3, groups.Count);
+ Assert.Equal(DefaultPersistentGroupName, groups[0]);
+ Assert.Equal(PersistentGroupName_1, groups[1]);
+ Assert.Equal(PersistentGroupName_2, groups[2]);
+
+ foreach (var group in groups)
+ {
+ await sut.UpdatePersistentGroupFromPlcToRepository(group);
+ }
+
+ var group_default = repo.Read(groups[0]);
+ var group_1 = repo.Read(groups[1]);
+ var group_2 = repo.Read(groups[2]);
+
+ Assert.Equal(2, group_default.Tags.Count());
+ Assert.Equal(1, group_1.Tags.Count());
+ Assert.Equal(28, group_2.Tags.Count());
+ }
+
+ [Fact()]
+ public async void InitializeRemoteDataExchange_ShouldInitializeRPC_Calls_WithGivenRepository()
+ {
+ const string PersistentGroupName = "default";
+
+ var parent = NSubstitute.Substitute.For();
+ parent.GetConnector().Returns(AXSharp.Connector.ConnectorAdapterBuilder.Build().CreateDummy().GetConnector(null));
+
+ var data = new OnlineMockData(parent, "a", "b");
+ var repo = new InMemoryRepository();
+ var sut = new AXOpen.Data.AxoDataPersistentExchange(parent, "perExchange", "PerExchange");
+
+ await sut.InitializeRemoteDataExchange(data, repo);
+
+ await sut.Operation.DataEntityIdentifier.SetAsync(PersistentGroupName);
+ sut.Operation.StartTimeStamp.Cyclic = DateAndTime.Now;
+
+ Assert.True(await sut.Operation.IsInitialized.GetAsync());
+ }
+
+ [Fact()]
+ public async void DeInitializeRemoteDataExchange_ShouldInitializeRPC_Calls()
+ {
+ const string PersistentGroupName = "default";
+
+ var parent = NSubstitute.Substitute.For();
+ parent.GetConnector().Returns(AXSharp.Connector.ConnectorAdapterBuilder.Build().CreateDummy().GetConnector(null));
+
+ var data = new OnlineMockData(parent, "a", "b");
+ var repo = new InMemoryRepository();
+ var sut = new AXOpen.Data.AxoDataPersistentExchange(parent, "perExchange", "PerExchange");
+
+ await sut.InitializeRemoteDataExchange(data, repo);
+
+ await sut.Operation.DataEntityIdentifier.SetAsync(PersistentGroupName);
+ sut.Operation.StartTimeStamp.Cyclic = DateAndTime.Now;
+
+ Assert.True(await sut.Operation.IsInitialized.GetAsync());
+
+ await sut.DeInitializeRemoteDataExchange();
+
+ Assert.False(await sut.Operation.IsInitialized.GetAsync());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/data/tests/AXOpen.Data.Tests/PersistentExchange/Mocks.cs b/src/data/tests/AXOpen.Data.Tests/PersistentExchange/Mocks.cs
new file mode 100644
index 000000000..d3d20e47c
--- /dev/null
+++ b/src/data/tests/AXOpen.Data.Tests/PersistentExchange/Mocks.cs
@@ -0,0 +1,1007 @@
+using AXSharp.Connector.ValueTypes;
+using AXSharp.Connector;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AXOpen.Data;
+using AXSharp.Connector.Localizations;
+
+namespace AxoDataPersistentExchangeExample
+{
+ public partial class PersistentRootObject : AXSharp.Connector.ITwinObject
+ {
+ public OnlinerBool NotPersistentVariable { get; }
+
+ [Persistent()]
+ public OnlinerInt PersistentVariable_1 { get; }
+
+ [Persistent("default", "1")]
+ public OnlinerInt PersistentVariable_2 { get; }
+
+ [Container(Layout.Stack)]
+ [Group(GroupLayout.GroupBox)]
+ public AxoDataPersistentExchangeExample.ObjectWithPersistentMember PropertyWithPersistentMember { get; }
+
+ partial void PreConstruct(AXSharp.Connector.ITwinObject parent, string readableTail, string symbolTail);
+ partial void PostConstruct(AXSharp.Connector.ITwinObject parent, string readableTail, string symbolTail);
+ public PersistentRootObject(AXSharp.Connector.ITwinObject parent, string readableTail, string symbolTail)
+ {
+ Symbol = AXSharp.Connector.Connector.CreateSymbol(parent.Symbol, symbolTail);
+ this.@SymbolTail = symbolTail;
+ this.@Connector = parent.GetConnector();
+ this.@Parent = parent;
+ HumanReadable = AXSharp.Connector.Connector.CreateHumanReadable(parent.HumanReadable, readableTail);
+ PreConstruct(parent, readableTail, symbolTail);
+ NotPersistentVariable = @Connector.ConnectorAdapter.AdapterFactory.CreateBOOL(this, "NotPersistentVariable", "NotPersistentVariable");
+ PersistentVariable_1 = @Connector.ConnectorAdapter.AdapterFactory.CreateINT(this, "PersistentVariable_1 (default pg.))", "PersistentVariable_1");
+ PersistentVariable_1.AttributeName = "PersistentVariable_1 (default pg.))";
+ PersistentVariable_2 = @Connector.ConnectorAdapter.AdapterFactory.CreateINT(this, "PersistentVariable_2 (default & 1 pg.)", "PersistentVariable_2");
+ PersistentVariable_2.AttributeName = "PersistentVariable_2 (default & 1 pg.)";
+ PropertyWithPersistentMember = new AxoDataPersistentExchangeExample.ObjectWithPersistentMember(this, "PropertyWithPersistentMember", "PropertyWithPersistentMember");
+ PropertyWithPersistentMember.AttributeName = "PropertyWithPersistentMember";
+ parent.AddChild(this);
+ parent.AddKid(this);
+ PostConstruct(parent, readableTail, symbolTail);
+ }
+
+ public async virtual Task OnlineToPlain()
+ {
+ return await (dynamic)this.OnlineToPlainAsync();
+ }
+
+ public async Task OnlineToPlainAsync()
+ {
+ Pocos.AxoDataPersistentExchangeExample.PersistentRootObject plain = new Pocos.AxoDataPersistentExchangeExample.PersistentRootObject();
+ await this.ReadAsync();
+ plain.NotPersistentVariable = NotPersistentVariable.LastValue;
+ plain.PersistentVariable_1 = PersistentVariable_1.LastValue;
+ plain.PersistentVariable_2 = PersistentVariable_2.LastValue;
+#pragma warning disable CS0612
+ plain.PropertyWithPersistentMember = await PropertyWithPersistentMember._OnlineToPlainNoacAsync();
+#pragma warning restore CS0612
+ return plain;
+ }
+
+ [Obsolete("This method should not be used if you indent to access the controllers data. Use `OnlineToPlain` instead.")]
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public async Task _OnlineToPlainNoacAsync()
+ {
+ Pocos.AxoDataPersistentExchangeExample.PersistentRootObject plain = new Pocos.AxoDataPersistentExchangeExample.PersistentRootObject();
+ plain.NotPersistentVariable = NotPersistentVariable.LastValue;
+ plain.PersistentVariable_1 = PersistentVariable_1.LastValue;
+ plain.PersistentVariable_2 = PersistentVariable_2.LastValue;
+#pragma warning disable CS0612
+ plain.PropertyWithPersistentMember = await PropertyWithPersistentMember._OnlineToPlainNoacAsync();
+#pragma warning restore CS0612
+ return plain;
+ }
+
+ [Obsolete("This method should not be used if you indent to access the controllers data. Use `OnlineToPlain` instead.")]
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ protected async Task _OnlineToPlainNoacAsync(Pocos.AxoDataPersistentExchangeExample.PersistentRootObject plain)
+ {
+ plain.NotPersistentVariable = NotPersistentVariable.LastValue;
+ plain.PersistentVariable_1 = PersistentVariable_1.LastValue;
+ plain.PersistentVariable_2 = PersistentVariable_2.LastValue;
+#pragma warning disable CS0612
+ plain.PropertyWithPersistentMember = await PropertyWithPersistentMember._OnlineToPlainNoacAsync();
+#pragma warning restore CS0612
+ return plain;
+ }
+
+ public async virtual Task PlainToOnline(T plain)
+ {
+ await this.PlainToOnlineAsync((dynamic)plain);
+ }
+
+ public async Task> PlainToOnlineAsync(Pocos.AxoDataPersistentExchangeExample.PersistentRootObject plain)
+ {
+ NotPersistentVariable.Cyclic = plain.NotPersistentVariable;
+ PersistentVariable_1.Cyclic = plain.PersistentVariable_1;
+ PersistentVariable_2.Cyclic = plain.PersistentVariable_2;
+#pragma warning disable CS0612
+ await this.PropertyWithPersistentMember._PlainToOnlineNoacAsync(plain.PropertyWithPersistentMember);
+#pragma warning restore CS0612
+ return await this.WriteAsync();
+ }
+
+ [Obsolete("This method should not be used if you indent to access the controllers data. Use `PlainToOnline` instead.")]
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public async Task _PlainToOnlineNoacAsync(Pocos.AxoDataPersistentExchangeExample.PersistentRootObject plain)
+ {
+ NotPersistentVariable.Cyclic = plain.NotPersistentVariable;
+ PersistentVariable_1.Cyclic = plain.PersistentVariable_1;
+ PersistentVariable_2.Cyclic = plain.PersistentVariable_2;
+#pragma warning disable CS0612
+ await this.PropertyWithPersistentMember._PlainToOnlineNoacAsync(plain.PropertyWithPersistentMember);
+#pragma warning restore CS0612
+ }
+
+ public async virtual Task ShadowToPlain()
+ {
+ return await (dynamic)this.ShadowToPlainAsync();
+ }
+
+ public async Task ShadowToPlainAsync()
+ {
+ Pocos.AxoDataPersistentExchangeExample.PersistentRootObject plain = new Pocos.AxoDataPersistentExchangeExample.PersistentRootObject();
+ plain.NotPersistentVariable = NotPersistentVariable.Shadow;
+ plain.PersistentVariable_1 = PersistentVariable_1.Shadow;
+ plain.PersistentVariable_2 = PersistentVariable_2.Shadow;
+ plain.PropertyWithPersistentMember = await PropertyWithPersistentMember.ShadowToPlainAsync();
+ return plain;
+ }
+
+ protected async Task ShadowToPlainAsync(Pocos.AxoDataPersistentExchangeExample.PersistentRootObject plain)
+ {
+ plain.NotPersistentVariable = NotPersistentVariable.Shadow;
+ plain.PersistentVariable_1 = PersistentVariable_1.Shadow;
+ plain.PersistentVariable_2 = PersistentVariable_2.Shadow;
+ plain.PropertyWithPersistentMember = await PropertyWithPersistentMember.ShadowToPlainAsync();
+ return plain;
+ }
+
+ public async virtual Task PlainToShadow(T plain)
+ {
+ await this.PlainToShadowAsync((dynamic)plain);
+ }
+
+ public async Task> PlainToShadowAsync(Pocos.AxoDataPersistentExchangeExample.PersistentRootObject plain)
+ {
+ NotPersistentVariable.Shadow = plain.NotPersistentVariable;
+ PersistentVariable_1.Shadow = plain.PersistentVariable_1;
+ PersistentVariable_2.Shadow = plain.PersistentVariable_2;
+ await this.PropertyWithPersistentMember.PlainToShadowAsync(plain.PropertyWithPersistentMember);
+ return this.RetrievePrimitives();
+ }
+
+ public void Poll()
+ {
+ this.RetrievePrimitives().ToList().ForEach(x => x.Poll());
+ }
+
+ public Pocos.AxoDataPersistentExchangeExample.PersistentRootObject CreateEmptyPoco()
+ {
+ return new Pocos.AxoDataPersistentExchangeExample.PersistentRootObject();
+ }
+
+ private IList Children { get; } = new List();
+ public IEnumerable GetChildren()
+ {
+ return Children;
+ }
+
+ private IList Kids { get; } = new List();
+ public IEnumerable GetKids()
+ {
+ return Kids;
+ }
+
+ private IList ValueTags { get; } = new List();
+ public IEnumerable GetValueTags()
+ {
+ return ValueTags;
+ }
+
+ public void AddValueTag(AXSharp.Connector.ITwinPrimitive valueTag)
+ {
+ ValueTags.Add(valueTag);
+ }
+
+ public void AddKid(AXSharp.Connector.ITwinElement kid)
+ {
+ Kids.Add(kid);
+ }
+
+ public void AddChild(AXSharp.Connector.ITwinObject twinObject)
+ {
+ Children.Add(twinObject);
+ }
+
+ protected AXSharp.Connector.Connector @Connector { get; }
+
+ public AXSharp.Connector.Connector GetConnector()
+ {
+ return this.@Connector;
+ }
+
+ public string GetSymbolTail()
+ {
+ return this.SymbolTail;
+ }
+
+ public AXSharp.Connector.ITwinObject GetParent()
+ {
+ return this.@Parent;
+ }
+
+ public string Symbol { get; protected set; }
+
+ private string _attributeName;
+ public System.String AttributeName { get => string.IsNullOrEmpty(_attributeName) ? SymbolTail : _attributeName.Interpolate(this).CleanUpLocalizationTokens(); set => _attributeName = value; }
+
+ public System.String GetAttributeName(System.Globalization.CultureInfo culture)
+ {
+ return this.Translate(_attributeName, culture).Interpolate(this);
+ }
+
+ private string _humanReadable;
+ public string HumanReadable { get => string.IsNullOrEmpty(_humanReadable) ? SymbolTail : _humanReadable.Interpolate(this).CleanUpLocalizationTokens(); set => _humanReadable = value; }
+
+ public System.String GetHumanReadable(System.Globalization.CultureInfo culture)
+ {
+ return this.Translate(_humanReadable, culture);
+ }
+
+ protected System.String @SymbolTail { get; set; }
+
+ protected AXSharp.Connector.ITwinObject @Parent { get; set; }
+
+ public Translator Interpreter => throw new NotImplementedException();
+ }
+
+ public partial class ObjectWithPersistentMember : AXSharp.Connector.ITwinObject
+ {
+ public OnlinerInt NotPersistentVariable { get; }
+
+ [Persistent("2")]
+ [Container(Layout.Stack)]
+ [Group(GroupLayout.GroupBox)]
+ public AxoDataPersistentExchangeExample.InitializedPrimitives InitializedPrimitives { get; }
+
+ partial void PreConstruct(AXSharp.Connector.ITwinObject parent, string readableTail, string symbolTail);
+ partial void PostConstruct(AXSharp.Connector.ITwinObject parent, string readableTail, string symbolTail);
+ public ObjectWithPersistentMember(AXSharp.Connector.ITwinObject parent, string readableTail, string symbolTail)
+ {
+ Symbol = AXSharp.Connector.Connector.CreateSymbol(parent.Symbol, symbolTail);
+ this.@SymbolTail = symbolTail;
+ this.@Connector = parent.GetConnector();
+ this.@Parent = parent;
+ HumanReadable = AXSharp.Connector.Connector.CreateHumanReadable(parent.HumanReadable, readableTail);
+ PreConstruct(parent, readableTail, symbolTail);
+ NotPersistentVariable = @Connector.ConnectorAdapter.AdapterFactory.CreateINT(this, "NotPersistentVariable", "NotPersistentVariable");
+ InitializedPrimitives = new AxoDataPersistentExchangeExample.InitializedPrimitives(this, "InitializedPrimitives (2 pg.)", "InitializedPrimitives");
+ InitializedPrimitives.AttributeName = "InitializedPrimitives (2 pg.)";
+ parent.AddChild(this);
+ parent.AddKid(this);
+ PostConstruct(parent, readableTail, symbolTail);
+ }
+
+ public async virtual Task OnlineToPlain()
+ {
+ return await (dynamic)this.OnlineToPlainAsync();
+ }
+
+ public async Task OnlineToPlainAsync()
+ {
+ Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember plain = new Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember();
+ await this.ReadAsync();
+ plain.NotPersistentVariable = NotPersistentVariable.LastValue;
+#pragma warning disable CS0612
+ plain.InitializedPrimitives = await InitializedPrimitives._OnlineToPlainNoacAsync();
+#pragma warning restore CS0612
+ return plain;
+ }
+
+ [Obsolete("This method should not be used if you indent to access the controllers data. Use `OnlineToPlain` instead.")]
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public async Task _OnlineToPlainNoacAsync()
+ {
+ Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember plain = new Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember();
+ plain.NotPersistentVariable = NotPersistentVariable.LastValue;
+#pragma warning disable CS0612
+ plain.InitializedPrimitives = await InitializedPrimitives._OnlineToPlainNoacAsync();
+#pragma warning restore CS0612
+ return plain;
+ }
+
+ [Obsolete("This method should not be used if you indent to access the controllers data. Use `OnlineToPlain` instead.")]
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ protected async Task _OnlineToPlainNoacAsync(Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember plain)
+ {
+ plain.NotPersistentVariable = NotPersistentVariable.LastValue;
+#pragma warning disable CS0612
+ plain.InitializedPrimitives = await InitializedPrimitives._OnlineToPlainNoacAsync();
+#pragma warning restore CS0612
+ return plain;
+ }
+
+ public async virtual Task PlainToOnline(T plain)
+ {
+ await this.PlainToOnlineAsync((dynamic)plain);
+ }
+
+ public async Task> PlainToOnlineAsync(Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember plain)
+ {
+ NotPersistentVariable.Cyclic = plain.NotPersistentVariable;
+#pragma warning disable CS0612
+ await this.InitializedPrimitives._PlainToOnlineNoacAsync(plain.InitializedPrimitives);
+#pragma warning restore CS0612
+ return await this.WriteAsync();
+ }
+
+ [Obsolete("This method should not be used if you indent to access the controllers data. Use `PlainToOnline` instead.")]
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public async Task _PlainToOnlineNoacAsync(Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember plain)
+ {
+ NotPersistentVariable.Cyclic = plain.NotPersistentVariable;
+#pragma warning disable CS0612
+ await this.InitializedPrimitives._PlainToOnlineNoacAsync(plain.InitializedPrimitives);
+#pragma warning restore CS0612
+ }
+
+ public async virtual Task ShadowToPlain()
+ {
+ return await (dynamic)this.ShadowToPlainAsync();
+ }
+
+ public async Task ShadowToPlainAsync()
+ {
+ Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember plain = new Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember();
+ plain.NotPersistentVariable = NotPersistentVariable.Shadow;
+ plain.InitializedPrimitives = await InitializedPrimitives.ShadowToPlainAsync();
+ return plain;
+ }
+
+ protected async Task ShadowToPlainAsync(Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember plain)
+ {
+ plain.NotPersistentVariable = NotPersistentVariable.Shadow;
+ plain.InitializedPrimitives = await InitializedPrimitives.ShadowToPlainAsync();
+ return plain;
+ }
+
+ public async virtual Task PlainToShadow(T plain)
+ {
+ await this.PlainToShadowAsync((dynamic)plain);
+ }
+
+ public async Task> PlainToShadowAsync(Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember plain)
+ {
+ NotPersistentVariable.Shadow = plain.NotPersistentVariable;
+ await this.InitializedPrimitives.PlainToShadowAsync(plain.InitializedPrimitives);
+ return this.RetrievePrimitives();
+ }
+
+ public void Poll()
+ {
+ this.RetrievePrimitives().ToList().ForEach(x => x.Poll());
+ }
+
+ public Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember CreateEmptyPoco()
+ {
+ return new Pocos.AxoDataPersistentExchangeExample.ObjectWithPersistentMember();
+ }
+
+ private IList Children { get; } = new List();
+ public IEnumerable GetChildren()
+ {
+ return Children;
+ }
+
+ private IList Kids { get; } = new List();
+ public IEnumerable GetKids()
+ {
+ return Kids;
+ }
+
+ private IList ValueTags { get; } = new List();
+ public IEnumerable GetValueTags()
+ {
+ return ValueTags;
+ }
+
+ public void AddValueTag(AXSharp.Connector.ITwinPrimitive valueTag)
+ {
+ ValueTags.Add(valueTag);
+ }
+
+ public void AddKid(AXSharp.Connector.ITwinElement kid)
+ {
+ Kids.Add(kid);
+ }
+
+ public void AddChild(AXSharp.Connector.ITwinObject twinObject)
+ {
+ Children.Add(twinObject);
+ }
+
+ protected AXSharp.Connector.Connector @Connector { get; }
+
+ public AXSharp.Connector.Connector GetConnector()
+ {
+ return this.@Connector;
+ }
+
+ public string GetSymbolTail()
+ {
+ return this.SymbolTail;
+ }
+
+ public AXSharp.Connector.ITwinObject GetParent()
+ {
+ return this.@Parent;
+ }
+
+ public string Symbol { get; protected set; }
+
+ private string _attributeName;
+ public System.String AttributeName { get => string.IsNullOrEmpty(_attributeName) ? SymbolTail : _attributeName.Interpolate(this).CleanUpLocalizationTokens(); set => _attributeName = value; }
+
+ public System.String GetAttributeName(System.Globalization.CultureInfo culture)
+ {
+ return this.Translate(_attributeName, culture).Interpolate(this);
+ }
+
+ private string _humanReadable;
+ public string HumanReadable { get => string.IsNullOrEmpty(_humanReadable) ? SymbolTail : _humanReadable.Interpolate(this).CleanUpLocalizationTokens(); set => _humanReadable = value; }
+
+ public System.String GetHumanReadable(System.Globalization.CultureInfo culture)
+ {
+ return this.Translate(_humanReadable, culture);
+ }
+
+ protected System.String @SymbolTail { get; set; }
+
+ protected AXSharp.Connector.ITwinObject @Parent { get; set; }
+
+ public Translator Interpreter => throw new NotImplementedException();
+ }
+
+ public partial class InitializedPrimitives : AXSharp.Connector.ITwinObject
+ {
+ public OnlinerBool myBOOL { get; }
+
+ public OnlinerByte myBYTE { get; }
+
+ public OnlinerWord myWORD { get; }
+
+ public OnlinerDWord myDWORD { get; }
+
+ public OnlinerLWord myLWORD { get; }
+
+ public OnlinerSInt mySINTMin { get; }
+
+ public OnlinerSInt mySINTMax { get; }
+
+ public OnlinerInt myINT { get; }
+
+ public OnlinerDInt myDINT { get; }
+
+ public OnlinerLInt myLINT { get; }
+
+ public OnlinerUSInt myUSINT { get; }
+
+ public OnlinerUInt myUINT { get; }
+
+ public OnlinerUDInt myUDINT { get; }
+
+ public OnlinerULInt myULINT { get; }
+
+ public OnlinerReal myREAL { get; }
+
+ public OnlinerLReal myLREAL { get; }
+
+ public OnlinerTime myTIME { get; }
+
+ public OnlinerLTime myLTIME { get; }
+
+ public OnlinerDate myDATE { get; }
+
+ public OnlinerDate myLDATE { get; }
+
+ public OnlinerTimeOfDay myTIME_OF_DAY { get; }
+
+ public OnlinerLTimeOfDay myLTIME_OF_DAY { get; }
+
+ public OnlinerDateTime myDATE_AND_TIME { get; }
+
+ public OnlinerLDateTime myLDATE_AND_TIME { get; }
+
+ public OnlinerChar myCHAR { get; }
+
+ public OnlinerWChar myWCHAR { get; }
+
+ public OnlinerString mySTRING { get; }
+
+ public OnlinerWString myWSTRING { get; }
+
+ partial void PreConstruct(AXSharp.Connector.ITwinObject parent, string readableTail, string symbolTail);
+ partial void PostConstruct(AXSharp.Connector.ITwinObject parent, string readableTail, string symbolTail);
+ public InitializedPrimitives(AXSharp.Connector.ITwinObject parent, string readableTail, string symbolTail)
+ {
+ Symbol = AXSharp.Connector.Connector.CreateSymbol(parent.Symbol, symbolTail);
+ this.@SymbolTail = symbolTail;
+ this.@Connector = parent.GetConnector();
+ this.@Parent = parent;
+ HumanReadable = AXSharp.Connector.Connector.CreateHumanReadable(parent.HumanReadable, readableTail);
+ PreConstruct(parent, readableTail, symbolTail);
+ myBOOL = @Connector.ConnectorAdapter.AdapterFactory.CreateBOOL(this, "myBOOL", "myBOOL");
+ myBYTE = @Connector.ConnectorAdapter.AdapterFactory.CreateBYTE(this, "myBYTE", "myBYTE");
+ myWORD = @Connector.ConnectorAdapter.AdapterFactory.CreateWORD(this, "myWORD", "myWORD");
+ myDWORD = @Connector.ConnectorAdapter.AdapterFactory.CreateDWORD(this, "myDWORD", "myDWORD");
+ myLWORD = @Connector.ConnectorAdapter.AdapterFactory.CreateLWORD(this, "myLWORD", "myLWORD");
+ mySINTMin = @Connector.ConnectorAdapter.AdapterFactory.CreateSINT(this, "mySINTMin", "mySINTMin");
+ mySINTMax = @Connector.ConnectorAdapter.AdapterFactory.CreateSINT(this, "mySINTMax", "mySINTMax");
+ myINT = @Connector.ConnectorAdapter.AdapterFactory.CreateINT(this, "myINT", "myINT");
+ myDINT = @Connector.ConnectorAdapter.AdapterFactory.CreateDINT(this, "myDINT", "myDINT");
+ myLINT = @Connector.ConnectorAdapter.AdapterFactory.CreateLINT(this, "myLINT", "myLINT");
+ myUSINT = @Connector.ConnectorAdapter.AdapterFactory.CreateUSINT(this, "myUSINT", "myUSINT");
+ myUINT = @Connector.ConnectorAdapter.AdapterFactory.CreateUINT(this, "myUINT", "myUINT");
+ myUDINT = @Connector.ConnectorAdapter.AdapterFactory.CreateUDINT(this, "myUDINT", "myUDINT");
+ myULINT = @Connector.ConnectorAdapter.AdapterFactory.CreateULINT(this, "myULINT", "myULINT");
+ myREAL = @Connector.ConnectorAdapter.AdapterFactory.CreateREAL(this, "myREAL", "myREAL");
+ myLREAL = @Connector.ConnectorAdapter.AdapterFactory.CreateLREAL(this, "myLREAL", "myLREAL");
+ myTIME = @Connector.ConnectorAdapter.AdapterFactory.CreateTIME(this, "myTIME", "myTIME");
+ myLTIME = @Connector.ConnectorAdapter.AdapterFactory.CreateLTIME(this, "myLTIME", "myLTIME");
+ myDATE = @Connector.ConnectorAdapter.AdapterFactory.CreateDATE(this, "myDATE", "myDATE");
+ myLDATE = @Connector.ConnectorAdapter.AdapterFactory.CreateLDATE(this, "myLDATE", "myLDATE");
+ myTIME_OF_DAY = @Connector.ConnectorAdapter.AdapterFactory.CreateTIME_OF_DAY(this, "myTIME_OF_DAY", "myTIME_OF_DAY");
+ myLTIME_OF_DAY = @Connector.ConnectorAdapter.AdapterFactory.CreateLTIME_OF_DAY(this, "myLTIME_OF_DAY", "myLTIME_OF_DAY");
+ myDATE_AND_TIME = @Connector.ConnectorAdapter.AdapterFactory.CreateDATE_AND_TIME(this, "myDATE_AND_TIME", "myDATE_AND_TIME");
+ myLDATE_AND_TIME = @Connector.ConnectorAdapter.AdapterFactory.CreateLDATE_AND_TIME(this, "myLDATE_AND_TIME", "myLDATE_AND_TIME");
+ myCHAR = @Connector.ConnectorAdapter.AdapterFactory.CreateCHAR(this, "myCHAR", "myCHAR");
+ myWCHAR = @Connector.ConnectorAdapter.AdapterFactory.CreateWCHAR(this, "myWCHAR", "myWCHAR");
+ mySTRING = @Connector.ConnectorAdapter.AdapterFactory.CreateSTRING(this, "mySTRING", "mySTRING");
+ myWSTRING = @Connector.ConnectorAdapter.AdapterFactory.CreateWSTRING(this, "myWSTRING", "myWSTRING");
+ parent.AddChild(this);
+ parent.AddKid(this);
+ PostConstruct(parent, readableTail, symbolTail);
+ }
+
+ public async virtual Task OnlineToPlain()
+ {
+ return await (dynamic)this.OnlineToPlainAsync();
+ }
+
+ public async Task OnlineToPlainAsync()
+ {
+ Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives plain = new Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives();
+ await this.ReadAsync();
+ plain.myBOOL = myBOOL.LastValue;
+ plain.myBYTE = myBYTE.LastValue;
+ plain.myWORD = myWORD.LastValue;
+ plain.myDWORD = myDWORD.LastValue;
+ plain.myLWORD = myLWORD.LastValue;
+ plain.mySINTMin = mySINTMin.LastValue;
+ plain.mySINTMax = mySINTMax.LastValue;
+ plain.myINT = myINT.LastValue;
+ plain.myDINT = myDINT.LastValue;
+ plain.myLINT = myLINT.LastValue;
+ plain.myUSINT = myUSINT.LastValue;
+ plain.myUINT = myUINT.LastValue;
+ plain.myUDINT = myUDINT.LastValue;
+ plain.myULINT = myULINT.LastValue;
+ plain.myREAL = myREAL.LastValue;
+ plain.myLREAL = myLREAL.LastValue;
+ plain.myTIME = myTIME.LastValue;
+ plain.myLTIME = myLTIME.LastValue;
+ plain.myDATE = myDATE.LastValue;
+ plain.myLDATE = myLDATE.LastValue;
+ plain.myTIME_OF_DAY = myTIME_OF_DAY.LastValue;
+ plain.myLTIME_OF_DAY = myLTIME_OF_DAY.LastValue;
+ plain.myDATE_AND_TIME = myDATE_AND_TIME.LastValue;
+ plain.myLDATE_AND_TIME = myLDATE_AND_TIME.LastValue;
+ plain.myCHAR = myCHAR.LastValue;
+ plain.myWCHAR = myWCHAR.LastValue;
+ plain.mySTRING = mySTRING.LastValue;
+ plain.myWSTRING = myWSTRING.LastValue;
+ return plain;
+ }
+
+ [Obsolete("This method should not be used if you indent to access the controllers data. Use `OnlineToPlain` instead.")]
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public async Task _OnlineToPlainNoacAsync()
+ {
+ Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives plain = new Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives();
+ plain.myBOOL = myBOOL.LastValue;
+ plain.myBYTE = myBYTE.LastValue;
+ plain.myWORD = myWORD.LastValue;
+ plain.myDWORD = myDWORD.LastValue;
+ plain.myLWORD = myLWORD.LastValue;
+ plain.mySINTMin = mySINTMin.LastValue;
+ plain.mySINTMax = mySINTMax.LastValue;
+ plain.myINT = myINT.LastValue;
+ plain.myDINT = myDINT.LastValue;
+ plain.myLINT = myLINT.LastValue;
+ plain.myUSINT = myUSINT.LastValue;
+ plain.myUINT = myUINT.LastValue;
+ plain.myUDINT = myUDINT.LastValue;
+ plain.myULINT = myULINT.LastValue;
+ plain.myREAL = myREAL.LastValue;
+ plain.myLREAL = myLREAL.LastValue;
+ plain.myTIME = myTIME.LastValue;
+ plain.myLTIME = myLTIME.LastValue;
+ plain.myDATE = myDATE.LastValue;
+ plain.myLDATE = myLDATE.LastValue;
+ plain.myTIME_OF_DAY = myTIME_OF_DAY.LastValue;
+ plain.myLTIME_OF_DAY = myLTIME_OF_DAY.LastValue;
+ plain.myDATE_AND_TIME = myDATE_AND_TIME.LastValue;
+ plain.myLDATE_AND_TIME = myLDATE_AND_TIME.LastValue;
+ plain.myCHAR = myCHAR.LastValue;
+ plain.myWCHAR = myWCHAR.LastValue;
+ plain.mySTRING = mySTRING.LastValue;
+ plain.myWSTRING = myWSTRING.LastValue;
+ return plain;
+ }
+
+ [Obsolete("This method should not be used if you indent to access the controllers data. Use `OnlineToPlain` instead.")]
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ protected async Task _OnlineToPlainNoacAsync(Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives plain)
+ {
+ plain.myBOOL = myBOOL.LastValue;
+ plain.myBYTE = myBYTE.LastValue;
+ plain.myWORD = myWORD.LastValue;
+ plain.myDWORD = myDWORD.LastValue;
+ plain.myLWORD = myLWORD.LastValue;
+ plain.mySINTMin = mySINTMin.LastValue;
+ plain.mySINTMax = mySINTMax.LastValue;
+ plain.myINT = myINT.LastValue;
+ plain.myDINT = myDINT.LastValue;
+ plain.myLINT = myLINT.LastValue;
+ plain.myUSINT = myUSINT.LastValue;
+ plain.myUINT = myUINT.LastValue;
+ plain.myUDINT = myUDINT.LastValue;
+ plain.myULINT = myULINT.LastValue;
+ plain.myREAL = myREAL.LastValue;
+ plain.myLREAL = myLREAL.LastValue;
+ plain.myTIME = myTIME.LastValue;
+ plain.myLTIME = myLTIME.LastValue;
+ plain.myDATE = myDATE.LastValue;
+ plain.myLDATE = myLDATE.LastValue;
+ plain.myTIME_OF_DAY = myTIME_OF_DAY.LastValue;
+ plain.myLTIME_OF_DAY = myLTIME_OF_DAY.LastValue;
+ plain.myDATE_AND_TIME = myDATE_AND_TIME.LastValue;
+ plain.myLDATE_AND_TIME = myLDATE_AND_TIME.LastValue;
+ plain.myCHAR = myCHAR.LastValue;
+ plain.myWCHAR = myWCHAR.LastValue;
+ plain.mySTRING = mySTRING.LastValue;
+ plain.myWSTRING = myWSTRING.LastValue;
+ return plain;
+ }
+
+ public async virtual Task PlainToOnline(T plain)
+ {
+ await this.PlainToOnlineAsync((dynamic)plain);
+ }
+
+ public async Task> PlainToOnlineAsync(Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives plain)
+ {
+ myBOOL.Cyclic = plain.myBOOL;
+ myBYTE.Cyclic = plain.myBYTE;
+ myWORD.Cyclic = plain.myWORD;
+ myDWORD.Cyclic = plain.myDWORD;
+ myLWORD.Cyclic = plain.myLWORD;
+ mySINTMin.Cyclic = plain.mySINTMin;
+ mySINTMax.Cyclic = plain.mySINTMax;
+ myINT.Cyclic = plain.myINT;
+ myDINT.Cyclic = plain.myDINT;
+ myLINT.Cyclic = plain.myLINT;
+ myUSINT.Cyclic = plain.myUSINT;
+ myUINT.Cyclic = plain.myUINT;
+ myUDINT.Cyclic = plain.myUDINT;
+ myULINT.Cyclic = plain.myULINT;
+ myREAL.Cyclic = plain.myREAL;
+ myLREAL.Cyclic = plain.myLREAL;
+ myTIME.Cyclic = plain.myTIME;
+ myLTIME.Cyclic = plain.myLTIME;
+ myDATE.Cyclic = plain.myDATE;
+ myLDATE.Cyclic = plain.myLDATE;
+ myTIME_OF_DAY.Cyclic = plain.myTIME_OF_DAY;
+ myLTIME_OF_DAY.Cyclic = plain.myLTIME_OF_DAY;
+ myDATE_AND_TIME.Cyclic = plain.myDATE_AND_TIME;
+ myLDATE_AND_TIME.Cyclic = plain.myLDATE_AND_TIME;
+ myCHAR.Cyclic = plain.myCHAR;
+ myWCHAR.Cyclic = plain.myWCHAR;
+ mySTRING.Cyclic = plain.mySTRING;
+ myWSTRING.Cyclic = plain.myWSTRING;
+ return await this.WriteAsync();
+ }
+
+ [Obsolete("This method should not be used if you indent to access the controllers data. Use `PlainToOnline` instead.")]
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public async Task _PlainToOnlineNoacAsync(Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives plain)
+ {
+ myBOOL.Cyclic = plain.myBOOL;
+ myBYTE.Cyclic = plain.myBYTE;
+ myWORD.Cyclic = plain.myWORD;
+ myDWORD.Cyclic = plain.myDWORD;
+ myLWORD.Cyclic = plain.myLWORD;
+ mySINTMin.Cyclic = plain.mySINTMin;
+ mySINTMax.Cyclic = plain.mySINTMax;
+ myINT.Cyclic = plain.myINT;
+ myDINT.Cyclic = plain.myDINT;
+ myLINT.Cyclic = plain.myLINT;
+ myUSINT.Cyclic = plain.myUSINT;
+ myUINT.Cyclic = plain.myUINT;
+ myUDINT.Cyclic = plain.myUDINT;
+ myULINT.Cyclic = plain.myULINT;
+ myREAL.Cyclic = plain.myREAL;
+ myLREAL.Cyclic = plain.myLREAL;
+ myTIME.Cyclic = plain.myTIME;
+ myLTIME.Cyclic = plain.myLTIME;
+ myDATE.Cyclic = plain.myDATE;
+ myLDATE.Cyclic = plain.myLDATE;
+ myTIME_OF_DAY.Cyclic = plain.myTIME_OF_DAY;
+ myLTIME_OF_DAY.Cyclic = plain.myLTIME_OF_DAY;
+ myDATE_AND_TIME.Cyclic = plain.myDATE_AND_TIME;
+ myLDATE_AND_TIME.Cyclic = plain.myLDATE_AND_TIME;
+ myCHAR.Cyclic = plain.myCHAR;
+ myWCHAR.Cyclic = plain.myWCHAR;
+ mySTRING.Cyclic = plain.mySTRING;
+ myWSTRING.Cyclic = plain.myWSTRING;
+ }
+
+ public async virtual Task ShadowToPlain()
+ {
+ return await (dynamic)this.ShadowToPlainAsync();
+ }
+
+ public async Task ShadowToPlainAsync()
+ {
+ Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives plain = new Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives();
+ plain.myBOOL = myBOOL.Shadow;
+ plain.myBYTE = myBYTE.Shadow;
+ plain.myWORD = myWORD.Shadow;
+ plain.myDWORD = myDWORD.Shadow;
+ plain.myLWORD = myLWORD.Shadow;
+ plain.mySINTMin = mySINTMin.Shadow;
+ plain.mySINTMax = mySINTMax.Shadow;
+ plain.myINT = myINT.Shadow;
+ plain.myDINT = myDINT.Shadow;
+ plain.myLINT = myLINT.Shadow;
+ plain.myUSINT = myUSINT.Shadow;
+ plain.myUINT = myUINT.Shadow;
+ plain.myUDINT = myUDINT.Shadow;
+ plain.myULINT = myULINT.Shadow;
+ plain.myREAL = myREAL.Shadow;
+ plain.myLREAL = myLREAL.Shadow;
+ plain.myTIME = myTIME.Shadow;
+ plain.myLTIME = myLTIME.Shadow;
+ plain.myDATE = myDATE.Shadow;
+ plain.myLDATE = myLDATE.Shadow;
+ plain.myTIME_OF_DAY = myTIME_OF_DAY.Shadow;
+ plain.myLTIME_OF_DAY = myLTIME_OF_DAY.Shadow;
+ plain.myDATE_AND_TIME = myDATE_AND_TIME.Shadow;
+ plain.myLDATE_AND_TIME = myLDATE_AND_TIME.Shadow;
+ plain.myCHAR = myCHAR.Shadow;
+ plain.myWCHAR = myWCHAR.Shadow;
+ plain.mySTRING = mySTRING.Shadow;
+ plain.myWSTRING = myWSTRING.Shadow;
+ return plain;
+ }
+
+ protected async Task ShadowToPlainAsync(Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives plain)
+ {
+ plain.myBOOL = myBOOL.Shadow;
+ plain.myBYTE = myBYTE.Shadow;
+ plain.myWORD = myWORD.Shadow;
+ plain.myDWORD = myDWORD.Shadow;
+ plain.myLWORD = myLWORD.Shadow;
+ plain.mySINTMin = mySINTMin.Shadow;
+ plain.mySINTMax = mySINTMax.Shadow;
+ plain.myINT = myINT.Shadow;
+ plain.myDINT = myDINT.Shadow;
+ plain.myLINT = myLINT.Shadow;
+ plain.myUSINT = myUSINT.Shadow;
+ plain.myUINT = myUINT.Shadow;
+ plain.myUDINT = myUDINT.Shadow;
+ plain.myULINT = myULINT.Shadow;
+ plain.myREAL = myREAL.Shadow;
+ plain.myLREAL = myLREAL.Shadow;
+ plain.myTIME = myTIME.Shadow;
+ plain.myLTIME = myLTIME.Shadow;
+ plain.myDATE = myDATE.Shadow;
+ plain.myLDATE = myLDATE.Shadow;
+ plain.myTIME_OF_DAY = myTIME_OF_DAY.Shadow;
+ plain.myLTIME_OF_DAY = myLTIME_OF_DAY.Shadow;
+ plain.myDATE_AND_TIME = myDATE_AND_TIME.Shadow;
+ plain.myLDATE_AND_TIME = myLDATE_AND_TIME.Shadow;
+ plain.myCHAR = myCHAR.Shadow;
+ plain.myWCHAR = myWCHAR.Shadow;
+ plain.mySTRING = mySTRING.Shadow;
+ plain.myWSTRING = myWSTRING.Shadow;
+ return plain;
+ }
+
+ public async virtual Task PlainToShadow(T plain)
+ {
+ await this.PlainToShadowAsync((dynamic)plain);
+ }
+
+ public async Task> PlainToShadowAsync(Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives plain)
+ {
+ myBOOL.Shadow = plain.myBOOL;
+ myBYTE.Shadow = plain.myBYTE;
+ myWORD.Shadow = plain.myWORD;
+ myDWORD.Shadow = plain.myDWORD;
+ myLWORD.Shadow = plain.myLWORD;
+ mySINTMin.Shadow = plain.mySINTMin;
+ mySINTMax.Shadow = plain.mySINTMax;
+ myINT.Shadow = plain.myINT;
+ myDINT.Shadow = plain.myDINT;
+ myLINT.Shadow = plain.myLINT;
+ myUSINT.Shadow = plain.myUSINT;
+ myUINT.Shadow = plain.myUINT;
+ myUDINT.Shadow = plain.myUDINT;
+ myULINT.Shadow = plain.myULINT;
+ myREAL.Shadow = plain.myREAL;
+ myLREAL.Shadow = plain.myLREAL;
+ myTIME.Shadow = plain.myTIME;
+ myLTIME.Shadow = plain.myLTIME;
+ myDATE.Shadow = plain.myDATE;
+ myLDATE.Shadow = plain.myLDATE;
+ myTIME_OF_DAY.Shadow = plain.myTIME_OF_DAY;
+ myLTIME_OF_DAY.Shadow = plain.myLTIME_OF_DAY;
+ myDATE_AND_TIME.Shadow = plain.myDATE_AND_TIME;
+ myLDATE_AND_TIME.Shadow = plain.myLDATE_AND_TIME;
+ myCHAR.Shadow = plain.myCHAR;
+ myWCHAR.Shadow = plain.myWCHAR;
+ mySTRING.Shadow = plain.mySTRING;
+ myWSTRING.Shadow = plain.myWSTRING;
+ return this.RetrievePrimitives();
+ }
+
+ public void Poll()
+ {
+ this.RetrievePrimitives().ToList().ForEach(x => x.Poll());
+ }
+
+ public Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives CreateEmptyPoco()
+ {
+ return new Pocos.AxoDataPersistentExchangeExample.InitializedPrimitives();
+ }
+
+ private IList Children { get; } = new List();
+ public IEnumerable GetChildren()
+ {
+ return Children;
+ }
+
+ private IList Kids { get; } = new List();
+ public IEnumerable GetKids()
+ {
+ return Kids;
+ }
+
+ private IList ValueTags { get; } = new List();
+ public IEnumerable GetValueTags()
+ {
+ return ValueTags;
+ }
+
+ public void AddValueTag(AXSharp.Connector.ITwinPrimitive valueTag)
+ {
+ ValueTags.Add(valueTag);
+ }
+
+ public void AddKid(AXSharp.Connector.ITwinElement kid)
+ {
+ Kids.Add(kid);
+ }
+
+ public void AddChild(AXSharp.Connector.ITwinObject twinObject)
+ {
+ Children.Add(twinObject);
+ }
+
+ protected AXSharp.Connector.Connector @Connector { get; }
+
+ public AXSharp.Connector.Connector GetConnector()
+ {
+ return this.@Connector;
+ }
+
+ public string GetSymbolTail()
+ {
+ return this.SymbolTail;
+ }
+
+ public AXSharp.Connector.ITwinObject GetParent()
+ {
+ return this.@Parent;
+ }
+
+ public string Symbol { get; protected set; }
+
+ private string _attributeName;
+ public System.String AttributeName { get => string.IsNullOrEmpty(_attributeName) ? SymbolTail : _attributeName.Interpolate(this).CleanUpLocalizationTokens(); set => _attributeName = value; }
+
+ public System.String GetAttributeName(System.Globalization.CultureInfo culture)
+ {
+ return this.Translate(_attributeName, culture).Interpolate(this);
+ }
+
+ private string _humanReadable;
+ public string HumanReadable { get => string.IsNullOrEmpty(_humanReadable) ? SymbolTail : _humanReadable.Interpolate(this).CleanUpLocalizationTokens(); set => _humanReadable = value; }
+
+ public System.String GetHumanReadable(System.Globalization.CultureInfo culture)
+ {
+ return this.Translate(_humanReadable, culture);
+ }
+
+ protected System.String @SymbolTail { get; set; }
+
+ protected AXSharp.Connector.ITwinObject @Parent { get; set; }
+
+ public Translator Interpreter => throw new NotImplementedException();
+
+ //public AXSharp.Connector.Localizations.Translator Interpreter => global::axopen_data_app.PlcTranslator.Instance;
+ }
+}
+
+namespace Pocos
+{
+ namespace AxoDataPersistentExchangeExample
+ {
+ public partial class PersistentRootObject : AXSharp.Connector.IPlain
+ {
+ public Boolean NotPersistentVariable { get; set; }
+
+ public Int16 PersistentVariable_1 { get; set; }
+
+ public Int16 PersistentVariable_2 { get; set; }
+
+ public AxoDataPersistentExchangeExample.ObjectWithPersistentMember PropertyWithPersistentMember { get; set; } = new AxoDataPersistentExchangeExample.ObjectWithPersistentMember();
+ }
+
+ public partial class ObjectWithPersistentMember : AXSharp.Connector.IPlain
+ {
+ public Int16 NotPersistentVariable { get; set; }
+
+ public AxoDataPersistentExchangeExample.InitializedPrimitives InitializedPrimitives { get; set; } = new AxoDataPersistentExchangeExample.InitializedPrimitives();
+ }
+
+ public partial class InitializedPrimitives : AXSharp.Connector.IPlain
+ {
+ public Boolean myBOOL { get; set; }
+
+ public Byte myBYTE { get; set; }
+
+ public UInt16 myWORD { get; set; }
+
+ public UInt32 myDWORD { get; set; }
+
+ public UInt64 myLWORD { get; set; }
+
+ public SByte mySINTMin { get; set; }
+
+ public SByte mySINTMax { get; set; }
+
+ public Int16 myINT { get; set; }
+
+ public Int32 myDINT { get; set; }
+
+ public Int64 myLINT { get; set; }
+
+ public Byte myUSINT { get; set; }
+
+ public UInt16 myUINT { get; set; }
+
+ public UInt32 myUDINT { get; set; }
+
+ public UInt64 myULINT { get; set; }
+
+ public Single myREAL { get; set; }
+
+ public Double myLREAL { get; set; }
+
+ public TimeSpan myTIME { get; set; } = default(TimeSpan);
+ public TimeSpan myLTIME { get; set; } = default(TimeSpan);
+ public DateOnly myDATE { get; set; } = default(DateOnly);
+ public DateOnly myLDATE { get; set; } = default(DateOnly);
+ public TimeSpan myTIME_OF_DAY { get; set; } = default(TimeSpan);
+ public TimeSpan myLTIME_OF_DAY { get; set; } = default(TimeSpan);
+ public DateTime myDATE_AND_TIME { get; set; } = default(DateTime);
+ public DateTime myLDATE_AND_TIME { get; set; } = default(DateTime);
+ public Char myCHAR { get; set; }
+
+ public Char myWCHAR { get; set; }
+
+ public string mySTRING { get; set; } = string.Empty;
+ public string myWSTRING { get; set; } = string.Empty;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/data/tests/AXOpen.Repository.Integration.Tests_L1/Repository/JsonRepositoryTests.cs b/src/data/tests/AXOpen.Repository.Integration.Tests_L1/Repository/JsonRepositoryTests.cs
index 245094e11..99bab0b4c 100644
--- a/src/data/tests/AXOpen.Repository.Integration.Tests_L1/Repository/JsonRepositoryTests.cs
+++ b/src/data/tests/AXOpen.Repository.Integration.Tests_L1/Repository/JsonRepositoryTests.cs
@@ -5,7 +5,6 @@
using AXOpen.Base.Data;
using AXOpen.Data;
using AXOpen.Data.Json;
-using Ix.Repository.Json;
namespace AXOpen.Repository.Integration.Tests
{
diff --git a/src/integrations/src/AXOpen.Integrations.Blazor/Program.cs b/src/integrations/src/AXOpen.Integrations.Blazor/Program.cs
index 0122ca77c..b473f971f 100644
--- a/src/integrations/src/AXOpen.Integrations.Blazor/Program.cs
+++ b/src/integrations/src/AXOpen.Integrations.Blazor/Program.cs
@@ -56,8 +56,8 @@ public static async Task Main(string[] args)
await Entry.Plc.Connector.IdentityProvider.ConstructIdentitiesAsync();
- var repository = Ix.Repository.Json.Repository.Factory(new AXOpen.Data.Json.JsonRepositorySettings(Path.Combine(Environment.CurrentDirectory, "data", "processdata")));
- var repository2 = Ix.Repository.Json.Repository.Factory(new AXOpen.Data.Json.JsonRepositorySettings(Path.Combine(Environment.CurrentDirectory, "data", "testdata")));
+ var repository = AXOpen.Data.Json.Repository.Factory(new AXOpen.Data.Json.JsonRepositorySettings(Path.Combine(Environment.CurrentDirectory, "data", "processdata")));
+ var repository2 = AXOpen.Data.Json.Repository.Factory(new AXOpen.Data.Json.JsonRepositorySettings(Path.Combine(Environment.CurrentDirectory, "data", "testdata")));
//inherited IxProductionData
//var repository = Ix.Repository.Json.Repository.Factory(new AXOpen.Data.Json.JsonRepositorySettings(Path.Combine(Environment.CurrentDirectory, "data", "processdata")));
@@ -69,7 +69,7 @@ public static async Task Main(string[] args)
Entry.Plc.Integrations.DM
.InitializeRemoteDataExchange(
- Ix.Repository.Json.
+ AXOpen.Data.Json.
Repository.Factory
(new AXOpen.Data.Json.JsonRepositorySettings(Path.Combine(Environment.CurrentDirectory, "data", "processdata1"))));
@@ -88,7 +88,7 @@ public static async Task Main(string[] args)
Path.Combine(Environment.CurrentDirectory, "exampledata"));
var exampleRepository =
- Ix.Repository.Json.Repository.Factory(exampleRepositorySettings);
+ AXOpen.Data.Json.Repository.Factory(exampleRepositorySettings);
Entry.Plc.AxoDataExamplesDocu.DataManager.InitializeRemoteDataExchange(exampleRepository);
//
diff --git a/src/templates.simple/README.md b/src/templates.simple/README.md
index 1fefa25ea..b380c4beb 100644
--- a/src/templates.simple/README.md
+++ b/src/templates.simple/README.md
@@ -181,7 +181,7 @@ Downloads current build into the controller.
apax download
```
-Build the both AX and AX# part of the project.
+Build the both SIMATIC-AX and AXOpen projects.
```
apax build
```
@@ -195,5 +195,6 @@ dotnet ixc
## Resources
Documentation sources:
+- `docs` in this folder
- [AXOpen]https://ix-ax.github.io/AXOpen/
- [AX#]https://ix-ax.github.io/axsharp/
diff --git a/src/templates.simple/ax/.tours/addunit.tour b/src/templates.simple/ax/.tours/addunit.tour
index efae6bd2c..151eca46e 100644
--- a/src/templates.simple/ax/.tours/addunit.tour
+++ b/src/templates.simple/ax/.tours/addunit.tour
@@ -7,6 +7,12 @@
"description": "## Call following command to scaffold the unit\r\n>> apax addunit\r\n",
"pattern": "CLASS PUBLIC Context"
},
+ {
+ "file": "src/Context.st",
+ "pattern": "// Units - You can use snippet unitDeclaration",
+ "description": "## Add unit declaration\r\nPress `ctrl+space` and select `unitDeclaration` snippet.\r\nReplace name with your unit name and press `tab`\r\n___\r\n#### Example: \r\n```cs\r\nCu1 : axosimple.Cu1.Unit;\r\nCu1ProcessData : axosimple.Cu1.ProcessDataManager;\r\nCu1TechnologySettings : axosimple.Cu1.TechnologyDataManager;\r\n```",
+ "line": 16
+ },
{
"file": "src/CommonData/eStations.st",
"description": "## Add station/unit number.\r\n\r\nExample:\r\n```\r\nCu1 := 01,\r\n```",
@@ -14,34 +20,39 @@
},
{
"file": "src/Context.st",
- "pattern": "// Units - You can use snippet unitDeclaration",
- "description": "## Add unit declaration\r\nPress `ctrl+space` and select `unitDeclaration` snippet.\r\nReplace name with your unit name and press `tab`\r\n___\r\n#### Example: \r\n```cs\r\nCu1 : axosimple.Cu1.Unit;\r\nCu1ProcessData : axosimple.Cu1.ProcessDataManager;\r\n```"
- },
- {
- "file": "src/Context.st",
- "description": "## Add unit root calls\r\nPress `ctrl+space` and select `unitRootCall` snippet.\r\nReplace name with your unit name and press `tab`\r\n___\r\n#### Example:\r\n`Cu1.ProcessData := REF(Cu1ProcessData);\r\n`Cu1.Run(THIS, Inputs^, Outputs^);",
- "line": 20
+ "description": "## Add unit root calls\r\nPress `ctrl+space` and select `unitRootCall` snippet.\r\nReplace name with your unit name and press `tab`\r\n___\r\n#### Example:\r\n```\r\nCu1.UnitObjects.StationNumber := eStations#Cu1;\r\nCu1.UnitObjects.TechnologySettings := REF(Cu1TechnologySettings);\r\nCu1.UnitObjects.ProcessSettings := REF(ProcessSettings.Cu1);\r\nCu1.UnitObjects.ProcessData := REF(Cu1ProcessData);\r\nCu1.Run(THIS, Inputs^, Outputs^);\r\n```",
+ "line": 51
},
{
"file": "src/Context.st",
- "description": "## Add unit process data\r\nPress `ctrl+space` and select `unitAddProcessData` snippet.\r\nReplace name with your unit name and press `tab`\r\n___\r\n#### Example: \r\n`{#ix-attr:[AXOpen.Data.AxoDataFragmentAttribute]}\r\n`Cu1 : axosimple.Cu1.FragmentProcessDataManger;\r\n ",
- "line": 31
+ "description": "## Add unit process data\r\nPress `ctrl+space` and select `unitAddProcessData` snippet.\r\nReplace name with your unit name and press `tab`\r\n___\r\n#### Example: \r\n```\r\n{#ix-attr:[AXOpen.Data.AxoDataFragmentAttribute]}\r\nCu1 : axosimple.Cu1.FragmentProcessDataManger;\r\n``` ",
+ "line": 89
},
{
"file": "src/Context.st",
- "description": "## Add unit technology data\r\nPress `ctrl+space` and select `unitAddTechnologyData` snippet.\r\nReplace name with your unit name and press `tab`\r\n___\r\n#### Example: \r\n`{#ix-attr:[AXOpen.Data.AxoDataFragmentAttribute]}\r\n`Cu1 : axosimple.Cu1.FragmentTechnologyDataManger;\r\n ",
- "line": 42
+ "description": "## Add unit technology data\r\nPress `ctrl+space` and select `unitAddTechnologyData` snippet.\r\nReplace name with your unit name and press `tab`\r\n___\r\n#### Example: \r\n```\r\n{#ix-attr:[AXOpen.Data.AxoDataFragmentAttribute]}\r\nCu1 : axosimple.Cu1.FragmentTechnologyDataManger;\r\n```\r\n ",
+ "line": 105
},
{
"title": "Initialize Unit repository - Server project",
"file": "../axpansion/server/Program.cs",
- "line": 73,
- "description": "### Create repositories :\r\n\r\n```cs\r\nvar Cu1_TechSettings = Repository.Factory(\"Cu1_TechnologySettings\");\r\nvar Cu1_ProcessSettings = Repository.Factory(\"Cu1_ProcessSettings\");\r\nvar Cu1_ProcessData = Repository.Factory(\"Cu1_ProcessData\");\r\n```"
+ "line": 85,
+ "description": "### Create repositories :\r\n\r\n```cs\r\nvar Cu1_TechSettings = Repository.Factory(\"Cu1_TechnologySettings\");\r\nvar Cu1_ProcessSettings = Repository.Factory(\"Cu1_ProcessSettings\");\r\nvar Cu1_ProcessData = Repository.Factory(\"Cu1_ProcessData\");\r\n```",
+ "selection": {
+ "start": {
+ "line": 4,
+ "character": 101
+ },
+ "end": {
+ "line": 4,
+ "character": 102
+ }
+ }
},
{
"title": "Connect PLC data with Server - Server project",
"file": "../axpansion/server/Program.cs",
- "line": 93,
+ "line": 114,
"description": "### Initialize Remote Data Exchange - Create connection between PLC and Repository\r\n\r\n```cs\r\nvar axoappContext_Cu1 = axosimple.server.Units.Cu1Services.Create(axoappContext);\r\naxoappContext_Cu1.SetUnitsData(\r\n TechnologySettingsRepository : Cu1_TechSettings,\r\n ProcessSettingsRepository : Cu1_ProcessSettings,\r\n ProcessDataRepository : Cu1_ProcessData);\r\n```"
},
{
diff --git a/src/templates.simple/ax/snippets/AxoSnippets.json b/src/templates.simple/ax/snippets/AxoSnippets.json
index b5063b50b..6c4b3e90a 100644
--- a/src/templates.simple/ax/snippets/AxoSnippets.json
+++ b/src/templates.simple/ax/snippets/AxoSnippets.json
@@ -194,5 +194,15 @@
"$0"
],
"description": "structure or member will be read only"
+ } ,
+ "attritubePersistentProperty":
+ {
+ "prefix": ["attPersistent","persistent"],
+ "scope": "st",
+ "body":[
+ "{#ix-attr:[AXOpen.Data.PersistentAttribute(\"\")]}",
+ "$0"
+ ],
+ "description": "The variable will be flagged as persistent, and the persistent data exchange will handle CRUD operations."
}
}
diff --git a/src/templates.simple/ax/src/BaseUnit/UnitObjects.st b/src/templates.simple/ax/src/BaseUnit/UnitObjects.st
index b79226ae4..fc4205c29 100644
--- a/src/templates.simple/ax/src/BaseUnit/UnitObjects.st
+++ b/src/templates.simple/ax/src/BaseUnit/UnitObjects.st
@@ -11,6 +11,15 @@ NAMESPACE axosimple.BaseUnit
/// Station/Unit number.
///
StationNumber : INT;
+
+ ///
+ /// Reference global objects in technology.
+ ///
+ Global : REF_TO axosimple.GlobalContextObjects;
+
+ {#ix-attr:[AXOpen.Data.PersistentAttribute("")]}
+ LastTechnologySettings : STRING[64] ;
+
END_VAR
END_CLASS
diff --git a/src/templates.simple/ax/src/Context.st b/src/templates.simple/ax/src/Context.st
index 93c7c50cd..af532e762 100644
--- a/src/templates.simple/ax/src/Context.st
+++ b/src/templates.simple/ax/src/Context.st
@@ -7,6 +7,9 @@ NAMESPACE axosimple
TechnologySettings : TechnologyData;
ProcessSettings : ProcessData;
ProcessData : ProcessData;
+ PersistentData : AxoDataPersistentExchange;
+ GlobalObjects : GlobalContextObjects;
+
Inputs : REF_TO axosimple.Inputs;
Outputs : REF_TO axosimple.Outputs;
// Units - You can use snippet unitDeclaration
@@ -22,18 +25,36 @@ NAMESPACE axosimple
END_VAR
- METHOD PROTECTED OVERRIDE Main
+ METHOD PROTECTED OVERRIDE Main
+
TechnologySettings.Run(THIS);
ProcessSettings.Run(THIS);
ProcessData.Run(THIS);
+ PersistentData.Run(THIS);
- IF(Inputs <> NULL AND Outputs <> NULL) THEN
+ // initialize grogal object references
+ GlobalObjects.TechnologySettings := REF(TechnologySettings);
+ GlobalObjects.ProcessSettings := REF(ProcessSettings);
+ GlobalObjects.PersistentData := REF(PersistentData);
+ GlobalObjects.ProcessData := REF(ProcessData);
+
+ // remember last selected identifier
+ if TechnologySettings.Common.Entity.DataEntityId <> '' THEN
+ IF GlobalObjects.LastTechnologySettings <> TechnologySettings.Common.Entity.DataEntityId THEN
+ IF PersistentData.InvokeUpdate(TechnologySettings, '') THEN
+ GlobalObjects.LastTechnologySettings := TechnologySettings.Common.Entity.DataEntityId;
+ END_IF;
+ END_IF;
+ END_IF;
+
+ IF(Inputs <> NULL AND Outputs <> NULL) THEN
;//Units entry calls - You can use snippet unitRootCall
StarterUnitTemplate.UnitObjects.StationNumber := eStations#StarterUnitTemplate;
StarterUnitTemplate.UnitObjects.TechnologySettings := REF(StarterUnitTemplateTechnologySettings);
StarterUnitTemplate.UnitObjects.ProcessSettings := REF(ProcessSettings.StarterUnitTemplate);
StarterUnitTemplate.UnitObjects.ProcessData := REF(StarterUnitTemplateProcessData);
+ StarterUnitTemplate.UnitObjects.Global := REF(GlobalObjects);
StarterUnitTemplate.Run(THIS, Inputs^, Outputs^);
@@ -41,11 +62,24 @@ NAMESPACE axosimple
UnitTemplate.UnitObjects.TechnologySettings := REF(UnitTemplateTechnologySettings);
UnitTemplate.UnitObjects.ProcessSettings := REF(ProcessSettings.UnitTemplate);
UnitTemplate.UnitObjects.ProcessData := REF(UnitTemplateProcessData);
+ UnitTemplate.UnitObjects.Global := REF(GlobalObjects);
UnitTemplate.Run(THIS, Inputs^, Outputs^);
END_IF;
END_METHOD
END_CLASS
+ CLASS PUBLIC GlobalContextObjects
+ VAR PUBLIC
+ {#ix-attr:[AXOpen.Data.PersistentAttribute("")]}
+ LastTechnologySettings : STRING ;
+
+ PersistentData : REF_TO axosimple.AxoDataPersistentExchange;
+ ProcessSettings : REF_TO axosimple.ProcessData;
+ TechnologySettings : REF_TO axosimple.TechnologyData;
+ ProcessData : REF_TO axosimple.ProcessData;
+ END_VAR
+ END_CLASS
+
CLASS ProcessData EXTENDS AXOpen.Data.AxoDataFragmentExchange
VAR PUBLIC
{#ix-attr:[AXOpen.Data.AxoDataFragmentAttribute]}
diff --git a/src/templates.simple/ax/src/templates/starterunit/GroundSequence.st b/src/templates.simple/ax/src/templates/starterunit/GroundSequence.st
index 4058b36e3..dea12d91f 100644
--- a/src/templates.simple/ax/src/templates/starterunit/GroundSequence.st
+++ b/src/templates.simple/ax/src/templates/starterunit/GroundSequence.st
@@ -15,18 +15,21 @@ NAMESPACE axosimple.StarterUnitTemplate
MoveToWork : AxoTask;
END_VAR
VAR PRIVATE
- Objs : REF_TO UnitObjects;
+ Objs : REF_TO axosimple.StarterUnitTemplate.UnitObjects;
Components : REF_TO Components;
ProcessData : REF_TO ProcessDataManager;
ProcessSettings : REF_TO FragmentProcessDataManger;
TechnologySettings : REF_TO TechnologyDataManager;
+ PersistentSettings : REF_TO AxoDataPersistentExchange;
+
_currentId : STRING;
+ techSetttingsId : STRING;
END_VAR
METHOD INTERNAL Run
VAR_INPUT
_parent : IAxoObject;
- _unitObjects : REF_TO UnitObjects;
+ _unitObjects : REF_TO axosimple.StarterUnitTemplate.UnitObjects;
END_VAR
IF Objs = NULL THEN
@@ -36,6 +39,7 @@ NAMESPACE axosimple.StarterUnitTemplate
ProcessData := Objs^.ProcessData;
ProcessSettings := Objs^.ProcessSettings;
TechnologySettings := Objs^.TechnologySettings;
+ PersistentSettings := Objs^.Global^.PersistentData;
END_IF;
SUPER.Run(_parent);
@@ -45,6 +49,9 @@ NAMESPACE axosimple.StarterUnitTemplate
/// Contains logic of the steps of this sequence
///
METHOD PROTECTED OVERRIDE Main
+ VAR
+ taskState :IAxoTaskState;
+ END_VAR;
// IF (Components = NULL OR ProcessData = NULL) THEN
// RETURN;
// END_IF;
@@ -57,23 +64,91 @@ NAMESPACE axosimple.StarterUnitTemplate
// This is more verbose but also more versatile way of executing step logic.
IF (Steps[0].Execute(THIS, TRUE, '<#RESTORE#>')) THEN
//-------------------------------------------------------
- ProcessData^.Restore();
- THIS.MoveNext();
+ TechnologySettings^.Restore();
+ ProcessSettings^.Restore();
+ ProcessData^.Restore();
+
+ THIS.MoveNext();
+ //-------------------------------------------------------
+ END_IF;
+
+ IF (Steps[1].Execute(THIS, TRUE, '<#READ PERSISTENT SETTINGS#>')) THEN
+ //-------------------------------------------------------
+ IF CurrentStep.IsFirstEntryToStep() THEN
+ ;// some special initialization
+ END_IF;
+
+ CASE TO_INT( THIS.GetMicroStepValue()) OF
+ 0 :
+ IF PersistentSettings^.InvokeEntityExist(THIS ,'') THEN
+ THIS.SetMicroStepValue(UINT#10);
+ END_IF;
+
+ 10 :
+ IF PersistentSettings^.IsEntityExistDone(THIS) THEN
+ IF PersistentSettings^.EntityExistResult() THEN
+ THIS.SetMicroStepValue(UINT#40); // default data exist
+ ELSE
+ THIS.SetMicroStepValue(UINT#20); // no data exist
+ END_IF;
+ END_IF;
+
+ 20 :
+ IF PersistentSettings^.InvokeUpdateAll(THIS) THEN // data not exist -> udate all to DB
+ THIS.SetMicroStepValue(UINT#30);
+ END_IF;
- ProcessData^.Shared.Entity.WasReset := true;
- _currentId := 'lastPartId'; // just for simulation
+ 30:
+ IF PersistentSettings^.IsUpdateAllDone(THIS) THEN
+ THIS.SetMicroStepValue(UINT#40); // all was updated to DB
+ END_IF;
+
+ 40 :
+ IF PersistentSettings^.InvokeRead(THIS, '') THEN // read default group
+ THIS.SetMicroStepValue(UINT#50);
+ END_IF;
+ 50 :
+ IF PersistentSettings^.IsReadDone(THIS) THEN
+ THIS.MoveNext(); // just default group was readed
+ END_IF;
+ ELSE
+ THIS.SetMicroStepValue(UINT#0);
+ END_CASE;
+
//-------------------------------------------------------
END_IF;
- IF (Steps[1].Execute(THIS, TRUE, '<#READ TECHNOLOGY SETTINS#>')) THEN
+ IF (Steps[5].Execute(THIS, TRUE, '<#READ TECHNOLOGY SETTINS#>')) THEN
//-------------------------------------------------------
- IF(TechnologySettings^.Read('default').IsDone()) THEN
- THIS.MoveNext();
- END_IF;
+ IF CurrentStep.IsFirstEntryToStep() THEN
+
+ IF Objs^.Global^.TechnologySettings^.Common.Entity.DataEntityId <> '' THEN
+ techSetttingsId := Objs^.Global^.TechnologySettings^.Common.Entity.DataEntityId; // TAKE GLOGAL TECHNOLOGY SETTINGS ID
+
+ ELSIF Objs^.TechnologySettings^.Shared.Entity.DataEntityId <> '' THEN // TAKE LAST LOADED FOR STATION
+ techSetttingsId := Objs^.TechnologySettings^.Shared.Entity.DataEntityId;
+
+ ELSIF Objs^.LastTechnologySettings <> '' THEN // TAKE LAST LOADED FROM PERSISTENT
+ techSetttingsId := Objs^.LastTechnologySettings;
+ ELSE
+ Objs^.LastTechnologySettings := 'default'; // FORCE DEFAULT VALUE
+ techSetttingsId := Objs^.LastTechnologySettings;
+ END_IF;
+
+ IF Objs^.Global^.TechnologySettings^.Common.Entity.DataEntityId = Objs^.TechnologySettings^.Shared.Entity.DataEntityId then
+ //THIS.MoveNext(); // you can skip loading...
+ ;
+ end_if;
+
+ else
+ IF(TechnologySettings^.Read( techSetttingsId ).IsDone()) THEN
+ THIS.MoveNext();
+ END_IF;
+ END_IF;
//-------------------------------------------------------
END_IF;
-
- IF (Steps[3].Execute(THIS, TRUE, '<#DO SOMETHING#>')) THEN
+
+ IF (Steps[6].Execute(THIS, TRUE, '<#DO SOMETHING#>')) THEN
//-------------------------------------------------------
IF CurrentStep.Duration > TIME#2s THEN
THIS.MoveNext();
@@ -83,9 +158,24 @@ NAMESPACE axosimple.StarterUnitTemplate
IF (Steps[19].Execute(THIS, TRUE, '<#SAVE DATA#>')) THEN
//-------------------------------------------------------
- IF(ProcessData^.Update(_currentId).IsDone()) THEN
- THIS.MoveNext();
- END_IF;
+ CASE TO_INT( THIS.GetMicroStepValue()) OF
+ 0 :
+ IF ProcessData^.Shared.Entity.DataEntityId <> '' THEN
+ _currentId := ProcessData^.Shared.Entity.DataEntityId;
+ THIS.SetMicroStepValue(UINT#10);
+
+ ELSE // FIST RUN OR WITHOUT DATA
+ THIS.MoveNext();
+ END_IF;
+
+ 10 : // update part as a corrupted a interrupted
+ ProcessData^.Shared.Entity.WasReset := true;
+ IF ProcessData^.Update(_currentId).IsDone() THEN
+ THIS.MoveNext();
+ END_IF;
+ ELSE
+ THIS.SetMicroStepValue(UINT#0);
+ END_CASE;
//-------------------------------------------------------
END_IF;
diff --git a/src/templates.simple/ax/src/templates/starterunit/Unit.st b/src/templates.simple/ax/src/templates/starterunit/Unit.st
index 67e99c2fa..c5b8e0d21 100644
--- a/src/templates.simple/ax/src/templates/starterunit/Unit.st
+++ b/src/templates.simple/ax/src/templates/starterunit/Unit.st
@@ -57,7 +57,8 @@ NAMESPACE axosimple.StarterUnitTemplate
Inputs : axosimple.Inputs;
Outputs : axosimple.Outputs;
END_VAR
- // Component I/O immange and management handling.
+
+ // Component I/O immange and management handling.
UnitObjects.Components.Update(THIS, Inputs, Outputs);
// Process data manager
UnitObjects.ProcessData^.Run(THIS);
@@ -70,6 +71,7 @@ NAMESPACE axosimple.StarterUnitTemplate
AutomatSequence.Restore();
ServiceMode.Restore();
END_IF;
+
// Handles automatic sequence (ground must be previously done)
AutomatSequence.IsDisabled := GroundSequence.Status <> eAxoTaskState#Done;
AutomatSequence.Run(THIS, REF(UnitObjects));
@@ -80,6 +82,16 @@ NAMESPACE axosimple.StarterUnitTemplate
GroundSequence.Restore();
AutomatSequence.Restore();
END_IF;
+
+ // remember last selected technolgy identifier
+ if (UnitObjects.TechnologySettings^.DataManger.Payload.DataEntityId <> '') THEN
+ IF UnitObjects.LastTechnologySettings <> UnitObjects.TechnologySettings^.DataManger.Payload.DataEntityId THEN
+ IF UnitObjects.Global^.PersistentData^.InvokeUpdate(THIS, '') THEN
+ UnitObjects.LastTechnologySettings := UnitObjects.TechnologySettings^.DataManger.Payload.DataEntityId;
+ END_IF;
+ END_IF;
+ END_IF;
+
END_METHOD
END_CLASS
diff --git a/src/templates.simple/ax/src/templates/starterunit/UnitObjects.st b/src/templates.simple/ax/src/templates/starterunit/UnitObjects.st
index 5f7c45db6..a1fc7e387 100644
--- a/src/templates.simple/ax/src/templates/starterunit/UnitObjects.st
+++ b/src/templates.simple/ax/src/templates/starterunit/UnitObjects.st
@@ -27,6 +27,9 @@ NAMESPACE axosimple.StarterUnitTemplate
///
TechnologySettings : REF_TO TechnologyDataManager;
+ {#ix-attr:[AXOpen.Data.PersistentAttribute("StarterUnitTemplate")]}
+ SpecialSettings :STRING[64] ;
+
END_VAR
END_CLASS
diff --git a/src/templates.simple/ax/src/templates/starterunit/server/StarterUnitTemplate.razor b/src/templates.simple/ax/src/templates/starterunit/server/StarterUnitTemplate.razor
index 73cc0c9e0..8ba9feb58 100644
--- a/src/templates.simple/ax/src/templates/starterunit/server/StarterUnitTemplate.razor
+++ b/src/templates.simple/ax/src/templates/starterunit/server/StarterUnitTemplate.razor
@@ -15,9 +15,5 @@
DataHeader="Entry.Plc.Context.StarterUnitTemplateProcessData.Shared.Entity">
-
- @foreach (var axoObject in Entry.Plc.Context.StarterUnitTemplate.GetChildren().Flatten(p => p.GetChildren()).OfType())
- {
-
- }
-
+
+
\ No newline at end of file
diff --git a/src/templates.simple/ax/src/templates/unit/GroundSequence.st b/src/templates.simple/ax/src/templates/unit/GroundSequence.st
index 6b23d5f26..136b11356 100644
--- a/src/templates.simple/ax/src/templates/unit/GroundSequence.st
+++ b/src/templates.simple/ax/src/templates/unit/GroundSequence.st
@@ -20,7 +20,11 @@ NAMESPACE axosimple.UnitTemplate
ProcessData : REF_TO ProcessDataManager;
ProcessSettings : REF_TO FragmentProcessDataManger;
TechnologySettings : REF_TO TechnologyDataManager;
+ PersistentSettings : REF_TO AxoDataPersistentExchange;
+
_currentId : STRING;
+
+ techSetttingsId : STRING;
END_VAR
METHOD INTERNAL Run
@@ -36,9 +40,11 @@ NAMESPACE axosimple.UnitTemplate
ProcessData := Objs^.ProcessData;
ProcessSettings := Objs^.ProcessSettings;
TechnologySettings := Objs^.TechnologySettings;
+ PersistentSettings := Objs^.Global^.PersistentData;
END_IF;
SUPER.Run(_parent);
+
END_METHOD
///
/// Contains logic of the steps of this sequence
@@ -56,19 +62,58 @@ NAMESPACE axosimple.UnitTemplate
IF (Steps[0].Execute(THIS, TRUE, '<#RESTORE#>')) THEN
//-------------------------------------------------------
+ TechnologySettings^.Restore();
+ ProcessSettings^.Restore();
ProcessData^.Restore();
- THIS.MoveNext();
- ProcessData^.Shared.Entity.WasReset := true;
- _currentId := 'lastPartId'; // just for simulation
+ THIS.MoveNext();
+ //-------------------------------------------------------
+ END_IF;
+
+ IF (Steps[1].Execute(THIS, TRUE, '<#READ PERSISTENT SETTINGS#>')) THEN
+ //-------------------------------------------------------
+ CASE TO_INT( THIS.GetMicroStepValue()) OF
+ 0 :
+ IF PersistentSettings^.InvokeRead(THIS,'UnitTemplate') THEN
+ THIS.SetMicroStepValue(UINT#10);
+ END_IF;
+ 10 :
+ IF PersistentSettings^.IsReadDone(THIS) THEN
+ THIS.MoveNext();
+ END_IF;
+ ELSE
+ THIS.SetMicroStepValue(UINT#0);
+ END_CASE;
//-------------------------------------------------------
END_IF;
- IF (Steps[1].Execute(THIS, TRUE, '<#READ TECHNOLOGY SETTINS#>')) THEN
+ IF (Steps[5].Execute(THIS, TRUE, '<#READ TECHNOLOGY SETTINS#>')) THEN
//-------------------------------------------------------
- IF(TechnologySettings^.Read('default').IsDone()) THEN
- THIS.MoveNext();
- END_IF;
+ IF CurrentStep.IsFirstEntryToStep() THEN
+
+ IF Objs^.Global^.TechnologySettings^.Common.Entity.DataEntityId <> '' THEN
+ techSetttingsId := Objs^.Global^.TechnologySettings^.Common.Entity.DataEntityId; // TAKE GLOGAL TECHNOLOGY SETTINGS ID
+
+ ELSIF Objs^.TechnologySettings^.Shared.Entity.DataEntityId <> '' THEN // TAKE LAST LOADED FOR STATION
+ techSetttingsId := Objs^.TechnologySettings^.Shared.Entity.DataEntityId;
+
+ ELSIF Objs^.LastTechnologySettings <> '' THEN // TAKE LAST LOADED FROM PERSISTENT
+ techSetttingsId := Objs^.LastTechnologySettings;
+ ELSE
+ Objs^.LastTechnologySettings := 'default'; // FORCE DEFAULT VALUE
+ techSetttingsId := Objs^.LastTechnologySettings;
+ END_IF;
+
+ IF Objs^.Global^.TechnologySettings^.Common.Entity.DataEntityId = Objs^.TechnologySettings^.Shared.Entity.DataEntityId then
+ //THIS.MoveNext(); // you can skip loading...
+ ;
+ end_if;
+
+ else
+ IF(TechnologySettings^.Read( techSetttingsId ).IsDone()) THEN
+ THIS.MoveNext();
+ END_IF;
+ END_IF;
//-------------------------------------------------------
END_IF;
@@ -82,9 +127,24 @@ NAMESPACE axosimple.UnitTemplate
IF (Steps[19].Execute(THIS, TRUE, '<#SAVE DATA#>')) THEN
//-------------------------------------------------------
- IF(ProcessData^.Update(_currentId).IsDone()) THEN
- THIS.MoveNext();
- END_IF;
+ CASE TO_INT( THIS.GetMicroStepValue()) OF
+ 0 :
+ IF ProcessData^.Shared.Entity.DataEntityId <> '' THEN
+ _currentId := ProcessData^.Shared.Entity.DataEntityId;
+ THIS.SetMicroStepValue(UINT#10);
+
+ ELSE // FIST RUN OR WITHOUT DATA
+ THIS.MoveNext();
+ END_IF;
+
+ 10 : // update part as a corrupted a interrupted
+ ProcessData^.Shared.Entity.WasReset := true;
+ IF ProcessData^.Update(_currentId).IsDone() THEN
+ THIS.MoveNext();
+ END_IF;
+ ELSE
+ THIS.SetMicroStepValue(UINT#0);
+ END_CASE;
//-------------------------------------------------------
END_IF;
diff --git a/src/templates.simple/ax/src/templates/unit/Unit.st b/src/templates.simple/ax/src/templates/unit/Unit.st
index 1d1f02969..b495f85b6 100644
--- a/src/templates.simple/ax/src/templates/unit/Unit.st
+++ b/src/templates.simple/ax/src/templates/unit/Unit.st
@@ -57,6 +57,7 @@ NAMESPACE axosimple.UnitTemplate
Inputs : axosimple.Inputs;
Outputs : axosimple.Outputs;
END_VAR
+
// Component I/O immange and management handling.
UnitObjects.Components.Update(THIS, Inputs, Outputs);
// Process data manager
@@ -65,11 +66,13 @@ NAMESPACE axosimple.UnitTemplate
UnitObjects.TechnologySettings^.Run(THIS);
// Handles ground sequence
GroundSequence.Run(THIS, REF(UnitObjects));
+
// Restores other states when Ground executes
IF (GroundSequence.IsBusy()) THEN
AutomatSequence.Restore();
ServiceMode.Restore();
END_IF;
+
// Handles automatic sequence (ground must be previously done)
AutomatSequence.IsDisabled := GroundSequence.Status <> eAxoTaskState#Done;
AutomatSequence.Run(THIS, REF(UnitObjects));
@@ -81,6 +84,15 @@ NAMESPACE axosimple.UnitTemplate
AutomatSequence.Restore();
END_IF;
+ // remember last selected technolgy identifier
+ if (UnitObjects.TechnologySettings^.DataManger.Payload.DataEntityId <> '') THEN
+ IF UnitObjects.LastTechnologySettings <> UnitObjects.TechnologySettings^.DataManger.Payload.DataEntityId THEN
+ IF UnitObjects.Global^.PersistentData^.InvokeUpdate(THIS, '') THEN
+ UnitObjects.LastTechnologySettings := UnitObjects.TechnologySettings^.DataManger.Payload.DataEntityId;
+ END_IF;
+ END_IF;
+ END_IF;
+
END_METHOD
END_CLASS
diff --git a/src/templates.simple/ax/src/templates/unit/UnitObjects.st b/src/templates.simple/ax/src/templates/unit/UnitObjects.st
index 20753d839..615a228b5 100644
--- a/src/templates.simple/ax/src/templates/unit/UnitObjects.st
+++ b/src/templates.simple/ax/src/templates/unit/UnitObjects.st
@@ -26,7 +26,10 @@ NAMESPACE axosimple.UnitTemplate
/// Provides access to process data manager of this unit
///
TechnologySettings : REF_TO TechnologyDataManager;
-
+
+ {#ix-attr:[AXOpen.Data.PersistentAttribute("UnitTemplate")]}
+ SpecialSettings : STRING[64] ;
+
END_VAR
END_CLASS
diff --git a/src/templates.simple/ax/src/templates/unit/server/UnitTemplate.razor b/src/templates.simple/ax/src/templates/unit/server/UnitTemplate.razor
index ac0369cf6..e89ecc70d 100644
--- a/src/templates.simple/ax/src/templates/unit/server/UnitTemplate.razor
+++ b/src/templates.simple/ax/src/templates/unit/server/UnitTemplate.razor
@@ -16,9 +16,4 @@
DataHeader="Entry.Plc.Context.UnitTemplateProcessData.Shared.Entity">
-
- @foreach (var axoObject in Entry.Plc.Context.UnitTemplate.GetChildren().Flatten(p => p.GetChildren()).OfType())
- {
-
- }
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/templates.simple/axpansion/server/Program.cs b/src/templates.simple/axpansion/server/Program.cs
index 604a7b17c..398f92369 100644
--- a/src/templates.simple/axpansion/server/Program.cs
+++ b/src/templates.simple/axpansion/server/Program.cs
@@ -6,10 +6,9 @@
using AXOpen.Core;
using AXOpen.Core.Blazor.AxoDialogs.Hubs;
using AXOpen.Data;
-using AXOpen.Data.Json;
using AXOpen.Logging;
using axosimple;
-using axosimple.MongoDb;
+using AXOpen.Data.MongoDb;
using axosimple.server.Units;
using AXSharp.Connector;
using AXSharp.Presentation.Blazor.Services;
@@ -63,8 +62,8 @@
var MongoConnectionString = "mongodb://localhost:27017";
var MongoDatabaseName = "axosimple";
-// initialize factory - store connection and credentials ...
-Repository.InitialzeFactory(MongoConnectionString, MongoDatabaseName, "user", "userpwd");
+// initialize factory - store connection and credentials
+Repository.InitializeFactory(MongoConnectionString, MongoDatabaseName, "user", "userpwd");
// repository - data connected with technology (not with production process)
var TechnologyCommonRepository = Repository.Factory
("TechnologyCommon_Settings");
@@ -89,6 +88,10 @@
#endregion MongoDB repository
+
+var persistentRepository = Repository.Factory("Persistent_Data");
+Entry.Plc.Context.PersistentData.InitializeRemoteDataExchange( Entry.Plc.Context, persistentRepository );
+
var axoappContext = ContextService.Create();
axoappContext.SetContextData(
technologyCommonRepository: TechnologyCommonRepository,
@@ -167,8 +170,8 @@
Directory.CreateDirectory(repositoryDirectory);
}
- IRepository userRepo = new JsonRepository(new JsonRepositorySettings(Path.Combine(repositoryDirectory, "Users")));
- IRepository groupRepo = new JsonRepository(new JsonRepositorySettings(Path.Combine(repositoryDirectory, "Groups")));
+ IRepository userRepo = new AXOpen.Data.Json.JsonRepository(new AXOpen.Data.Json.JsonRepositorySettings(Path.Combine(repositoryDirectory, "Users")));
+ IRepository groupRepo = new AXOpen.Data.Json.JsonRepository(new AXOpen.Data.Json.JsonRepositorySettings(Path.Combine(repositoryDirectory, "Groups")));
return (userRepo, groupRepo);
}
diff --git a/src/templates.simple/axpansion/server/VisualComposerSerialize/Context_UnitTemplate/Default.json b/src/templates.simple/axpansion/server/VisualComposerSerialize/Context_UnitTemplate/Default.json
new file mode 100644
index 000000000..e1d3e54b6
--- /dev/null
+++ b/src/templates.simple/axpansion/server/VisualComposerSerialize/Context_UnitTemplate/Default.json
@@ -0,0 +1,22 @@
+[
+ {
+ "Id": "Context_UnitTemplate_UnitObjects_Components_HorizontalCylinder_Move_to_work",
+ "RatioImgX": 15.974228816868411,
+ "RatioImgY": 11.140065146579806,
+ "Transform": "TopCenter",
+ "Presentation": "Status-Display",
+ "Width": -1,
+ "Height": -1,
+ "ZIndex": 0
+ },
+ {
+ "Id": "Context_UnitTemplate_UnitObjects_Components_HorizontalCylinder_Move_to_work",
+ "RatioImgX": 38.42639593908629,
+ "RatioImgY": 31.824104234527695,
+ "Transform": "TopCenter",
+ "Presentation": "Display",
+ "Width": -1,
+ "Height": -1,
+ "ZIndex": 0
+ }
+]
\ No newline at end of file
diff --git a/src/templates.simple/axpansion/twin/Context/RepositoryExtensions.cs b/src/templates.simple/axpansion/twin/Context/RepositoryExtensions.cs
deleted file mode 100644
index 37b19fbdc..000000000
--- a/src/templates.simple/axpansion/twin/Context/RepositoryExtensions.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using AXOpen.Base.Data;
-using AXOpen.Data.MongoDb;
-
-namespace axosimple.MongoDb
-{
- public static class Repository
- {
- public static IRepository Factory(string collectionName) where T : IBrowsableDataObject
- {
- try
- {
- return new MongoDbRepository(
- new MongoDbRepositorySettings(
- connectionString: MongoConnectionString,
- databaseName: MongoDatabaseName,
- collectionName: collectionName,
- credentials: Credentials)
- );
- }
- catch (Exception ex)
- {
- throw new Exception($"Creation of MongoDb repository failed. Check number, type and value of parameters. For detail see inner exception.", ex);
- }
- }
-
- public static void InitialzeFactory(string mongoConnectionString = "mongodb://localhost:27017",
- string mongoDatabaseName = "axosimple",
- string user = "user",
- string userpw = "userpwd")
- {
- MongoConnectionString = mongoConnectionString;
- MongoDatabaseName = mongoDatabaseName;
- Credentials = new MongoDbCredentials(MongoDatabaseName, user, userpw);
- }
-
- public static string MongoConnectionString { get; private set; }
- public static string MongoDatabaseName { get; private set; }
- internal static MongoDbCredentials Credentials { get; private set; }
- }
-}
\ No newline at end of file
diff --git a/src/templates.simple/docs/Alarms.md b/src/templates.simple/docs/Alarms.md
new file mode 100644
index 000000000..d175c7293
--- /dev/null
+++ b/src/templates.simple/docs/Alarms.md
@@ -0,0 +1,23 @@
+
+## Alarms
+
+[Components](../../core/docs/AXOCOMPONENT.md) generate alarms during their operation, in the event of a delay or failure to meet a condition. An alarm describes why the machine is slowing down or stopped, or why a subsequent operation is not being performed.
+
+Integration of alarms is based on [AxoMessenger](../../core/docs/AXOMESSENGER.md).
+
+The [level of alarms](../../core/docs/AXOCOMPONENT#Alarm-Level.md) depends on component integration.
+
+### Alarms on Unit Spot View
+If a CU has any alarms, a pink dot is displayed on the Open button of the CU.
+![Alarm on Unit view ](assets\AlarmCUSpotView.png)
+
+### Alarms over Unit in Modal Window
+A modal window that displays all alarms in the Controlled Unit can be shown by clicking on the 'Alarms In Station' button.
+![Alarm on Unit view ](assets\AlarmsModalView.png)
+
+### Alarm in Component View
+In service mode, you can manually control every component. When a component has any alarms, the emoji icon on the right side changes color. After clicking on it, alarm details will be displayed.
+![Alarm on component ](assets\AlarmsServiceMode.png)
+
+
+[!include[Ref](Navigation.md)]
diff --git a/src/templates.simple/docs/ControlledUnitModes.md b/src/templates.simple/docs/ControlledUnitModes.md
new file mode 100644
index 000000000..48599b944
--- /dev/null
+++ b/src/templates.simple/docs/ControlledUnitModes.md
@@ -0,0 +1,41 @@
+## Controlled Unit Modes
+Modes are essential for the Controlled Unit to effectively and safely use Components for various tasks and scenarios. In our case, we primarily use four major modes.
+
+```mermaid
+---
+title: "Controlled unit modes"
+---
+flowchart TD
+
+ classDef idleType stroke-width:3px,stroke:gray;
+ classDef groundType stroke-width:3px,stroke:blue;
+ classDef autoType stroke-width:3px,stroke:green;
+ classDef serviceType stroke-width:3px,stroke:orange
+
+ AUTO(Automatic mode):::autoType
+ SERVICE(Service mode):::serviceType
+ IDLE(Idle Mode):::idleType
+ GROUND(Ground mode):::groundType
+
+ IDLE <--> SERVICE
+ GROUND <--> AUTO
+ IDLE <--> GROUND
+ AUTO --> SERVICE
+ SERVICE <--> GROUND
+ AUTO --> IDLE
+```
+### Idle Mode
+In this mode, the controller/PLC does not control the technology. This mode is primarily used after powering on the technology.
+
+### Service mode
+The operator manually controls the Controlled Unit through the operating/service elements of Components.
+
+### Ground Mode
+The primary objective of this automatic [sequence](../../core/docs/AXOSEQUENCER.md) is to prepare the components for a safe transition to Automatic Mode. This sequence typically addresses all possible states of the Component following Service Mode or an interruption in Automatic Mode.
+
+### Automatic mode
+The Controlled Unit controls Components automatically in a cyclic loop, according to the control [sequence](../../core/docs/AXOSEQUENCER.md).
+
+
+
+[!include[Ref](Navigation.md)]
diff --git a/src/templates.simple/docs/DataUsedInTechnology.md b/src/templates.simple/docs/DataUsedInTechnology.md
new file mode 100644
index 000000000..4ac28f3af
--- /dev/null
+++ b/src/templates.simple/docs/DataUsedInTechnology.md
@@ -0,0 +1,93 @@
+## Data used in the technology
+
+
+```mermaid
+---
+title: "Example of Data flow in the technology"
+---
+flowchart LR
+
+ classDef DataType stroke-width:3px,stroke:blue;
+ classDef ProcessSettingsType stroke-width:3px,stroke:blue
+ classDef TechnologySettingsType stroke-width:3px,stroke:orange
+ classDef CuType stroke-width:2px,stroke:green
+
+ CU1(CU 1):::CuType
+ CU2(CU 2):::CuType
+ CUn(CU n):::CuType
+
+
+ TechSetRepoCu1[(Technology settings CU 1)]:::TechnologySettingsType
+ TechSetRepoCu2[(Technology settings CU 2)]:::TechnologySettingsType
+ TechSetRepoCun[(Technology settings CU n)]:::TechnologySettingsType
+
+ ProcesSetRepoCu1[(Process settings CU 1)]:::ProcessSettingsType
+ ProcesSetRepoCu2[(Process settings CU 2)]:::ProcessSettingsType
+ ProcesSetRepoCun[(Process settings CU n)]:::ProcessSettingsType
+
+ ProductionSetRepoCu1[(Process data CU 1)]:::DataType
+ ProductionSetRepoCu2[(Process data CU 2)]:::DataType
+ ProductionSetRepoCun[(Process data CU n)]:::DataType
+
+
+ CU1 -->|Tracebility data| ProductionSetRepoCu1
+ CU2 -->|Tracebility data| ProductionSetRepoCu2
+ CUn -->|Tracebility data| ProductionSetRepoCun
+
+ ProcesSetRepoCu1 --> CU1
+ TechSetRepoCu1 --> CU1
+
+ ProcesSetRepoCu2 --> CU2
+ TechSetRepoCu2 --> CU2
+
+ ProcesSetRepoCun --> CUn
+ TechSetRepoCun --> CUn
+
+
+ subgraph SettingsRepoId [Settings Repository]
+ direction LR
+ TechSetRepoCu1
+ TechSetRepoCu2
+ TechSetRepoCun
+ ProcesSetRepoCu1
+ ProcesSetRepoCuX(...)
+ ProcesSetRepoCu2
+ ProcesSetRepoCun
+ end
+
+ subgraph graphTechId [Technology]
+ direction LR
+ CU1
+ CU2
+ CUx(...)
+ CUn
+ end
+
+ subgraph ProductionRepoId ["Process data repository"]
+ direction LR
+ ProductionSetRepoCu1
+ ProductionSetRepoCu2
+ ProductionSetRepoCuX(...)
+ ProductionSetRepoCun
+ end
+
+
+```
+
+
+### Technological Settings
+These data are associated with the machine settings and do not have a direct connection to the product. They can include, for example, motor speeds, positions of manipulators, and other settings that have some impact on the product.
+
+### Process Settings
+These data contain settings related to the product, including the settings of Inspectors and other required process settings.
+
+### Process Data
+This is the same data structure as a Process Settings. But filled by results. Therefore, the data include both settings and outcomes related to the product.
+
+### Traceability Data
+Traceability data are data collected during the process. They can include various values such as pressure, temperature, height, position, or scanned codes of the input material, etc.
+
+For storing values without additional parameters, base type values may be used. In this framework, [Inspectors](../../inspectors/docs/README.md) ensure the storage of values with additional settings, such as limits and timestamps.
+
+[!include[Ref](Navigation.md)]
+
diff --git a/src/templates.simple/docs/Navigation.md b/src/templates.simple/docs/Navigation.md
new file mode 100644
index 000000000..44715ab3f
--- /dev/null
+++ b/src/templates.simple/docs/Navigation.md
@@ -0,0 +1,22 @@
+
+---
+
+[Technology](Technology.md)
+|
+[CU Modes](ControlledUnitMOdes.md)
+|
+[New Controlled Unit](ScaffoldNewControlledUnit.md)
+|
+[Alarms](Alarms.md)
+|
+[Axo Data Exchange](../../data/docs/AxoDataExchange.md)
+|
+[Axo Data Fragment Exchange](../../data/docs/AxoDataFragmentExchange.md)
+|
+[AxoSequencer](../../core/docs/AXOSEQUENCER.md)
+|
+[AxoMessanger](../../core/docs/AXOMESSENGER.md)
+|
+[AxoComponent](../../core/docs/AXOCOMPONENT.md)
+
+---
diff --git a/src/templates.simple/docs/README.md b/src/templates.simple/docs/README.md
new file mode 100644
index 000000000..1b0000e11
--- /dev/null
+++ b/src/templates.simple/docs/README.md
@@ -0,0 +1,5 @@
+## Process Automation Template
+This documentation Template is based on [application temlate](../../../src/README.md#creating-an-axopen-application). It is a sample project for process automation.
+
+[!include[Ref](Navigation.md)]
+
diff --git a/src/templates.simple/docs/ScaffoldNewControlledUnit.md b/src/templates.simple/docs/ScaffoldNewControlledUnit.md
new file mode 100644
index 000000000..84269a94d
--- /dev/null
+++ b/src/templates.simple/docs/ScaffoldNewControlledUnit.md
@@ -0,0 +1,53 @@
+## 1. Create Controlled Unit (CU)
+
+Install to AxCode extension: CodeTour.
+When you open the Template poject in AxCode extension CodeTour automaticaly open code tour that describe all steps that are needed for scaffold of CU. Template project also contains all snippets for scaffolding.
+
+![](assets\CodeTour.png)
+
+## 2. Apax AddUnit
+Template.Simple project contains apax script for adding CU. When you execute next apax command, it will add new CU from template to project structure.
+> [!NOTE] Use this name in all snippets!
+```
+apax addunit
+```
+## 3. CU declaration
+Declaraton of CU is placed to `ax\src\Context.st`. Context is the root of Technology. On the same layer is defined instance of other data`s object due to allowed depth of structure. There is prepared snippet for adding unit declaraion.
+
+ ![Unit Declaration](assets\UnitDeclaration.png)
+
+## 4. CU number
+For part flow in the technology is used unique CU number. It is defined in file `ax\src\CommonData\eStations.st`.
+
+## 5. CU root call
+CU logic needs to be called in `ax\src\Context.st`.
+
+![Unit root call](assets\UnitRootCall.png)
+
+## 6. Add CU Process Data
+CU Process data needs to be added to global process data manager.
+`ax\src\Context.st`.
+![Add CU to Process data ](assets\AddUnitProcessData.png)
+
+
+## 7. Add CU Technology Data
+CU Technology data needs to be added to global technology data manager.
+`ax\src\Context.st`.
+
+![Add CU to Technology data ](assets\AddUnitTechnologyData.png)
+
+
+## 8. Initialize CU repository (.net)
+On .net side must be initialized repository that will be used by DataExchange instace.
+![Add CU repositories](assets\AddUnitRepositories.png)
+
+
+## 9. Connect Plc data with Server in (.net)
+Script that add CU also create a CUService.cs file that initilize Process and technology data. But connection or call with instance of the repository needs to be done manualy.
+![Connect PLC CU with Server](assets\ConnectPlcWithServer.png)
+
+
+## 10. Add CU To Units View (*.blazor)
+In HMI Units view are displayed all CU that are in Technology. Also must be specified munualy logo an name of controlled unit.
+
+[!include[Ref](Navigation.md)]
diff --git a/src/templates.simple/docs/Technology.md b/src/templates.simple/docs/Technology.md
new file mode 100644
index 000000000..9ca20a470
--- /dev/null
+++ b/src/templates.simple/docs/Technology.md
@@ -0,0 +1,60 @@
+## Technology
+Technology is a system that creates a new product through certain operations (pressing, welding, coating) on input material. Every such operation, with auxiliary functions, forms a certain logically `Controlled Unit`.
+
+
+```mermaid
+---
+title: "Relation ship of Technology and Controlled unit"
+---
+flowchart TD
+
+ classDef DataType stroke-width:2px,stroke:blue;
+ classDef SettingsType stroke-width:2px,stroke:yellow
+ classDef TechnologyType stroke-width:2px,stroke:lightgreen
+ classDef CuType stroke-width:2px,stroke:green
+
+ graphCuMode --> graphComponentsId
+ graphComponentsId --> graphCuMode
+
+ subgraph graphTechnology [Technology]
+ graphControledUnit
+ graphControledUnitNext
+ end
+
+ subgraph graphControledUnit [Controlled Unit]
+ graphCuMode
+ graphComponentsId
+ %% otherCu[...]
+ end
+
+ subgraph graphControledUnitNext [Controlled Unit ]
+ NexCuContext[...]
+ end
+
+ subgraph graphCuMode [Controlled Unit mode]
+ Idle(Idle)
+ GROUND(Ground)
+ AUTO(Auto)
+ SERVICE(Service)
+ end
+
+
+ subgraph graphComponentsId [Components]
+ SensorsD(Digital Inputs and Outpus)
+ SensorsA(Analog Inputs and Outpus)
+ Pistons(Pisons)
+ Servos(Servos)
+ Lasers(Lasers)
+ otherComponents[...]
+ end
+
+```
+## Controlled unit
+`Controlled Unit` To perform these operations, it is necessary to control actuators or more complex logical entities (`Components`) such as pistons, servos, lasers, welding units, etc. The Controlled Unit, therefore, includes some `Components` and control logic/control [sequences](../../core/docs/AXOSEQUENCER.md) which create the [`Mode`](ControlledUnitModes.md) of the controlled unit.
+
+## Components
+[Components](../../core/docs/AXOCOMPONENT.md) are digital/analog inputs or outputs, motors, servos, and also other complex devices such as lasers, welders, etc.
+Instances of components are created and encapsulated into a Components object for the purpose of passing a reference to the control sequence.
+
+
+[!include[Ref](Navigation.md)]
diff --git a/src/templates.simple/docs/assets/AddUnitProcessData.png b/src/templates.simple/docs/assets/AddUnitProcessData.png
new file mode 100644
index 000000000..a9729620d
Binary files /dev/null and b/src/templates.simple/docs/assets/AddUnitProcessData.png differ
diff --git a/src/templates.simple/docs/assets/AddUnitRepositories.png b/src/templates.simple/docs/assets/AddUnitRepositories.png
new file mode 100644
index 000000000..0b95be107
Binary files /dev/null and b/src/templates.simple/docs/assets/AddUnitRepositories.png differ
diff --git a/src/templates.simple/docs/assets/AddUnitTechnologyData.png b/src/templates.simple/docs/assets/AddUnitTechnologyData.png
new file mode 100644
index 000000000..faf750b4e
Binary files /dev/null and b/src/templates.simple/docs/assets/AddUnitTechnologyData.png differ
diff --git a/src/templates.simple/docs/assets/AddUnitToTechnologyView.png b/src/templates.simple/docs/assets/AddUnitToTechnologyView.png
new file mode 100644
index 000000000..f5947d278
Binary files /dev/null and b/src/templates.simple/docs/assets/AddUnitToTechnologyView.png differ
diff --git a/src/templates.simple/docs/assets/AlarmCUSpotView.png b/src/templates.simple/docs/assets/AlarmCUSpotView.png
new file mode 100644
index 000000000..0ea3b0557
Binary files /dev/null and b/src/templates.simple/docs/assets/AlarmCUSpotView.png differ
diff --git a/src/templates.simple/docs/assets/AlarmsModalView.png b/src/templates.simple/docs/assets/AlarmsModalView.png
new file mode 100644
index 000000000..5cd1c24a9
Binary files /dev/null and b/src/templates.simple/docs/assets/AlarmsModalView.png differ
diff --git a/src/templates.simple/docs/assets/AlarmsServiceMode.png b/src/templates.simple/docs/assets/AlarmsServiceMode.png
new file mode 100644
index 000000000..6f56cb069
Binary files /dev/null and b/src/templates.simple/docs/assets/AlarmsServiceMode.png differ
diff --git a/src/templates.simple/docs/assets/CodeTour.png b/src/templates.simple/docs/assets/CodeTour.png
new file mode 100644
index 000000000..b20c6f06a
Binary files /dev/null and b/src/templates.simple/docs/assets/CodeTour.png differ
diff --git a/src/templates.simple/docs/assets/ConnectPlcWithServer.png b/src/templates.simple/docs/assets/ConnectPlcWithServer.png
new file mode 100644
index 000000000..d9804c12f
Binary files /dev/null and b/src/templates.simple/docs/assets/ConnectPlcWithServer.png differ
diff --git a/src/templates.simple/docs/assets/UnitDeclaration.png b/src/templates.simple/docs/assets/UnitDeclaration.png
new file mode 100644
index 000000000..24167f62d
Binary files /dev/null and b/src/templates.simple/docs/assets/UnitDeclaration.png differ
diff --git a/src/templates.simple/docs/assets/UnitRootCall.png b/src/templates.simple/docs/assets/UnitRootCall.png
new file mode 100644
index 000000000..26b9de93e
Binary files /dev/null and b/src/templates.simple/docs/assets/UnitRootCall.png differ
diff --git a/src/templates.simple/docs/index.md b/src/templates.simple/docs/index.md
new file mode 100644
index 000000000..53c6d9beb
--- /dev/null
+++ b/src/templates.simple/docs/index.md
@@ -0,0 +1 @@
+[!INCLUDE [rm](README.md)]
\ No newline at end of file
diff --git a/src/templates.simple/docs/toc.yml b/src/templates.simple/docs/toc.yml
new file mode 100644
index 000000000..7954c2e88
--- /dev/null
+++ b/src/templates.simple/docs/toc.yml
@@ -0,0 +1,18 @@
+- name: AXOpen.SimpleTemplate
+ href: index.md
+ items:
+ - name: Technology
+ href: Technology.md
+
+ - name: Controlled Unit Modes
+ href: ControlledUnitModes.md
+
+ - name: Data Used In Technology
+ href: DataUsedInTechnology.md
+
+ - name: Adding Controlled Unit
+ href: ScaffoldNewControlledUnit.md
+
+ - name: Alarms
+ href: Alarms.md
+
diff --git a/src/traversals/traversalBuilds/tmp_build_.ps1 b/src/traversals/traversalBuilds/tmp_build_.ps1
index a12dd3cd7..4b2593bcf 100644
--- a/src/traversals/traversalBuilds/tmp_build_.ps1
+++ b/src/traversals/traversalBuilds/tmp_build_.ps1
@@ -2,13 +2,15 @@
cd ctrl
apax clean
apax install
-apax build
+apax build --ignore-scripts
+dotnet ixc
apax test
cd ..
cd app
apax clean
apax install
-apax build
+apax build --ignore-scripts
+dotnet ixc
apax test
cd ..
dotnet clean tmp_sol_.proj