REpresentational State Transfer (REST) è un tipo di architettura software per sistemi distribuiti come il World Wide Web, l’utilizzo dell’architettura REST è su HTTP, con una struttura degli URL ben definita e metodi HTTP specifici per il recupero di informazioni (GET) e altri per la modifica (POST). Anche il REST fà parte dei protocolli per l’IoT e permette scambio dati tra sistemi distribuiti e server nel cloud, l’utilizzo del protocollo HTTP e la comunicazione senza stato richiedono un limitato uso di risorse sia lato client (Nel nostro caso i ns sistemi programmabili) che lato server.
I vari client scambiano dati con il server tramite protocollo HTTP, l’invio dati dal client al server avviene con metodo POST, ad ogni invio il client riceve i dati in risposta dal server. In questa architettura è il client ad iniziare la connessione HTTP verso il server (Che deve avere un IP pubblico), quindi permette lo scambio dati anche con client connessi su reti NATttate e con IP dinamici. Naturalmente essendo il client ad iniziare la connessione il server per inviare i dati al client deve attendere che si connetta
Questo blocco funzione da eseguire in task Back, gestisce la connessione verso un server REST con protocollo HTTP. Viene inviato il messaggio con i dati in POST (Lunghezza massima 12 Kbytes) e ritornati i dati ricevuti dal server (Lunghezza definita in BLength).
Per garantire il buffering dei dati su eventi molto rapidi, il servizio utilizza il FB FIFOFile per gestire uno stack FIFO in memoria o su di un file, l’indirizzo di istanza và passato in FIFOFile. In HTTPClient occorre indicare l’indirizzo di istanza del FB HTTPClient per gestire la connessione al server REST.
Attivando Enable ogni tempo definito in HBitTime viene inviato un messaggio di heartbeat al server REST, questo permette di controllare il corretto funzionamento del servizio anche in assenza di messaggi da inviare al server. Inoltre permette al server di inviare nella risposta eventuali dati al client. In Delay è possibile impostare il tempo di ritardo tra l’invio di messaggi REST successivi, l’uscita RSvcOn se attiva indica che la comunicazione con il server è attiva.
La comunicazione con il server è in formato JSON e quindi i dati che si desidera scambiare devono essere codificati in JSON. Per inviare messaggi al server REST occorre eseguire il FB FIFOFile che trasferirà il messaggio nel buffer FIFO e successivamente al server. L’utilizzo di un buffer FIFO garantisce che tutti i messaggi siano inviati al server bufferizzandoli nel caso di mancata connessione.
La risposta ricevuta dal server è memorizzata in un buffer interno al FB (Di dimensione BLength il cui indirizzo è ritornato in PBuffer), alla ricezione si attiva SvcOk ed il programma utente potrà utilizzare i dati presenti nel buffer. In caso di errore ricezione dal server si attiva SvcError ed in PBuffer vi è la pagina ricevuta dal server, sarà possibile trasferirla in un file per visualizzarla da browser per indagare sull’errore.
Approfondimenti
- In questo articolo una applicazione REST in pratica.
Upgrade list
RestClient_v6
Identica nel funzionamento alla precedente, questa versione si appoggia alla FB HTTPClient_v4. In PktsOk sono conteggiati anche i pacchetti di heartbeat.
RestClient_v7
Utilizzata la nuva versione del FB HTTPClient_v5. Sono stati eliminati i parametri JEncode e JSONDecode ed utilizzate le nuove funzioni JSONEncoder e JSONDecoder. Modificato tipo parametri Delay, HBitTime e RSvcTime ora sono di tipo TIME.
Descrizione
Enable (BOOL) Comando attivazione connessione al server REST.
SpyOn (BOOL) Se attivo permette di spiare il funzionamento della FB.
DDelete (BOOL) Attivandolo per un loop se errore SvcError, rimuove il dato dal registro FIFO.
FIFOFile (@FIFOFile_v1) Indirizzo allocazione FB FIFOFile di supporto.
HTTPClient (@HTTPClient_v5) Indirizzo allocazione FB HTTPClient di supporto.
BLength (UDINT) Dimensione allocazione buffer risposta ricevuta server REST (SysRMalloc).
Delay (TIME) Tempo pausa invio dati al server.
HBitTime (TIME) Tempo invio heartbeat al server. Ogni tempo definito viene inviato un messaggio di heartbeat al server.
Enabled (BOOL) Blocco funzione abilitato.
Fault (BOOL) Attivo per un loop se errore esecuzione comando.
RSvcOn (BOOL) Servizio REST attivo. Si disattiva se si interrompe la comunicazione per errore su invio messaggi o heartbeat.
SvcSend (BOOL) Attivo su invio dati al server REST, sul fronte di attivazione è possibile acquisire i dati inviati al server nel buffer Request del FB HTTPClient. Non si attiva sull’invio messaggi heartbeat.
SvcOk (BOOL) Ok ricezione da server REST, si attiva per un loop solo se il messaggio ricevuto dal server contiene dati oltre al MID. Su attivazione è possibile acquisire i dati ricevuti nel buffer PBuffer.
SvcError (BOOL) Errore ricezione da server REST, si attiva per un loop su ricezione risposta. Il messaggio viene automaticamente reinviato al server. E’ possibile controllare la risposta del server presente in PBuffer e decidere se eliminare il messaggio dopo un numero di reinvii settando DDelete.
MIDNr (UINT) Identificativo messaggio inviato al server.
PktsOk (UDINT) Numero di pacchetti dati scambiati con il server, compresi i pacchetti di heartbeat.
PBuffer (@STRING) Pointer buffer risposta ricevuta da server REST. Il buffer è allocato nel FB tramite funzione SysRMAlloc. Valido per un solo loop, la lettura dei dati può essere fatta solo su attivazione di SvcOk o SvcError.
MLength (UDINT) Dimensione messaggio REST inviato al server.
RSvcTime (TIME) Tempo esecuzione servizio REST.
Resyncs (UDINT) Counter resincronizzazioni con il server REST. I messaggi scambiati con il server hanno un identificativo MIDNr che ne permette il controllo. In caso di disallineamento (Perdita di un messaggio) viene eseguita una resincronizzazione.
Retries (UDINT) Counter tentativi invio messaggio a server REST. In caso di errore di comunicazione il messaggio viene reinviato automaticamente. Il messaggio viene eliminato dal registro FIFO solo se comunicazione avvenuta correttamente.
Errors (UDINT) Counter errori comunicazione con il server REST.

Comunicazione REST
- Alla abilitazione dopo tempo definito in Delay viene inviato un messaggio di heartbeat del tipo {“MID”:5,”ST”:1,”UID”:17825927,”MV”:”1.0″} al server e se si riceve risposta corretta si attiva RsvcOn e si passa al punto (b). In caso contrario ogni tempo definito in Delay viene inviato un nuovo messaggo di heartbeat.
- Se non vi sono dati da inviare (Non sono stati inseriti dati nel FIFO di appoggio) ogni tempo definito in HBitTime viene inviato un messaggo di heartbeat e controllata la risposta. Se risposta in errore dopo 3 errori consecutivi si resetta RsvcOn e si ritorna al punto (a).
- Se ci sono dati da inviare ogni tempo definito in Delay viene inviato un record dati del tipo {“MID”:1234, “ST”:0, “UID”:10879070, “MV”:”1.0″, “FIFO”:[{“Date”:”09/06/2018 08:20:00″, …}]} e si controlla la risposta del server. Se risposta corretta si continua ad iterare sui punti (b, c). In caso di errore risposta dal server si esegue un reinvio del dato incrementanto il valore in Retries, dopo 3 errori consecutivi si resetta RsvcOn e si ritorna al punto (a). Retries si resetta solo quando l’invio del mesaggio và a buon fine.
Considerazioni
Ogni messaggio inizia con un Message ID MID che viene incrementato ad ogni invio sia dei messaggi di heartbeat che dati, questo permette di controllare la sequenzialità dei messaggi. Nel caso in cui il messaggio ricevuto dal server sia corretto ma abbia un MID diverso dal messaggio inviato si considera come resincronizzazione Resyncs si incrementa ed il messaggio verrà reiviato.Il MID ricevuto incrementato è utilizzato nei nuovi messaggi inviati.
Per risposta corretta dal server si intende la ricezione di un messaggio del tipo {“MID”:5} che contiene il valore di Message ID identico a quello del messaggio inviato. Inoltre l’HTTP status deve essere 200.
Trigger di spy
Se SpyOn attivo è possibile utilizzare utilizzare la console di spionaggio per verificare il funzionamento della FB. Sono previsti vari livelli di triggers.
Trigger di spionaggio
Trigger | Descrizione |
---|---|
16#00010000 | Td: Invio dati verso server REST. |
16#00020000 | Rd: Ricezione dati da server REST. |
16#10000000 | Lg: Log di esecuzione. |
16#40000000 | Er: Errori di esecuzione. |
Esempi
Come utilizzare gli esempi.
Il programma scambia dati con un server REST nel cloud, per facilitare il test del programma abbiamo installato lo script PHP sul nostro server (Link). Per testare il programma basterà eseguirlo su di un sistema SlimLine connesso ad Internet.
Cosa fà il programma LogicLab
Ogni 10 secondi vengono inviati i dati al server REST, come valori di dividendo e divisore sono utilizzati due numeri random nel range da 0-1000. I dati inviati sono memorizzati nell’array RESTData, è stato creato un array di 3 membri in modo da contenere il dividendo, il divisore e d il risultato ritornato dal server.
Viene inizializzata la stringa RESTRequest e con la funzione JSONEncoder creo la stringa di richiesta che è visualizzabile inserendola nella finestra di watch, la stringa è del tipo {“Dividend”:100.5,”Divisor”:10.0}.
La richiesta viene inserita nel registro FIFO dove gli viene automaticamente abbinato un valore di timestamp (in GMT). Utilizzando un FIFO è possibile inserire più stringhe di richiesta indipendentemente dal tempo necessario all’invio verso il server. Il FIFO farà da “polmone” accumulandole, ed anche nel caso non sia possibile la comunicazione con il server le stringhe non verranno perse.
Il FB RESTClient “si accorge” che è stato inserita una nuova stringa nel FIFO e provvede ad inoltrarla al server, la stringa inviata oltre ai dati inseriti avrà ulteriori informazioni utili al server: {“MID”:1234, “ST”:0, “UID”:10879070, “MV”:”1.0″, “FIFO”:[{“Date”:”09/06/2018 08:20:00″, “Value”:{“Dividend”:100.5, “Divisor”:10.0}}]}.
MID: | ID messaggio, numero progressivo incrementato ad ogni messaggio inviato |
ST: | Numero di ritrasmissioni messaggio. Al primo invio il valore è 1, viene incrementato se l’invio di un messaggio non và a buon fine ed il messaggio viene reinviato. |
UID: | UniqueID del sistema dove è in esecuzione il FB |
MV: | Versione del messaggio REST. Al momento è sempre “1.0” |
FIFO: | Stringa caricata da programma nel registro FIFO. E’ un array di valori con il campo Date che ritorna la data (GMT) del caricamento della stringa nel FIFO, ed il campo Value con la stringa caricata. |
A seguito della ricezione dei dati il server REST provvede ad eseguire le operazioni richieste e se necessario può inviare dei dati in risposta. Nel nostro script PHP di esempio eseguiamo la divisione e ritorniamo il risultato con una stringa del tipo: {“Result”:10.05}.
Il programma controlla l’Ok ricezione stringa dal server SvcOk, a solo scopo di debug (Visualizzazione in watch da LogicLab) la stringa ricevuta è copiata in a RESTAnswer. Poi con la funzione JSONDecoder il valore di Result è trasferito nella variabile RESTData[2].
LogicLab (Ptp156, ST_RESTClient)
PROGRAM ST_RESTClient
VAR
i : UDINT; (* Auxiliary variable *)
TimeBf : UDINT; (* Time buffer (mS) *)
RESTData : ARRAY[0..2] OF REAL; (* Data exchanged with REST server *)
RESTRequest : STRING[ 128 ]; (* REST request *)
RESTAnswer : STRING[ 128 ]; (* REST answer *)
FIFO : FIFOFile_v1; (* FIFO on file *)
TCPClient : SysTCPClient; (* TCP client management *)
HTTPRq : HTTPClient_v5; (* HTTP client *)
REST : RESTClient_v7; (* REST service client *)
END_VAR
// *****************************************************************************
// PROGRAM "ST_RESTClient"
// *****************************************************************************
// It connects to the REST server, sends data and manage answer.
// -----------------------------------------------------------------------------
// -------------------------------------------------------------------------
// PROGRAM INIT
// -------------------------------------------------------------------------
// Executed at first program execution, all variables are initialized.
IF (SysFirstLoop) THEN
// If "FIFOFilename" it's not defined the data are stored in memory
// instead of a file on disk. Stored data are lost on system power off.
// If "FIFOIDx" it's not defined the index are stored internally of the
// FB. Stored data are lost on power off.
// FIFO.FIFOFilename:=ADR('C:/REST.bin'); //Path and name of FIFO file
// FIFO.FIFOIDx:=ADR(FIFOIDx); //FIFO indexes
FIFO.FIFOFilename:=eNULL; //Path and name of FIFO file
FIFO.FIFOSize:=2048; //FIFO file size
FIFO.FIFOIDx:=eNULL; //FIFO indexes
// Set TCPClient parameters.
TCPClient.PeerAdd:=ADR('demos.elsist.biz'); //Peer address
TCPClient.PeerPort:=80; //Peer port
TCPClient.LocalAdd:=ADR('0.0.0.0'); //Local address
TCPClient.LocalPort:=0; //Local port
TCPClient.FlushTm:=50; //Flush time (mS)
TCPClient.LifeTm:=20; //Life time (S)
TCPClient.RxSize:=512; //Rx buffer size
TCPClient.TxSize:=512; //Tx buffer size
// Set HTTPClient parameters.
HTTPRq.SpyOn:=TRUE; //Activate the spy
HTTPRq.KeepAlive:=FALSE; //HTTP keep-alive
HTTPRq.RMethod:=HTTP_REQUEST#HTTP_POST; //Request method
HTTPRq.HostName:=ADR('demos.elsist.biz'); // Hostname
HTTPRq.Page:=ADR('eLLabHTTPLib/RESTServer/RESTSvc.php'); //Web page
HTTPRq.Header:=ADR('Content-Type:application/json$r$n'); //HTTP header
HTTPRq.DBSize:=512; //Data buffer size
HTTPRq.Timeout:=T#10s; //Execution timeout
// REST definitions.
REST.SpyOn:=TRUE; //Spy On
REST.BLength:=512; //REST Request/Answer buffers length
REST.Delay:=T#2s; //Delay time
REST.HBitTime:=T#5s; //Heartbeat time
// REST auxiliary object references.
REST.FIFOFile:=ADR(FIFO); //FIFO on file
REST.HTTPClient:=ADR(HTTPRq); //HTTP Client
// Set local variables.
TimeBf:=SysTimeGetMs(); //Time buffer (mS)
END_IF;
// -------------------------------------------------------------------------
// REST CLIENT MANAGEMENT
// -------------------------------------------------------------------------
// Here the REST client is managed, it's always enabled.
TCPClient(Connect:=HTTPRq.Connect); //TCPClient management
HTTPRq(File:=TCPClient.File); //HTTP client
REST(Enable:=TRUE); //REST client management
REST.DDelete:=FALSE; //Delete data from FIFO
// -------------------------------------------------------------------------
// ERROR MANAGEMENT
// -------------------------------------------------------------------------
// On error it's possible to count the retries and then decide what to do.
IF (REST.SvcError) THEN
// After 3 retries the message to be sent to the REST server is removed
// from the FIFO register. Before to remove the message it's returned
// to the spy console.
IF (REST.Retries > 3) THEN
FIFO(Out:=TRUE, Dp:=eNULL, Dls:=0); //Read record length
IF NOT(SysRMAlloc(FIFO.Dl+1, ADR(FIFO.Dp))) THEN RETURN; END_IF;
FIFO(Out:=TRUE, Dls:=FIFO.Dl); //Read record data
i:=SysWrSpyData(SPY_ASCII, 0, 16#00000001, ADR('-Delete-'), FIFO.Dp);
i:=SysRMFree(ADR(FIFO.Dp)); //Free malloc memory
REST.DDelete:=TRUE; //Delete data from FIFO
END_IF;
END_IF;
// -------------------------------------------------------------------------
// SEND DATA TO SERVER
// -------------------------------------------------------------------------
// Request string, preparation and sending, it will be of the type:
// {"MID":0,"ST":1,"UID":10878977,"MV":"1.0"}
// {"Dividend":502.987671,"Divisor":584.047363}
IF ((SysTimeGetMs()-TimeBf) > TO_UDINT(T#10s)) THEN
TimeBf:=SysTimeGetMs(); //Time buffer (mS)
RESTData[0]:=SysGetRandom(TRUE)*1000.0; //Data exchanged with REST server
RESTData[1]:=SysGetRandom(TRUE)*1000.0; //Data exchanged with REST server
i:=Sysmemset(ADR(RESTRequest), 0, SIZEOF(RESTRequest)); //REST request
i:=JSONEncoder(ADR(RESTRequest), SIZEOF(RESTRequest), ADR('Dividend'), REAL_TYPE, ADR(RESTData[0]), 1);
i:=JSONEncoder(ADR(RESTRequest), SIZEOF(RESTRequest), ADR('Divisor'), REAL_TYPE, ADR(RESTData[1]), 1);
FIFO(In:=TRUE, Dp:=ADR(RESTRequest), Dls:=LEN(RESTRequest)); //Write record on FIFO
END_IF;
// -------------------------------------------------------------------------
// RECEIVED VARIABLES ACQUISITION
// -------------------------------------------------------------------------
// When answer is received decodes it's value and store it on variable.
// The REST server answer will be {"Result":"xxxx"}
IF (REST.SvcOk AND (REST.PBuffer <> eNULL)) THEN
// Copy the received data to buffer.
i:=Sysmemset(ADR(RESTAnswer), 0, SIZEOF(RESTAnswer));
i:=Sysstrlen(REST.PBuffer);
IF (i > SIZEOF(RESTAnswer)) THEN i:=SIZEOF(RESTAnswer); END_IF;
i:=Sysmemmove(ADR(RESTAnswer), REST.PBuffer, i);
// Decodes the value of "Result" and stores it to RESTData[2].
i:=JSONDecoder(REST.PBuffer, ADR('Result'), REAL_TYPE, ADR(RESTData[2]), 1, 0);
END_IF;
// [End of file]
Cosa fà lo script PHP
Il FB RESTClient invia i dati in POST, lo script controlla se vi sono dati in POST se vi sono li trasferisce nella variabile $RxMessage. Per poter testare lo script da un comune browser ho previsto di gestire una richiesta in GET, quindi basterà dal browser visualizzare questo link per verificare la risposta.
Successivamente con la funzione json_decode vengono estratti dalla richiesta i dati JSON e trasferiti nell’array associativo $RESTRx. Avendo un array con i campi ed il loro valore abbinato, è intuitivo nel programma accedere ai dati ricevuti. Viene anche inizializzata la variabile $RESTTx per contenere i valori da inviare in risposta al RESTClient.
Viene controllata la correttezza di tutti i dati ricevuti, per quanto riguarda l’ID di messaggio in questo demo è solo controllato il valore 0. Quando il FB RESTClient inizia la connessione parte con MID=0, il server REST invia la risposta con un MID random, da questo punto in avanti il MID di ogni messaggio è il numero successivo al MID precedente (Roll over a 65536). Un errore nella sequenza provoca una risincronizzazione (Il sever REST invia un nuovo numero random).
Se il messaggio non contiene il campo FIFO è un messaggio di heartbeat (Inviato automaticamente), in tal caso si risponde con il solo MID, messaggio del tipo {“MID”:1234}.
Se il messaggio contiene il campo FIFO è un messaggio dati, viene eseguita l’operazione e oltre al MID è ritornato anche il risultato, messaggio del tipo {“MID”:1234, “Result”:10.5}.
Il campo MID è controllato internamente dal FB RESTClient e non viene ritornato nel buffer PBuffer. Nel buffer di ritorno avrò solo i dati che sono presenti nel messaggio di risposta dopo il MID (Esempio {“Result”:10.5}).
PHP (Ptp156, RESTSvc)
<?php
// *****************************************************************************
// Project : SFR091C800
// Programmer : Sergio Bertana
// Date : 10/05/2022
// *****************************************************************************
// PHP example script for RESTClient FB.
// -----------------------------------------------------------------------------
error_reporting(E_ALL);
ini_set('display_errors', 1);
// This is useful only to allow to test the script directly by a browser.
// Use http://www.slimline.altervista.org/Utilities/RESTSvcDemo/RESTSvc.php?Post={"MID":1234, "ST":0, "UID":10879070, "MV":"1.0"}
// Use http://www.slimline.altervista.org/Utilities/RESTSvcDemo/RESTSvc.php?Post={"MID":1234, "ST":0, "UID":10879070, "MV":"1.0", "FIFO":[{"Date":"09/06/2018 08:20:00", "Value":{"Dividend":100, "Divisor":10}}]}
isset($_REQUEST['Post'])?$RxMessage=$_REQUEST['Post']:$RxMessage=file_get_contents("php://input");
// Convert to array the JSON received string and initialize the answer array
$RESTRx=json_decode($RxMessage, true); //Received array
$RESTTx=array(); //Answer array
// The request must have the fields, MID, ST, UID, MV. exit on error.
if ($RESTRx == null ) exit("Wrong REST parameters, Received:[{$RxMessage}]");
if (!isset($RESTRx['MID'])) exit("MID not in message, Received:[{$RxMessage}]");
if (!isset($RESTRx['ST'])) exit("ST not in message, Received:[{$RxMessage}]");
if (!isset($RESTRx['UID'])) exit("UID not in message, Received:[{$RxMessage}]");
if (!isset($RESTRx['MV'])) exit("MV not in message, Received:[{$RxMessage}]");
// Checks numeric fields. If the FIFO field is not present is an heartbeat message.
if (!is_numeric($RESTRx['MID'])) exit("Wrong system MID, Received:[{$RxMessage}]");
if (!is_numeric($RESTRx['UID'])) exit("Wrong system UID, Received:[{$RxMessage}]");
// This is a demo, so the message ID is not checked, if it's 0 a random number is sent back.
// The FB RESTClient increases the Resyncs counter and use this number as MID.
$MID=($RESTRx['MID'] == 0)?rand(0, 65535):$RESTRx['MID']; //Message ID
// Check if it's an heartbeat message.
if (!isset($RESTRx['FIFO'])) goto SENDDATA;
// Converting data and time received into a Epoch time ((GMT).
list($Day, $Month, $Year, $Hour, $Minute, $Second)=sscanf($RESTRx['FIFO'][0]['Date'], "%02d/%02d/%04d %02d:%02d:%02d");
$ETime=gmmktime($Hour, $Minute, $Second, $Month, $Day, $Year); //Epoch time (GMT) FIFO record
// Executing the operations needed.
$RESTTx['Result']=$RESTRx['FIFO'][0]['Value']['Dividend']/$RESTRx['FIFO'][0]['Value']['Divisor'];
// A message with is sent to the client (MID must be the first JSON object).
// The message sent to client could be:
// {"MID":1234} //Answer to hearbeat message
// {"MID":1234,"Result":10} //Answer to a data message
SENDDATA:
$RESTTx=array_merge(array("MID" => (int)$MID), $RESTTx);
exit(json_encode($RESTTx));
?>