Leonardo Platform Simulator Reference Manual

# Introduzione

'Leonardo Platform Simulator' e' un' applicazione destinata alle attivita' di test e qualifica di apparati che utilizzano protocolli di comunicazione dati.

Poiche' tali attivita' sono spesso effettuate utilizzando medesime tipologie di bus o metodologie di comunicazione, si e' cercato di realizzare un ambiente software atto a soddisfare tali esigenze con il proposito di minimizzare la scrittura di nuovo codice.

Lo scopo di questo documento è definire una guida all'utilizzo del software 'Leonardo Platform Simulator'.

'Leonardo Platform Simulator' ha le seguenti caratteristiche:

1. Generazione automatica della GUI
2. Generazione automatica del protocollo di comunicazione tramite ICD espresso in linguaggio formale (es. file XML )
3. Facilità di utilizzo e impiego su hardware di produttori differenti
4. Accesso remoto dellinterfaccia
5. Automatizzazione delle operazioni sulle interfacce (test)
6. Personalizzazione delle logiche di simulazione attraverso il meccanismo dei plugin
7. Interfaccia Python e meccanismo di scripting integrato
8. Multipiattaforma (Windows, Linux, & )
9. Costi di licenza nulli tramite utilizzo SW factory open sourc
10. Sistema di diagnostica evoluto e personalizzabile (eg. ERROR, WARNING, INFO )

L' interfaccia grafica del simulatore di piattaforma, come anche i protocolli di comunicazione, sono gestiti automaticamente interpretando un file di inizializzazione che ne descrive le caratteristiche in termine di:

- Numero di interfacce gestite
- Descrizione della singola interfaccia per mezzo di un documento (ICD) che dettaglia la codifica dei dati ossia:
  - Messaggio:
  - velocità
  - trasmissione/ricezione
  - lunghezza
  - periodicità
- Campi costituenti il messaggio:
  - tipo di dato ( numero, stringa, booleano,& )
  - dimensione
  - range operativo
  - tipo di codifica

L' applicativo implementa il concetto di Model/View/Controller (MVC),disaccoppiando linterfaccia grafica e la gestione dei dati in modo da aumentarne la flessibilità ed il riuso.
Permette, ad esempio, di poter utilizzare diverse piattaforme di protocollo proprietario aggiungendo librerie senza intervenire sull interfaccia grafica.
Il framework è dotato di un motore di scripting integrato ( Python ).
Tale motore è mirato ad offrire la possibilità, da parte dellutilizzatore, di elaborare algoritmi ed operazioni che interagiscano con linterfaccia di comunicazione e descrivano sequenze operative ripetibili per mezzo di script.
E inoltre possibile accedere al simulatore tramite connessione remota, utilizzando un servizio basato su un sistema di RPC (Remote Procedure Call), esportando gli oggetti Python propri del simulatore.

I possibili campi di utilizzo sono svariati grazie alle sue caratteristiche.

- Accesso al simulatore da remoto (via GUI o command line)
- Scalabilità multidirezionale fra i diversi moduli
- La tracciabilità del traffico dati dellinterfaccia è flessibile adattandosi alle esigenze dell` operatore in termini di livello di accuratezza.
- Modalità di preparazione del test comune ed indipendente dal programma specifico grazie alle API in Python
- E possibile aggiungere, rimuovere, modificare le interfacce di piattaforma semplicemente intervenendo su file di configurazione, senza intervenire sul codice sorgente
- Eventuali personalizzazioni dedicate alla singola interfaccia, per esempio:
  - meccanismi automatici di trasmissione/ricezione
  - menu/funzionalità specifiche di un apparato

# Configurazione

Leonardo Platform Simulator e' configurabile attraverso files che ne descrivono le caratteristiche ed il comportamento in termini di:

1. caratteristiche globali
2. descrizione del numero di interfacce a cui l'applicazione deve connettersi
3. caratteristiche della singola interfaccia
4. definizione del vocabolario di comunicazione

## File INI

Il file che definisce globalmente l'operativita' ( sessione ) dell'applicativo verso un dato sistema e' un file <session name>.INI.
Il file .INI utilizza il seguente formalismo:
`[Globals]
(IniScript=<path_to_file>{.py})
(StyleSheet=<path_to_file>)
(Icon=<path_to_file>)
(LegacyName=<legacy_code_string>)
(Font=<string>)
(Geometry=<string>)
InterfaceSize=<n>
[Interface_(i=0..n-1)]
Name=First Interface
ConfigFile=<path_to_file>{.xml}
BusDriver=<path_to_file>{(.dll|.so)|.py}
UiFile=<path_to_file>{.ui}
(Plugin=<path_to_file>{(.dll|.so)|.py})
(CurrentLogPath=<path>)
(IniScript=<path_to_file>{.py} )
[Tools]
(Plugin_0=<path_to_file>{(.dll|.so)|.py}`
L'applicativo, una volta determinata la sessione, in fase di inizializzazione carica per ogni interfaccia una serie di files:

- database ICD [ConfigFile]
- driver di comunicazione [BusDriver]
- interfaccia grafica [UiFile]
- plugin [Plugin]
- script python [IniScript]

I Plugin opzionali *plugin raccolti nella cartella $(LeoPlatSimHomeDir)/options vengono automaticamente inclusi nella sessione, altri possono essere specificati nella sezione Tools del file .INI.
Per essere riconosciuti tali plugin devono, oltre a rispettare la struttura software basata su SDK dedicato, avere la dicitura "<nome>Plugin.<estensione>"
con:

- <nome> : testo libero
- <estensione> : py ( plugin Python ) oppure dll ( Windows ) o so ( Linux )

### ConfigFile

E' un file che descrive la composizione dei messaggi di interscambio tra il simulatore ed il target.
Utilizza un formalismo XML le cui regole di composizione sono tracciate dal documento DTD:
`<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE ICDHandler [<!ELEMENT ICDHandler (HiLevel,LowLevel,Mapping)>
<!ELEMENT HiLevel (Message+)>
<!ELEMENT Message (Field+)>
<!ELEMENT Field (Values*)>
<!ELEMENT Values (Value+)>
<!ELEMENT Value (#PCDATA)>
<!ATTLIST ICDHandler
Name CDATA #REQUIRED
Version CDATA #REQUIRED
Endianness (rawbyte|bigendian16|bigendian32|littleendian16|littleendian32) "rawbyte"
Ui (all_tab|all_vertical|all_horizontal|by_iotype) "by_iotype"
>
<!ATTLIST Message
Id CDATA #REQUIRED
Name CDATA #REQUIRED
Width CDATA #REQUIRED
IOType (tx|rx) #REQUIRED
uSecPeriodTime CDATA #IMPLIED
>
<!ATTLIST Field
Name CDATA #REQUIRED
Width CDATA #REQUIRED
Offset CDATA #REQUIRED
Min CDATA #IMPLIED
Max CDATA #IMPLIED
Weight CDATA "1"
OffsetValue CDATA "0"
Type (spare|boolean|short|ushort|int|uint|long|ulong|double|float|string|enum) #REQUIRED
Unit CDATA #IMPLIED
Display (binary|decimal|hexadecimal) "decimal"
Default CDATA #IMPLIED
Encoding (sign_module|bcd|binary|=<formula>) "binary"
Resolution CDATA "0.000001"
Ui (default|entry|spinbox|combobox|radiobox|checkbox|listbox|slider|progressbar|knob) "default"
>
<!ATTLIST Value
Name CDATA #REQUIRED
>
]>`

``

Notes:

Encoding Formula

The string must contains a mathematic expresion.

The followings variables can be used:

- min : Field Min
- max : Field Max
- lsb : Field Weight
- off : Field OffsetValue
- len : Field Width

The following C math functions are also supported:

- abs (calls to fabs)
- acos, asin, atan, atan2, cos, cosh, sin, sinh, tan, tanh
- ceil, exp, floor
- ln (calls to log)
- log (calls to log10 by default, see below), log10
- pow, sqrt

Also, the following constants are available:

- pi, e

### BusDriver

E' un file, tipicamente una libreria dinamica (.dll, .so,... ), con il quale si construisce un canale di comunicazione tra l'applicativo ed il target.
E' dipendente dal protocollo utilizzato ( RS232, Socket , GPIB, Arinc 429, MIL 1553 ....)
Puo' far riferimento a file di configurazione esterni.
Puo' ottenere informazioni relazionate al database ICD tramite i nodi XML `LowLevel` e `Mapping `, i quali sono lasciati a libera implementazione.

### UiFile

E' un file testuale con il quale di definisce l'interfaccia grafica dell'interfaccia. E' un file con sintassi xml derivante dal framework Qt. Puo' essere modificato sia manualmente che tramite strumenti della toolchain Qt ( per es. Designer ).

### Plugin

E' un file, tipicamente una libreria dinamica (.dll, .so,... ), con il quale vengono definite azioni specifiche dell' interfaccia come automatismi , sincronizzazione tra messaggi, extra funzionalita'. Si basa su una struttuta definita e riconoscibile dall'applicativo ( vedi SDK e tecniche Plugin )

### IniScript

E' uno script python che viene eseguito in fase di inizializzazione dell'interfaccia

# Caratteristiche

Per quanto concerne le caratteristiche funzionali del simulatore, esse sono soggette alle versioni rilasciate nel tempo per fissare problemi o introdurre funzionalita' richieste dal cliente.
Attualmente sono implementate le seguenti:

Resivione | Nota
103017 | Possibilita' di configurare il BusDrver e i Plugin direttamente con file python
102810 | Portabilita' verso python 3.x.
49055 | Aggiunta gestione expressioni matematiche sull encoder
47733 | Implementato iniScript

47207

Allo startup LeoPlatSim carica, sottoforma di plugin, tuttle le librerie presenti nei seguenti path:

- <installation path>
- <appdata>/LeonardoCompany/<build>/options/

seguendo la regole <name>Plugin.<suffix> ( es "plotterPlugin.dll" )

# Funzionalita` di Base

`uPlatSim.exe --help
Usage: uPlatSim.exe [options]
LeonardoCompany Platform Simulator
Options:
-?, -h, --help Displays this help.
-v, --version Displays version information.
-s, --session <name> Set the platform application session to run
-a, --appdata <dir> Set the application data to dir
-b, --batch <file> Run python script in batch mode
-d, --debug <file> Log debug information into file
-i, --cli Run interactive cli python console instead of gui
-I, --ini <inifile> Manage session by using ini file
-P, --python <dir> Set python home environment`

L'applicativo puo essere utilizzato fondamentamente in 3 diversi modi:

1. Attraverso l'interfaccia grafica ( normale utilizzo dell'ambiente da parte di un utilizzatore )
2. Eseguendo uno script da riga di comando ( utilizzabile per es. all'interno di procedure shell/DOS )
3. Command line ( attivazione della sola modalita' python senza ambiente grafico [ per smanettoni ] )

## Argomenti globali

Tutte le modalita' di utilizzo prescindono dalla definizione di alcuni argomenti globali di seguito descritti

### Session

Una sessione e' il modo con cui si identifica un' implementazione del simulatore di piattaforma.

Leonardo Platform Simulator e' pensato per avere una singola installazione core ( eseguibile e librerie ) e una o piu' sessioni dispobili. Genericamente l'applicativo cerca tale sessione / identificativo in una determinata area applicativa dell' utente gestita da sistema operativo

Sistema Operativo | Variabile
--- | ---
Windows | %APPDATA%\LeonardoCompany
Linux | $HOME/.config/LeonardoCompany

L'identificativo passato come valore dell'argomento --session o in forma abbreviata -s individua il file .ini avente tale nome.

### appdata

Con questo argomento opzionale si indica, attraverso il suo valore, l'area applicativo nella quale cercare il file .ini con nome passato dall'argomento session.

Utile per diversificare/versionare distribuzioni di implementazioni o nel caso di versione portable.

### Ini

Analogamente alla sessione il valore dell'argomento --ini o in forma abbreviata -I individua univocamente il file .ini ed utilizzandone la directory come area applicativa.

### Python

Leonardo Platform Simulator utilizza la distribuzione Python individuata dalle variabili di ambiente PYTHONHOME e PYTHONPATH per poter usufruire dei moduli python dispobili e non integrati nell'applicativo ( solo i built-in ).

Alternativamente si puo' indicare quale python usare valorizzando tale argomento.

## Modalita' grafica

E la modalita' utlizzata per default . Di seguito le opzioni di chiamata:

`uPlatSim.exe --session=<name> [ --python=<dir> ] [ --appdata=<dir> ] [--debug=<file>]`

`uPlatSim.exe --ini=<inifile> [ --python=<dir> ] [--debug=<file>]`

Sono integrate due visualizzazioni. La prima (GUI mode) presenta le interfacce attraverso elementi interattivi ( entry , combobox, checkbuttons,... ), mentre la seconda (Python Mode) gestisce la parte di interfaccia a python.

### GUI Mode

Nella seguente modalita' l'applicativo esegue le seguenti operazioni:

- vengono rapresentate in forma grafica tutte le interfacce presentate dal file INI
- vengono eseguiti gli scripts di inizializzazione ( globali e di interfaccia )

L'utente ha la possibilita' di accedere ai campi di tutti i messaggi implemenati per ogni singola interfaccia sia

- modalita grafica : accesso diretto tramite oggetti grafici interattivi
- tramite console python : console python interattiva
- tramite il lancio di uno o piu' scripts: caricamento di files ( o liste di files ) ed escuzione con generazione automatica di un file di log nella stessa cartella dello script

Ogni interfaccia e` contraddistinta da un tab indicante il nome dell’ interfaccia ( quello relativo all’ attributo name del documento ICD (.xml) { Nella figura per es. troviamo le interfacce AEROFLEX-IFF45TS e TIC-T760 }.

#### comando e controllo { controllo interfaccia / bus }

Questa sezione gestisce il bus in termini di :
- Start :attivazione del traffico
- Stop :terminazione del traffico
- Configure: gestione dei parametri di interfaccia del bus

#### indicatore messaggi

Indicatore dei messaggi:
- Tx = trasmessi
- Rx = ricevuti ,
- Err = errati (Tx eRx )

#### iniettore errori di protocollo

Permette di comunicare al bus l’inserzione di tipologie di errore di protocollo al fine di testarne i possibili effetti sul target.
gestione log
Permette di registrare e salvare su file il traffico dati in base a tipologie di filtraggio:
- tutti i messaggi,
- solo i messaggi errati,
- solo i messaggi modificati
Su Windows per default i files sono salvati in %USERPROFILE%\Documents.

#### gestione scrittura ed invio dei dati

Questa sezione permette di gestire l’invio o la richiesta di ricezione di messaggi a seconda della tipologia del messaggio.
A seguito di una modifica di uno o piu’ campi associati a uno o piu messaggi, si possono attuare le seguenti azioni:

##### Commit Changes

TUTTE le modifiche sono salvate ed inviate al bus che ne gestisce la trasmissione secondo le modalita’ del caso.

##### Revert Changes

Vengono rigettate le modifiche e ripristinati i dati relativi all’ultima modifica.

##### Commit on the Fly

Le modifiche dei campi saranno processate direttamente.

##### Invio di messaggi non modificati

Un messaggio non periodico puo’ essere inviato anche quando questo non ha subito modifiche.
Il comando e’ attivo quando non vi sono modifiche pendenti.
Di seguito la sequenza:

- selezionare nella combobox associata al comando Resend Msg il messaggio
- cliccare sul button avente come testo il nome del messaggio ( ogni click corrisponde ad un invio )

##### richiesta ricezione messaggio

Per un messaggio non periodico in lettura puo’ esserne richiesto l’aggiornamento forzato ( quando questo e’ gestito dal protocollo )
Il comando e’ attivo quando non vi sono modifiche pendenti.
Di seguito la sequenza:

- selezionare nella combobox associata al comando Resend Msg il messaggio
- cliccare sul button avente come testo il nome del messaggio ( ogni click corrisponde ad una richiesta di lettura )

### Python Mode

La modalita' python e' rivolta principalmente ad attivita' di test e validazione del target, dove si desidera proceduralizzare seguenze di operazioni di input/output du una o piu interfacce in modo da poterle rendere riproducibili ed indipendenti dall'operatore.

Leonardo Platform simulator integra al suo interno un motore python contenente un modulo addizionale ( denominato interpreter ) che permette l'interoperabilita' con i messaggi caricati tramite ICD. Sono pertanto disponibili, oltre ai moduli python standard, API per:

- la lettura/scrittura di messaggi
- il controllo del bus
- l'esecuzione di funzionalita' fornite dai plugins

NB: Leonardo Platform NON contiene al suo interno una distribuzione Python ma solamente l'interprete; questo significa che non sono contenute librerie. L'host deve essere munito di distribuzione python ( 2.7.X.X ) compatibile con l'architettura dell'applicativo ( 32/64 bit )

Sono presentate per default due tab denominate:

1. Python Script #0
2. Terminal #0

Altri terminali python possono essere aggiunti. Ogni terminale e' una istanza python indipendente dalle altre.

#### controlli Python

Da sx a ds:

- aggiungi un python script alla lista di esecuzione ( alternativamente il file puo' essere trascinato consentendone il caricamento )
- carica una lista di scripts (.lst)
- salva la lista corrente su file (.lst)
- pulisci la finestra di output
- lancia l'esecuzione della lista di scripts
- abortisci l'esecuzione della lista di scripts
- apri un nuovo terminale python

Tali funzionalita' sono disponibili anche da menu Edit > Python

#### Python Script #0

Questa tab e' predisposta per gestire la parte di scripting python. In questa sezione si possono aggiungere / eliminare scripts, salvare / caricare una lista di scripts ed eseguirli tramite i suddetti controlli.

file manager | output
log

La parte destra (output) della pagina visualizza l' output derivante dall'esecuzione degli scripts.Ogni esecuzione produce un file di log avente radice pari al nome dello script file ( dirname e basename ) ed estensione .log
La parte bassa (log) comunica le operazioni effettuate in ambito di caricamento / cancellazione dei files ed esito esecuzione.

#### Terminal #(n)

Qusta finestra riproduce una console python avanzata in quanto contenente il modulo relativo alle API per accedere alle interfacce. Ogni terminale e' indipendente dagli altri ( oggetti, moduli, ... ).

Non produce output di log.

Puo' essere chiuso.

## CLI ( Command Line Interpreter )

`uPlatSim.exe --cli --session=<name> [--python=<dir> ] [ --appdata=<dir> ] [--debug <file>]`
L'applicativo si presenta come una console a riga di comando python. Non visualizza interfacce grafiche.
In questa modalita' l'applicativo esegue le seguenti operazioni:

- vengono eseguiti gli scripts di inizializzazione ( globali e di interfaccia )
- viene eseguito un singolo processo di interprete python interattivo

Esempio

Utilizzo la sessione test1 in modalita cli ed eseguo comandi python relativi al modulo interno 'interpreter'

## Batch

`uPlatSim.exe --batch <file> --session=<name> [--python=<dir> ] [ --appdata=<dir> ] [--debug <file>]`
In questa modalita' e' possibile eseguire un singolo file di script python direttamente da command line senza l'apertura dell' interfaccia grafica.L'applicativo esegue le seguenti operazioni:

- vengono eseguiti gli scripts di inizializzazione ( globali e di interfaccia )
- viene eseguito lo script passato da argomento

Tale esecuzione ridirige su standard output le comunicazioni risultanti dall'esecuzionde dello script e non viene effettuato il salvataggio su file ( come avviene in modalita' grafica ).
L'applicativo termina con valore:

- 0: in caso di esecuzione dello script terminata con successo
- numero diverso da 0 : in caso di errore durante l'esecuzione dello script oppure con il valore specificato dall'istruzione python sys.exit([arg])

Esempio

Utilizziamo la sessione 'Test1' individuata dal file Test1.ini e lo script python (Test.1.py).

## Log di un traffico dati

Ogni interfaccia di una determinata sessione permette di salvare su file il traffico dati in ingresso ed uscita.
La registrazione [ log ] puo' essere attivata/disattivata nei seguenti modi:

- tramite Python
  - logStart(self, level, dirName)
  - logStop()
- tramite Gui:
  - utilizzanto il tasto "Start Logging" per avviare (* un led rosso lampeggiante in prossimita' del tasto indica l'attivita' di registrazione in corso)
  - utilizzando il medesimo tasto (ma con testo "Stop Logging" se in fase di registrazione ) per terminare

Il file viene salvato per default nella cartella Documenti dell'utente ( in base al sistema operativo ), o nella cartella specificata dall'utente a seconda del tipo di chiamata alla funzione ( da Gui tramite tasto opportuno e da python tamite argomento ) e il nome e' determinato dalla seguente regola:
<nome interfaccia>__<anno>_<mese>_<giorno>_<ora>_<minuto>_<secondo>.log

### Contenuto del file di log

`# [TimeTag] [Flag] [MsgId] [IO] (...)
(TIME) (FLAG)[<ID>] [<IO>] (DATA0) ... (DATA-N)
...`
Con:

- TIME : tempo espresso in <Hour>.<Mim>.<Sec>.<mSec>
- FLAG : [-|E|I] [ Normal ]; E [ Errore ]; I [ Info ]
- ID : <digit> identificativo messaggio ( tipicamente utlizzato quello dell'ICD )
- IO : [Tx/Rx] Tx - se trasmissione ; Rx - se ricezione
- DATA : <carattere/parola> expresso in formato esadecimale

## Plugins

Come si e' detto il simulatore di piattaforma e' in grado di supportare funzionalita' particolari la dove l'interfaccia lo richieda, oppure aggiungere strumenti di utilizzo generale.

Questa funzionalita' e' gestita tramite il concetto dei plugin, ovvero librerie caricate dinamicamente le quali rspettano una struttura dati nota all'applicativo.

Le regole applicabili per la realizzazione di un plugin sono descritte nel documento 'plugin' incluso nell'installazione.

Gli oggetti applicabili al plugin sono sostanzialmente di 3 tipi:

1. elementi selezionabili ( eventualmente da multiple opzioni ) { per esempio abilitare/disabilitare una certa funzionalita' estra tramite una checkbox }
2. elementi richiamanti dialogs (solo GUI ) o azioni dirette riferite ad un certo istante
3. elementi richiamanti funzionalita' python ( solo python )

L'utilizzo dei suddetti plugin tramite GUI gli elementi sono raggiungibili da menu/Plugins/<nome interfaccia>/. Gli elementi possono appartenere a sottomenu.

L'utilizzo tramite Python e' gestito dall oggetto interpreter.PyPluginHandler ottenuto dall'interfaccia tramite API getPlugonHandler()

`>>>dir(interpreter.PyPluginHandler)
['__class__', '__del__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattr__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__swig_getmethods__', '__swig_setmethods__', '__weakref__', 'doAction', 'getActionHelp', 'getChecked', 'isCheckable', 'names', 'setChecked']`

con `isCheckable(n) [Boolean] : ritorna True se l'elemento passato come argomento e' del tipo selezinabile`

`getChecked(n) [Boolean] :ritorna True se l'elemento e' selezionato`

`setChecked(n,Boolean): imposta l'elemento selezionabile con valore passato da argomento`

`doAction(self, arg2, dict_args=None, dict_retval=None) con arg2 nome della funzione, dict_args=Tupla nome/valore dei parametri in input, dict_ret_val=Tupla nome/valore dei parametri in output`

# Funzionalita` avanzate

## Modificare layout di interfaccia

Per layout di una interfacia si intende la visualizzazione degli oggetti grafici i quali rappresentano i campi di tutti i messaggi descritti nel relativo file di ICD.
Tipicamente ogni campo di un dato messaggio viene visualizzato con tre elementi grafici:

- oggetto nome : contenente il testo indicativo il nome del campo
- oggetto valore : contenente il valore [ editabile se in scrittura ] del campo
- oggetto unita di misura : conenente il testo indicativo l'unita' di misura [ se presente ] del campo

Il layout di una interfaccia e' descritto da un file .ui gestito direttamente dalla piattaforma Qt ( http://doc.qt.io/archives/qt-4.8/designer-using-a-ui-file.html)
il quale viene caricato in fase di avviamento dell'applicativo.
Tale file e' specificato ne file .INI relativo alla sessione tramite chiave Interface_ <n> /UiFile dove n e' un numero progressivo che determina l'interfaccia.
E' possibile modificare il layout:

- direttamente modificando a mano il file ( che e' in formato XML )
- tramite Designer.exe ( Qt ) che e' fornito a corredo dell'applicativo nella directory stessa.
- da applicativo tramite Menu->Edit->Interface->[Nome Interface]

L'applicativo gestisce le seguenti modifiche del layout:

- modifiche posizionali [ organizzazione degli elementi tramite gestore geometrici di vario tipo (verticali / orizzonati / a matrice) ... ]
- modifiche delle proprieta' caratteristiche dell' elemento [ per es stile di visualizzazione di una combobox ]
- aggiunta/sostituzione di elementi collegabili ( se compatibili ) ad un campo di un messaggio.
  - Un dato messaggio puo' essere rappresentao da uno o piu' elementi grafici i quali sono collegati al campo tramite il valore della porprieta' ObjectName il quale deve seguire il formalismo seguente: "___MESSAGEID____ <id> _ <field_name> ___FIELDTAG___ <obj_text> "
    - <id> : e' l'identificativo numerico del messaggio
    - <field_name>: e' il nome del campo normalizzato ( i caratteri [:punct:] sono sostituiti da _ )
    - <obj_text> : testo libero che differenzia l'oggetto ( es: "numeric1" )

## Utilizzare uno script Python come Bus

Leonardo Platform Simulator e' in grado di utilizzare uno script Python per agire come gestore dei dato di basso livello ( denominato BusDriver ).
Per essere compatibile con l'applicativo tale script deve utilizzare come riferimento il modulo 'proxybusdriver' presente nella cartella dell'eseguibile.
Tale modulo permette di creare un canale di comunicazione univoco con l'applicativo consentendo di interfacciarsi con il database con la quale si intende operare ( messaggi e campi ).
Per creare un istanza di busDriver sono necessarie le seguenti operazioni:

- Instanziare o ridefinire i metodi della classe python fornita dal modulo python (proxybusdriver)
  - Basandosi su una classe padre definita in python (proxybus) con la quale viene gestita la parte di connessione al simulatore si possono finalmente definire le funzionalita' per gestire lo scambio dati. Distinguiamo:

- costanti:
  - __proxy_icdinterface__ : srtinga identificativa dell interfaccia
  - __proxy_icdxmlfile__ : path del file xml ( configurazione )
  - __proxy_identifier__ : identificatore dell'istanza proxybus
- metodi richiamabili direttamente da python:
  - onBusError : da utilizzare per passare al livello superiore ( il simulatore ) un errore di gestione dati
  - onClock : notificare al simulatore la periodicita' ( in modo da sincronizzare eventuali automatisci di varia natura )
  - onReceive : passare al livello superiore l'avvenuta ricezione ( codificata ) di dati proveniente dal dispositivo collegato
- callback python per implementare funzionalita' predefinite dal simulatore:
  - setOnErrorInjectionListCallback : implementare l'evento proveniente dal simulatore che definisce una lista di errori iniettabili sul canale di comunicazione
  - setSendCallback : implementare l'evento proveniente dal simulatore di invio dati verso il dispositivo collegato
  - setStartCallback : implementare l'evento proveniente dal simulatore di start della counicazione
  - setStopCallback : implementare l'evento proveniente dal simulatore di stop della counicazione
  - setUsClockPeriodCallback : implementare l'evento proveniente dal simulatore che imposta il tempo di clock ( espresso in microsecondi )

- il modulo dispone di una classe ProxyBusDriver che 'incorpora' proxybus rendendo piu facile la parte di programmazione python ( associazioni di callback, uso di safe-threading, ... )

### Esempio

Si vuole stabilire una connesione di tipo seriale tra un apparato (TDK-LAMBDA-40-38) e il simulatore utilizzando la libreria python PySerial.
Dato l'ICD:
<ICDHandler Name='TDK-LAMBDA-40-38' Ui='all_vertical' Version='89873786-A8FF-49A9-AF2E-0184193BE6CE' >
<HiLevel>
<Message Id='1' Name='OUT' Width='8' IOType='TX' >
<Field Name='ENABLE' Width='8' Offset='0' Type='ENUM' Display='DECIMAL' Default='OFF' >
<Values>
<Value Name="OFF" >0x00</Value>
<Value Name="ON">0x7F</Value>
</Values>
</Field>
</Message>
<Message Id='2' Name='VOLTAGE' Width='16' IOType='TX' >
<Field Name='VOLTAGE' Width='16' Offset='0' Min='0.00' Max='40.00' Type='DOUBLE' Weight='0.01' Precision='0.01' Default='28.00' Unit='Volt' />
</Message>
<Message Id='3' Name='CURRENT' Width='16' IOType='TX' >
<Field Name='CURRENT' Width='16' Offset='0' Min='0.00' Max='38.00' Type='DOUBLE' Weight='0.01' Precision='0.01' Default='10.00' Unit='Amp' />
</Message>
<Message Id='4' Name='TDK-LOOPBACK' Width='48' IsPeriodic='FALSE' IOType='RX' >
<Field Name='REMOTE-ENABLE' Width='8' Offset='0' Type='ENUM' Display='DECIMAL' Default='OFF' >
<Values>
<Value Name="OFF" >0x00</Value>
<Value Name="ON">0x7F</Value>
</Values>
</Field>
<Field Name='OUT-ENABLE' Width='8' Offset='8' Type='ENUM' Display='DECIMAL' Default='OFF' >
<Values>
<Value Name="OFF" >0x00</Value>
<Value Name="ON">0x7F</Value>
</Values>
</Field>
<Field Name='MEAS-VOLTAGE' Width='16' Offset='16' Min='0.00' Max='40.00' Type='DOUBLE' Weight='0.01' Precision='0.01' Default='0' Unit='Volt' />
<Field Name='MEAS-CURRENT' Width='16' Offset='32' Min='0.00' Max='38.00' Type='DOUBLE' Weight='0.01' Precision='0.01' Default='0' Unit='Amp' />
</Message>
</HiLevel>
<LowLevel>
<Config Port="COM1" />
</LowLevel>
<Mapping>
<Map Id="1">out</Map>
<Map Id="2">voltage</Map>
<Map Id="3">current</Map>
<Map Id="4">response</Map>
</Mapping>
</ICDHandler>
il codice python per realizzare il driver sara' il seguente:
import proxybus # proxy python fornita da ProxyPythonBusDriver
from proxybusdriver import ProxyBusDriver # per importare ProxyBusDriver
import serial # carico la seriale
import time
import struct
import re
import sys
import xml.etree.ElementTree as et # mi serve per parsare il file xml e costruire le mappature
class serialTDKLambdaPowerSupply(ProxyBusDriver):
def __init__(self):
ProxyBusDriver.__init__(self) # costruttore della classe padre
self.name = "Serial TDK-LAMBDA Power Supply"
self.usclockperiod = 500000 # deternimo la durata del periodo di clock
self.response_id = None
self.s=serial.Serial(None, 9600, # instanzio l'oggetto seriale della libreria python
timeout=0,
parity=serial.PARITY_NONE)
self.s.setWriteTimeout(10.0)
self.opened = False
self.query_err_reg_exp=re.compile('C0[1-4]')
self.eval_tx = {
"remote" : self.set_remote,
"out" : self.set_out,
"voltage" : self.set_voltage,
"current" : self.set_current
}
self.resolution = { 'voltage' : 0.01,
'current' : 0.01 }
self.icd_map={}
self.parseICD()
####
# re-implemento il metodo della classe padre open_comm
####
def open_comm(self):
self.opened = False
try:
self.s.open()
except:
print 'failed to open ....'
self.opened = self.s.isOpen()
if self.opened is True:
reg_exp=re.compile('.*LAMBDA,\s*GEN40\-38')
self.s.read(255)
self.ps_set('RMT REM')
self.ps_set('ADR 06')
ident = self.ps_query('IDN?')
if ident is None or reg_exp.match(ident) is False:
self.raiseError('No valid IDN reveived',3)
self.close_comm()
else:
self.raiseError('cannot open serial port %s' %self.s.port,3)
return self.opened
####
# re-implemento il metodo della classe padre close_comm
####
def close_comm(self):
self.ps_set('RMT LOC')
try:
self.s.close()
except:
self.raiseError(sys.exc_info(),3)
self.opened = False
####
# Utilizzo questo metodo per dedurre:
# - la porta seriale da utilizzare
# - la corrispondenza tra msg_id e tipo di dato da decodificare in lettura/scrittura
####
def parseICD(self):
xml=''
with open(self.icdxmlfile) as fileinput:
for line in fileinput:
xml += line.lower()
root = et.fromstring(xml)
config = root.find('./lowlevel/config')
if config is not None: self.s.port = config.attrib.get('port')
for i in root.findall('./mapping/map'):
key= int(i.attrib.get('id'))
val= i.text
self.icd_map[key] = val
if val == 'response' :
self.response_id = key
print self.icd_map
####
# re-implemento il metodo della classe padre send
####
def send(self, m_id, m_data, m_err):
ret = False
self.acquireLock()
try:
s_func = self.icd_map.get(m_id,"not_found")
ret=self.eval_tx.get(s_func,self.not_found)(m_data) # in base al mapping viene richiamata il methodo corrispondente
except:
self.raiseError(sys.exc_info(),3)
finally:
self.releaseLock()
return ret
####
# re-implemento il metodo della classe padre iteration ( routine che viene eseguita da un thread ogno uSec definiti come da Clock )
# in questo caso ogni clock voglio leggere dati dalla periferica ...
####
def iteration(self):
reg_exp="(\w+)=([a-zA-Z0-9\.]+)\;?\s"
out = { 'REMOTE' : None,
'OUT' : None,
'VOLTAGE' : None,
'CURRENT' : None
}
d = self.ps_query("RMT?")
if d[:3] == 'LOC': out['REMOTE']=0x00
elif d[:3] == 'REM': out['REMOTE']=0x7F
d = self.ps_query("OUT?")
if d[:3] == 'OFF': out['OUT']=0x00
elif d[:2] == 'ON': out['OUT']=0x7F
out['VOLTAGE'] = self.ps_query("MV?")
out['CURRENT'] = self.ps_query("MC?")
#print out
valid=True
for el in out.values():
valid =(False if el == None else valid)
if valid and self.response_id:
_rmt = int(out['REMOTE'])
_out = int(out['OUT'])
_vlt = float(out['VOLTAGE'])/self.resolution['voltage']
_cur = float(out['CURRENT'])/self.resolution['current']
x=struct.pack("bbhh",_rmt,_out,_vlt,_cur) #impacchetto il messaggio
self.forward_msg(self.response_id,x) #invio il messaggio codificato al simulatore
else:
print "invalid"
def ps_set(self,cmd):
r = self.ps_query(cmd)
#print "recv::",r
ret = (True if r[:2] == 'OK' else False )
return ret
def ps_query(self,cmd):
try:
n=self.s.write(cmd+'\r')
except:
self.raiseError(sys.exc_info(),3)
raise
#print 'sent',n
time.sleep(0.1)
resp = self.s.read(512)
#print cmd,'->',resp
if self.query_err_reg_exp.match(resp): resp=None
#print cmd,'->',resp
return resp
def set_remote(self,data):
ret = False
remote = 'LOC'
if len(data) == 1:
_bytes=struct.unpack('b',data)
if _bytes[0] == 0x7F: remote = 'REM'
ret=self.ps_set("RMT %s" %remote)
return ret
def set_out(self,data):
ret = False
out = 'OFF'
if len(data) == 1:
_bytes=struct.unpack('b',data)
if _bytes[0] == 0x7F: out = 'ON'
ret=self.ps_set("OUT %s" %out)
return ret
def set_voltage(self,data):
ret = False
#print len(data)
if len(data) == 2:
_bytes=struct.unpack('h',data)
voltage=float(_bytes[0])*self.resolution['voltage']
ret=self.ps_set("PV %.2f" %voltage)
return ret
def set_current(self,data):
ret = False
if len(data) == 2:
_bytes=struct.unpack('h',data)
#print _bytes
current=float(_bytes[0])*self.resolution['current']
ret=self.ps_set("PC %.2f" %current)
return ret
def not_found(self,data):
print "what?"
return False
me = serialTDKLambdaPowerSupply() #!!!!! IMPORTANTE : instanzio l'oggetto

## Utilizzare uno script Python come Plugin

Leonardo Platform Simulator e' in grado di utilizzare uno script Python per ampliare le funzionalita' di base. Tale script si basa sulla classe ProxyPluginObject offerta dal modulo python 'proxypluginobjectmod'.
Tale classe permette di creare un canale diretto di comunicazione con l'applicativo offrendo le stesse possibilita' di un plugin scritto in cpp e compilato
con l'ausilio dell'SDK dedicato alla creazione dei plugin ossia:
- Creazione del menu dedicato ( e sottomenu ) con elementi richiamabili e selezionabili ( addAction )
- Gestione degli eventi menu ( onPluginAction )
- Controllo del flusso degli eventi dell'applicativo ( consumeICDEvent )
- Gestione del database ( lettura scrittura dei campi / messaggi dell ICD )

### addAction

In quanto plugin l'applicativo crea una voce nel menu ( per es. fooPlugin ). Aggiungendo azioni quente vengono associate al menu
( e conseguentemente registrate anche dall'interprete python ) in base ai criteri descritti dagli argomenti di seguito elencati:
`@param action_id : (Integer) l'identificativo con il quale l'azione plugin viene registrata
@param action_name : (String) la stringa con la quale viene plubblicata sull' applicativo
@param action_type : (Integer) il tipo di azione ( voce diretta / sottomenu / elemento selezionabile / insieme di elementi` `selezionabili` `ma esclusivi
@param action_parent : (Integer) l'identificativo del menu padre ( se esistente ) { vedi sottomenu }
`

### onPluginAction

Questa funzione agisce da callback ed e' invocata dall' applicativo in conseguenza della sezione, da parte dell'utente, di una determinata azione associata al plugin. Per individuare univocamente l'azione invocata si utilizza il paramento action_id precedentemente utilizzato nella creazione dell'azione.
Una volta individuata tale azione non resta altro che indirizzare lo script verso la / le funzioni sclete per tale azione.
I parametri del metodo sono :
`@param action_id : (Integer) l'identificativo con il quale l'azione plugin viene registrata
` Per esempio, aggiungo un' azione diretta denominata 'click me' con id 7777 e preparo un metodo onClickMe.
Avro' un elemento grafico nella sezione menu denominato appunto 'click me'.
Alla selezione di tale elemento verra` evocata la funzione onPluginAction con il parametro 7777 dal quale lo script python capira` che proprio tale elemento e' stato selezionato.
A questo punto potro` finalmente eseguire la funzione dedicata, ossia onClickMe.

### consumeICDEvent

Questa funzione agisce da callback ed e' invocata dall' applicativo in conseguenza di azioni riferibili al database ( lettura/scrittura/aggiornamento/evento clock) o allo stato dell'applicativo ( start/stop/error ... ). L'utilita` della decodifica di tali eventi risiede nella possibilita' di sincronizzare operazioni sulla base di ebneti esterni al plugin ( tipicamente vogliamo effettuare un aggiornamento di una serie di dati ciclicamente utilizzando come periodo il clock fornito dall'applicativo

### Esempio

import proxyplugin
import sys
from proxypluginobjectmod import *
import ICDEVENTS
import time
events={}
class Test2ProxyHandler(ProxyHandler):
def __init__(self):
super().__init__()
def start(self):
super().start()
print(self.__class__,dir(self) )
print('Hello, I\'m',self.name)
print(self.interface.getMessageNames())
def consumeICDEvent(self,event_id):
if event_id == ICDEVENTS.ICD_EVENT_ONUPDATE: pass
elif event_id == ICDEVENTS.ICD_EVENT_ONBUSCLOCK:
...
else:
print('event ',events.get(event_id,event_id))
class Test2PluginObject(ProxyPluginObject):
def __init__(self):
#here i specify the factory class to implements the icd handler interfaces
ProxyPluginObject.__init__(self,"ExamplePluginObject",Test2ProxyHandler)
self.addAction(300,'Menu',ActionTypes['MENU'])
self.addAction(301,'Load file',ActionTypes['NORMAL'],300)
def start(self):
"""
for each handlers built perform start operation
"""
print ( self.handlers_dict )
for k,v in self.handlers_dict.items():
print('starting ',k)
v.start()
return True
def onPluginAction(self,action_id):
global root
if action_id == 301:
ret=self.openFileDialog('open file','.','CSV File ( *.csv);;')
print(ret)
if ret is None:
self.messageBox('Operation aborted' ,'foo',2,3)
else:
self.messageBox('Operation Ok' ,'foo')
if __name__ == "__main__":
#build a dictionary for event_id translation
gen = (i for i in dir(ICDEVENTS) if i.startswith("ICD_EVENT"))
for e in list(gen): events[eval( "ICDEVENTS.{}".format(e) )]=e
print(events)
print(dir(ProxyHandler))
print(dir(ProxyPluginObject))
me = globals().get('me')
#if me is not None: del me
me = Test2PluginObject()

# Risoluzione dei Problemi