logo leonardo

Leonardo Platform Simulator Reference Manual

 


  1. Introduzione
  2. Configurazione
    1. File INI
      1. ConfigFile
      2. BusDriver
      3. UiFile
      4. Plugin
      5. IniScript
  3. Caratteristiche
  4. Funzionalita` di Base
    1. Argomenti globali
      1. Session
      2. appdata
      3. Ini
      4. Python
    2. Modalita' grafica
      1. GUI Mode
        1. comando e controllo { controllo interfaccia / bus }
        2. indicatore messaggi
        3. iniettore errori di protocollo
        4. gestione scrittura ed invio dei dati
          1. Commit Changes
          2. Revert Changes
          3. Commit on the Fly
          4. Invio di messaggi non modificati
          5. richiesta ricezione messaggio
      2. Python Mode
        1. controlli Python
        2. Python Script #0
        3. Terminal #(n)
    3. CLI ( Command Line Interpreter )
    4. Batch
    5. Log di un traffico dati
      1. Contenuto del file di log
    6. Plugins
  5. Funzionalita` avanzate
    1. Modificare layout di interfaccia
    2. Utilizzare uno script Python come Bus
      1. Esempio
    3. Utilizzare uno script Python come Plugin
      1. addAction
      2. onPluginAction
      3. consumeICDEvent
      4. Esempio
  6. Risoluzione dei Problemi

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:

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.

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:
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:

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:

The following C math functions are also supported:

Also, the following constants are available:

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

gui 1
Nella seguente modalita' l'applicativo esegue le seguenti operazioni:
L'utente ha la possibilita' di accedere ai campi di tutti i messaggi implemenati  per ogni singola interfaccia sia

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:

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:

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:

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

toolbar python

Da sx a ds:
Tali funzionalita' sono disponibili anche da menu Edit > Python

menu python

Python Script #0

python mode 1

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)

terminale python

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:

Esempio

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

cli

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:

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:


Esempio

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

batch output

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:
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:
 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:
L'applicativo gestisce le seguenti modifiche del layout:

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:

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