Vai al contenuto

Sviluppo FB interprete a linea di comando

Viene sviluppato il blocco funzione CmdLineIFServer per i sistemi SlimLine basati su LogicLab. Il FB gestisce un interprete a linea di comando che permette di interagire con il sistema connettendosi in TCP/IP o in seriale ed impartire comandi. Il FB è scritto in linguaggio ST ed è modificabile in modo da adattarlo alla proprie esigenze, è possibile implementare nuovi comandi per interagire con il sistema. Una evoluzione di questo FB con funzionalità avanzate si trova nel FB CLIServer.

Passando al FB uno stream di comunicazione Fp, è possibile gestire comandi testuali che eseguono operazioni e ne ritornano il risultato. Come stream di comunicazione è possibile utilizzare una connessione seriale (Utilizzando il FB SysSerialPort), un canale UDP (Utilizzando i FB SysUDPServer, SysUDPClient) o un socket TCP (Utilizzando i FB SysTCPServer, SysTCPClient). Lo scopo del FB è dimostrativo quindi sono gestiti solo due comandi, ma modificando il programma sarà possibile implementare tutti i comandi desiderati. I comandi gestiti sono:

  • Inp<CR>: Ritorna il valore della variabile Inp di tipo REAL.
  • Out xxxx<CR>: Imposta il valore nella variabile Out di tipo REAL

Descrizione

Fp (@eFILEP) Stream di comunicazione.
Inp (REAL) Valore ingresso, il valore della variabile viene ritornato sul comando Inp<CR>.
Out (REAL) Valore uscita, questa variabile è valorizzata dal comando Out xxxx<CR>.

Immagine FB CmdLineIFServer

Sviluppo del FB

Il FB è sviluppato in linguaggio ST (Structured Text) ed è realizzato utilizzando lo statement CASE per gestirne l’esecuzione a stati. In base al valore della variabile CaseNr il programma esegue le operazioni richieste.

Messaggio di benvenuto

Alla connessione utente viene ritornato un messaggio di benvenuto. Ecco il listato programma che ne gestisce la visualizzazione.

    IF (SysFIsOpen(Fp) <> WMsgPulse) THEN
WMsgPulse:=SysFIsOpen(Fp); //Welcome message pulse
CaseNr:=0; //Program cases

IF (WMsgPulse) THEN
VisitorNr:=VisitorNr+1; //Visitor number
i:=SysVfprintf(Fp, ADR('Welcome, You are visitor Nr:%d$r$n'), USINT_TYPE, ADR(VisitorNr));
END_IF;
END_IF;

La funzione SysFIsOpen(Fp) ritorna TRUE solo se lo stream di comunicazione è aperto. Se la connessione è di tipo TCP/IP la funzione ritornerà TRUE solo alla connessione client al socket server di comunicazione. In questo modo quindi quando il client si connette (Esempio utilizando un Telnet) sul terminale del cliente verrà visualizzato il messaggio di benvenuto.

Se la connessione è di tipo UDP o seriale, essendo tipi di connessione stateless è impossibile determinare quando si instaura la connessione, quindi la funzione SysFIsOpen(Fp) ritorna sempre TRUE, ed il messaggio allora verrà inviato alla prima esecuzione del FB. E se il terminale client non è connesso andrà perso.

Esecuzione sequenze

Siccome il FB viene eseguito ciclicamente l’esecuzione è gestita con sequenze. Ad ogni sequenza si esegue una certa funzione, terminata la quale si passa alla sequenza successiva. Per evitare di uscire dal FB per un loop ad ogni passaggio di sequenza la gestione delle sequenze è chiusa in un ciclo:

WHILE.WHILE (TRUE) DO
CASE (CaseNr) OF
0: ...
1: ...
END_CASE;
END_WHILE;

Ricezione comando

Il comando viene digitato dall’utente carattere dopo carattere, quindi occorre ricevere tutti i caratteri in ingresso dallo stream e bufferizzarli in una stringa in modo da poterli poi elaborare.

    // -----------------------------------------------------------------
// COMMAND STRING RECEPTION
// -----------------------------------------------------------------
// Initialize string reception.

0:
i:=TO_INT(SysFIBfClear(Fp)); //Empty input buffer
Ptr:=ADR(RxCommand); //Auxiliary pointer
CaseNr:=CaseNr+1; //Program case

// -----------------------------------------------------------------
// The command must terminate with a <CR> character, the reception
// loops until has been received.

1:
IF NOT(TO_BOOL(SysFGetIChars(Fp))) THEN RETURN; END_IF;
i:=eSetBYTE(Ptr, TO_BYTE(Sysfgetc(Fp))); //Character read
IF (eGetBYTE(Ptr) = 16#0D) THEN
i:=eSetBYTE(Ptr, 16#00); //Terminate command string
i:=TO_INT(SysFIBfClear(Fp)); //Empty input buffer
CaseNr:=10; //Program case
RETURN;
END_IF;

// Increase the string pointer and check its range.
// Exit on error.

Ptr:=Ptr+1; //Auxiliary pointer
IF (TO_UDINT(Ptr) >= TO_UDINT(ADR(RxCommand))+SIZEOF(RxCommand)) THEN
i:=SysVfprintf(Fp, ADR('%s$r$n'), STRING_TYPE, ADR('Command too long'));
CaseNr:=0; //Program case
RETURN;
END_IF;

Case 0: Si inizializza il puntatore Ptr al buffer di ricezione comando, in uesto modo sarà possibile utilizzarlo per memorizzare nel buffer i caratteri ricevuti.

Case 1: La funzione SysFGetIChars(Fp) ritorna il numero di caratteri ricevuti dallo stream, se non ci sono caratteri con il RETURN si termina l’esecuzione del FB e si attende il prossimo loop di esecuzione.

Se vi è almeno un carattere con la funzione Sysfgetc(Fp) si legge un carattere ricevuto che viene rimosso dallo stream e lo si memorizza nel buffer ricezione comando. Siccome tutti i comandi terminano con il <CR> viene controllato se il carattere ricevuto è un <CR> codice ascii 16#0D. Nel caso in cui lo sia viene sostituito nel buffer con il codice tappo di fine stringa 16#00 e si passa alla interpretazione del comando ricevuto CaseNr:=10, con il RETURN si termina l’esecuzione del FB e si attende il prossimo loop di esecuzione.

In caso contrario si incrementa il puntatore e si controlla che il comando non superi la dimensione definita per il buffer di ricezione. Nel caso di superamento viene abortita la ricezione.

Comando “Inp”

Il comando “Inp” permette di ritornare il valore della variabile di ingresso Inp.

    10:
IF (SysStrFind(ADR(RxCommand),ADR('Inp'), FIND_DEFAULT) = NULL) THEN CaseNr:=20; RETURN; END_IF;
i:=SysVfprintf(Fp, ADR('Inp %f$r$n'), REAL_TYPE, ADR(Inp));
CaseNr:=0; // Program case
RETURN;

Case 10: Con la funzione SysStrFind viene verificato se nel buffer RxCommand che contiene il comando ricevuto si trova la stringa “Inp”, se non trovata la funzione ritorna NULL e si passa a controllare un’altro comando. Se la stringa viene trovata viene ritornato il valore della variabile Inp passata in ingresso al FB, poi si ritorna al case 0 reinizializzando la gestione.

Comando “Out”

Il comando “Out” permette di impostare il valore della variabile di uscita Out.

    20:
Ptr:=SysStrFind(ADR(RxCommand),ADR('Out '), FIND_GET_END); // Auxiliary pointer
IF (Ptr = NULL) THEN CaseNr:=30; RETURN; END_IF;

// Acquire the value to be set.

IF NOT(SysVsscanf(Ptr, ADR('%f'), REAL_TYPE, ADR(Out))) THEN
i:=SysVfprintf(Fp, ADR('%s$r$n'), STRING_TYPE, ADR('"Out" command error'));
CaseNr:=0; // Program case
RETURN;
END_IF;

// Return the value set.

i:=SysVfprintf(Fp, ADR('Out %f, Ok$r$n'), REAL_TYPE, ADR(Out));
CaseNr:=0; // Program case
RETURN;

Case 20: Con la funzione SysStrFind viene verificato se nel buffer RxCommand che contiene il comando ricevuto si trova la stringa “Out”, se non trovata la funzione ritorna NULL e si passa a controllare un’altro comando.

Se la stringa viene trovata Ptr punta la posizione del buffer RxCommand successivo al comando, passando questo valore alla funzione SysVarsscanf sarà possibile acquisire il valore definito in linea al comando. Nel caso non sia possibile acquisire il valore viene ritornato errore e si reinizializza la gestione.

Se il valore è acquisito correttamente viene visualizzato un messaggio con il valore definito.

Errore comando

Se non sono stati trovati i comandi gestiti, si arriva al case 30 dove viene gestito il messaggio di errore.

    30:
i:=SysVfprintf(Fp, ADR('%s$r$n'), STRING_TYPE, ADR('Wrong command'));
CaseNr:=0; //Program case
RETURN;

Case 30: Qui è possibile aggiungere l’interpretazione di altri comandi


Utilizzo con Socket TCP

Per collegare l’interprete comandi ad un socket TCP si può realizzare un programma in FBD dove si istanzia il FB realizzato insieme al FB di gestione socket TCP server.

Il FB SysTCPServer è posto in ascolto sulla porta 1000, ed accetta 1 sola connessione, lo stream di comunicazione Fp valorizzato dal server TCP è passato al FB interprete. Quando un client TCP (Esempio Toolly) si connette Fp si apre e l’interprete invierà il messaggio di benvenuto gestendo i comandi ricevuti.

Esempi

Come utilizzare gli esempi.
Di seguito riportiamo i sorgenti sia del FB CmdLineIFServer, che del programma ST ST_CLineOnTCPServer di gestione da socket TCP.

LogicLab (Ptp151, CmdLineIFServer)
FUNCTION_BLOCK CmdLineIFServer
VAR
    WMsgPulse : BOOL; (* Welcome message pulse *)
    CaseNr : USINT; (* Program case *)
    VisitorNr : USINT; (* Visitor number *)
    RxCommand : STRING[ 16 ]; (* Rx command buffer *)
    Ptr : @STRING; (* Auxiliary pointer *)
END_VAR

VAR_INPUT
    Fp : eFILEP; (* File pointer *)
    Inp : REAL; (* Input varaiabile *)
END_VAR

VAR_OUTPUT
    Out : REAL; (* Output variable *)
END_VAR

// *****************************************************************************
// FUNCTION BLOCK "CmdLineIFServer"
// *****************************************************************************
// The FB manages a command line interpreter.
// Visit: https://support.elsist.biz/articoli/sviluppo-fb-interprete-a-linea-di-comando/
// -----------------------------------------------------------------------------

    // -------------------------------------------------------------------------
    // WELCOME MESSAGE
    // -------------------------------------------------------------------------
    // On user connection a welcome message is returned. If TCP/IP connection
    // the message is returned to client connection. If serial connection the
    // message is returned at program execution start and if no terminal is
    // connected to serial port it will be lost.
    // -------------------------------------------------------------------------
    // On the connection the welcome message is returned.

    IF (SysFIsOpen(Fp) <> WMsgPulse) THEN
        WMsgPulse:=SysFIsOpen(Fp); //Welcome message pulse
         CaseNr:=0; //Program cases

        IF (WMsgPulse) THEN
            VisitorNr:=VisitorNr+1; //Visitor number
            eTO_JUNK(SysVfprintf(Fp, ADR('Welcome, You are visitor Nr:%d$r$n'), USINT_TYPE, ADR(VisitorNr)));
        END_IF;
    END_IF;

    // -------------------------------------------------------------------------
    // EXECUTION SEQUENCIES
    // -------------------------------------------------------------------------
    // Since the FB is cyclically executed, execution is divided on sequences.
    // At each sequence certain functions are executed, after we move on to the
    // next sequence. To avoid FB exiting for a loop at each sequence step, the
    // sequence management is closed in a WHILE loop.
    // -------------------------------------------------------------------------
    // Sequence management.

    WHILE (TRUE) DO
        CASE (CaseNr) OF

            // -----------------------------------------------------------------
            // COMMAND STRING RECEPTION
            // -----------------------------------------------------------------
            // Initialize string reception.

            0:
             eTO_JUNK(SysFIBfClear(Fp)); //Empty input buffer
            Ptr:=ADR(RxCommand); //Auxiliary pointer
             CaseNr:=CaseNr+1; //Program case

            // -----------------------------------------------------------------
            // The command must terminate with a  character, the reception
            // loops until has been received.

            1:
            IF NOT(TO_BOOL(SysFGetIChars(Fp))) THEN RETURN; END_IF;
            eTO_JUNK(eSetBYTE(Ptr, TO_BYTE(Sysfgetc(Fp)))); //Character read
            IF (eGetBYTE(Ptr) = 16#0D) THEN
                eTO_JUNK(eSetBYTE(Ptr, 16#00)); //Terminate command string
                 eTO_JUNK(SysFIBfClear(Fp)); //Empty input buffer
                CaseNr:=10; //Program case
                RETURN;
            END_IF;

            // Increase the string pointer and check its range.
            // Exit on error.

            Ptr:=Ptr+1; //Auxiliary pointer
            IF (TO_UDINT(Ptr) >= TO_UDINT(ADR(RxCommand))+SIZEOF(RxCommand)) THEN
                eTO_JUNK(SysVfprintf(Fp, ADR('%s$r$n'), STRING_TYPE, ADR('Command too long')));
                CaseNr:=0; //Program case
                RETURN;
            END_IF;

            // -----------------------------------------------------------------
            // MANAGE "Inp" COMMAND
            // -----------------------------------------------------------------
            // The command returns the "Inp" variable value.

            10:
            IF (SysStrFind(ADR(RxCommand),ADR('Inp'), FIND_DEFAULT) = NULL) THEN CaseNr:=20; RETURN; END_IF;
            eTO_JUNK(SysVfprintf(Fp, ADR('Inp %f$r$n'), REAL_TYPE, ADR(Inp)));
             CaseNr:=0; // Program case
             RETURN;

            // -----------------------------------------------------------------
            // MANAGE "Out" COMMAND
            // -----------------------------------------------------------------
            // This command sets the "Out xxxx" variable value.

            20:
            Ptr:=SysStrFind(ADR(RxCommand),ADR('Out '), FIND_GET_END); // Auxiliary pointer
            IF (Ptr = NULL) THEN CaseNr:=30; RETURN; END_IF;

            // Acquire the value to be set.

            IF NOT(SysVsscanf(Ptr, ADR('%f'), REAL_TYPE, ADR(Out))) THEN
                eTO_JUNK(SysVfprintf(Fp, ADR('%s$r$n'), STRING_TYPE, ADR('"Out" command error')));
                 CaseNr:=0; // Program case
                 RETURN;
            END_IF;

            // Return the value set.

            eTO_JUNK(SysVfprintf(Fp, ADR('Out %f, Ok$r$n'), REAL_TYPE, ADR(Out)));
             CaseNr:=0; // Program case
             RETURN;

            // -----------------------------------------------------------------
            // COMMAND ERROR
            // -----------------------------------------------------------------
            // If the command is not managed an error is returned.

            30:
            eTO_JUNK(SysVfprintf(Fp, ADR('%s$r$n'), STRING_TYPE, ADR('Wrong command')));
             CaseNr:=0; //Program case
             RETURN;
        END_CASE;
    END_WHILE;

// [End of file]
LogicLab (Ptp151, ST_CLineOnTCPServer)
PROGRAM ST_CLineOnTCPServer
VAR
    Fp : eFILEP; (* File pointer *)
    TCPServer : SysTCPServer; (* TCP server *)
    CmdIF : CmdLineIFServer; (* Command line interface server *)
END_VAR

// *****************************************************************************
// PROGRAM "ST_CLineOnTCPServer"
// *****************************************************************************
// By connecting to the program with a terminal emulator (for example the
// Terminal utility of Toolly) it's possible send commands.
// -----------------------------------------------------------------------------

    // -------------------------------------------------------------------------
    // INITIALIZATION
    // -------------------------------------------------------------------------
    // Program initializations.

    IF (SysFirstLoop) THEN

        // TCPClient initialization.

        TCPServer.FilesArr:=ADR(Fp); //Files array
        TCPServer.LocalAdd:=ADR('0.0.0.0'); //Local address
        TCPServer.LocalPort:=2000; //Local port
        TCPServer.MaxConn:=1; //Accepted connections
        TCPServer.FlushTm:=50; //Flush time (mS)
        TCPServer.LifeTm:=30; //Life time (S)
        TCPServer.RxSize:=128; //Rx buffer size
        TCPServer.TxSize:=256; //Tx buffer size
    END_IF;

    // -------------------------------------------------------------------------
    // FBs EXECUTION
    // -------------------------------------------------------------------------
    // Manage the FBs.

    TCPServer(Enable:=TRUE); //TCPServer management
    CmdIF(Fp:=Fp); //Command interface server

// [End of file]
Was this article helpful?