| OpenLab Studio | >= 0.10.x |
| OpenLab SDK | >= 1.0.x |
| Linguaggi | C# |
| Versione .NET | 8 |
Generale #
Questa guida spiega come creare un Logic Block, le porte, implementare le operazioni e le varie opzioni. Per il setup dell’ambiente di sviluppo e installare il plugin seguire questa guida, il codice completo è reperibile su GitHub
Un LogicBlock è l’equivalente di una classe, esegue delle operazioni e si interfaccia con gli altri LogicBlock tramite delle porte. Ogni LogicBlock può possedere una o più porte in entrata o in uscita, con tipi di dati diversi, che rappresentano le proprietà della della classe.
Esistono settaggi di default( LogicBlockDefaultSettings e LogicBlockPortDefaultSettings) sia per i LogicBlock che per le porte, a questi possono essere aggiunti settaggi specifici in fase di sviluppo e settaggi che possono essere modificati dall’utente nel file di configurazione di OpenLab.
LogicBlock #
Un LogicBlock viene esteso dalla classe LogicBlock o LogicBlockGroup. La prima serve a creare un LogicBlock classico la seconda serve a creare dei LogicBlock che ne possono contenere altri ed eseguirli in un ambiente separato, per l’appunto gruppi.
Ogni LogicBlock implementa i metodi run e stop.
Il metodo run viene usato per implementare l’operazione che deve eseguire il LogicBlock, questo metodo può essere richiamato dalle porte quando il loro valore cambia oppure dal Desk quando avvia l’esecuzione.
Il metodo stop viene usato per implementare le operazioni da eseguire allo stop del programma.
Di default sono presenti due porte: ToString ed Exception. La prima rappresenta il classico metodo toString ed è possibile specificare da quale porta prenda il suo valore convertito in stringa. Il secondo contiene l’eccezione che viene lanciata in caso di errore.
Le porte #
Ogni LogicBlock può contenere una o più porte, le porte sono l’equivalente delle proprietà di una classe, sono implementate dalla classe LogicBlockPort e vengono usate per stabilire una connessione con le porte degli altri LogicBlock e trasferire un valore in uscita o in entrata da un LogicBlock all’altro.
Ogni porta ha una direzione prestabilita per i dati: in Input(write), in Output(read) e Bidirectional(rw), un tipo di dato associato(il tipo di dato del valore effettivamente memorizzato all’interno della porta) e uno o più tipi di dato ammessi per il valore della porta. Una porta può contenere un solo valore per volta.
I settaggi di default di una porta vengono letti dalla classe LogicBlockPortDefaultSettings che è possibile estendere per aggiungere settaggi specifici.
Implementare le basi #
In questo esempio verrà creato un LogicBlock che somma due numeri, in arrivo da due porte in entrata e scrive in una porta d’uscita il risultato.
Innanzi tutto va creata la struttura base, estendendo la classe LogicBlock, creando le info e implementando i metodi run e stop.
using OpenLabSDK.config;
using OpenLabSDK.error;
using OpenLabSDK.events;
using OpenLabSDK.instruments;
using OpenLabSDK.plugin;
using OpenLabSDK.ui;
namespace LogicBlockTutorial1
{
public class LogicBlockTutorial1 : LogicBlock
{
public class Info : LogicBlockInfo
{
public string UID => "";
public string Title => "LogicBlock base";
public int[] version => [1, 0, 0];
public string vendorUID => "";
public string url => "";
public string description => "A base logic blocks";
public string instrumentsMenuCategoryTitle => "Tutorials/Logic blocks";
}
public LogicBlockTutorial1(IErrorManager _errorManager, IPluginsManager _pluginsManager, IEventsManager _eventsManager, IWindowsManager _windowsManager, InstrumentDefinition instrumentDefinition) : base(_errorManager, _pluginsManager, _eventsManager, _windowsManager, instrumentDefinition)
{
logicBlockInfo = new Info();
}
public override void run()
{
}
public override void stop()
{
}
}
}
Nel codice sopra sono state create le basi, a questo punto sono già presenti le porte standard(ToString e Exception) e i settaggi di default(classe LogicBlockDefaultSettins) la cui istanza è settata nella variabile settings.
Con la classe Info vengono specificate le info del LogicBlock. Nello specifico instrumentsMenuCategoryTitle viene usato per inserire il LogicBlock all’interno del menu ad albero Instruments di OpenLab Studio.
Creare le porte #
Vanno create due porte in entrata per gli addendi e una d’uscita per il risultato, il codice seguente va inserito nel costruttore
LogicBlockPort inputPortA, inputPortB, outputPort;
public LogicBlockTutorial1(IErrorManager _errorManager, IPluginsManager _pluginsManager, IEventsManager _eventsManager, IWindowsManager _windowsManager, InstrumentDefinition instrumentDefinition) : base(_errorManager, _pluginsManager, _eventsManager, _windowsManager, instrumentDefinition)
Type[] type = [
typeof(float),
typeof(int),
typeof(uint),
typeof(nint),
typeof(nuint),
typeof(long),
typeof(ulong),
typeof(ushort),
typeof(double),
typeof(decimal)
];
inputPortA = new LogicBlockPort("A", type, LogicBlockPort.DataDirection.Input);
inputPortA.settings.horizontalPosition = OpenLabSDK.logicblocks.LogicBlockPortDefaultSettings.Position.Left;
inputPortA.settings.onDataChangeOperation = LogicBlockPort.OnDataChangeOperations.ExecuteLogicBlockRunMethod;
addPort(inputPortA);
inputPortA.settings.dataValueEditable = true;
inputPortB = new LogicBlockPort("B", type, LogicBlockPort.DataDirection.Input);
inputPortB.settings.horizontalPosition = OpenLabSDK.logicblocks.LogicBlockPortDefaultSettings.Position.Left;
inputPortB.settings.onDataChangeOperation = LogicBlockPort.OnDataChangeOperations.ExecuteLogicBlockRunMethod;
addPort(inputPortB);
inputPortB.settings.dataValueEditable = true;
outputPort = new LogicBlockPort("A+B", type, LogicBlockPort.DataDirection.Output);
outputPort.settings.horizontalPosition = OpenLabSDK.logicblocks.LogicBlockPortDefaultSettings.Position.Right;
outputPort.settings.onDataChangeOperation = LogicBlockPort.OnDataChangeOperations.PutDataViaConnection;
addPort(outputPort);
setToStringPortOnDataChange(outputPort);
}
Nel codice sopra l’array type specifica i tipi di dati ammessi per le porte, in questo caso tutti i tipi numerici.
Nel costruttore della porta A vengono specificati:
- Nome della porta
- Tipi di dati ammessi
- Direzione della dei dati
Le porte possiedono giài settaggi della classe LogicBlockPortDefaultSettings alcuni dei quali vengono settati esplicitamente qui.
settings.horizontalPosition setta la posizione che avrà la porta nel Desk Editor in OpenLab Studio, se a destra o a sinistra. Per convensione a sinistra vengono posizionate le porta di Input e a destra le porte di Output.
settings.onDataChangeOperation specifica quale operazione verrà eseguita quando il valore memorizzato nella porta cambia, in questo caso le porte per le porte A e B verrà eseguito il metodo run quando il valore di queste porte cambia mentre la porta A+B invierà il proprio valore alla porta connessa
settings.dataValueEditable = true; fa in modo che il valore della porta sia modificabile nel Desk Editor.
setToStringPortOnDataChange(outputPort) specifica che la porta ToString conterrà il valore della porta outputPortogni volta che questo cambia. Il valore verrà trasformato in stringa.
Run #
Di seguito il codice per la somma, da inserire nel metodo run
if (inputPortA.getData(false) != null && inputPortB.getData(false) != null)
{
outputPort.setData(inputPortA.getData(false) + inputPortB.getData(false), false, false);
}
Qui viene verificato prima che i valori della porta A e B non sia null.
Il metodo getData prende come argomento un booleano che specifica se eseguire o meno il controllo sulla direzione dei dati. In questo caso viene disabilitato ponendolo a false perché le porte sono in Input e quindi leggere i dati da una porta che accetta solo la scrittura provocherebbe un errore.
Il metodo setData prende come primo argomento il valore da memorizzare, il secondo disabilita(se true) temporaneamente(solo per loperazione di scrittura del valore) l’evento onDataChange, il terzo disabilita(se false) il controllo sulla direzione dei dati(ovviamente leggere i valore su una porta in scrittura provocherebbe un errore)
Funzionamento #
Quando il valore di una delle due porte A e B cambia il metodo run legge i valori di queste porte e li somma memorizzando il risultato nella porta A+B.
null se non diversamente specificato.Quando il metodo run memorizza il risultato della somma nella porta A+B, scatena l’evento onDataChange della medesima porta che, come specificato in outputPort.settings.onDataChangeOperation invierà(LogicBlockPort.OnDataChangeOperations.PutDataViaConnection) il valore alla porta connessa ad A+B.
Valore iniziale per le porte #
Una porta, di default è impostata su un valore null, per settare un valore ad una porta usare il metodo setPort, riprendendo il codice per aggiungere la porta A
inputPortA = new LogicBlockPort("A", type, LogicBlockPort.DataDirection.Input);
inputPortA.settings.horizontalPosition = OpenLabSDK.logicblocks.LogicBlockPortDefaultSettings.Position.Left;
inputPortA.settings.onDataChangeOperation = LogicBlockPort.OnDataChangeOperations.ExecuteLogicBlockRunMethod;
addPort(inputPortA);
inputPortA.setData(10,false,false);
onDataChange event delle porte #
Quando il valore della porta cambia vengono eseguiti gli handler associati all’evento onDataChange dopo di che viene lanciata l’operazione selezionata per la porta(PutDataViaConnection, GetDataViaConnection, etc). Per aggiungere un handler all’evento usare il metodo onDataChangeEventAddHandler.
Le operazioni predefinite possono essere disabilitate dall’utente mente gli handler inseriti .
Mentre le operazioni predefinite possono essere disabilitate dall’utente, gli event handlers dell’evento onDataChange vengono eseguiti comunque. Viene eseguito un handler questo riceve come argomento la porta che ha lanciato l’evento e l’istanza della classe LogicBlockPortDataChangeEventArgs che contiene il nuovo valore e il vecchio valore. Inoltre, settando a false la proprietà stop viene interrotta l’esecuzione degli handlers successivi.
Di seguito un esempio
inputPortA.onDataChangeEventAddHandler((object sender, LogicBlockPortDataChangeEventArgs e) =>
{
if (e.newValue != null)
{
MessageBox.Show(@"{e.newValue.ToString()} :: {e.newValue.GetType.Name}");
MessageBox.Show(@"{e.oldValue.ToString()} :: {e.oldValue.GetType.Name}");
e.stop = true;
}
});
Nell’esempio sopra vengono mostrati il vecchio valore e il nuovo, ed i rispettivi tipi di dato, della porta inputPortA dopo che il valore è cambiato. Ponendo e.stop su true non verranno eseguiti gli handler successivi.