Supervisione e controllo real-real-time con SignalR e .NETMF

  • RSS
  • Add To My MSN
  • Add To Windows Live
  • Add To My Yahoo
  • Add To Google
Post di Lorenzo Maiorfi domenica 5 febbraio 2012 13:00:00
Valuta questo articolo 0 Voti

Chi segue gli sviluppi e l’evoluzione dei sistemi per quella che viene denominata “Internet-delle-cose” (IoT, Internet of Things), sa che uno degli aspetti più intriganti riguarda la possibilità di supervisionare (ossia osservare ed agire) sistemi fisici remoti. Le piattaforme più interessanti in tal senso, Pachube e ThingSpeak, forniscono l’infrastruttura di base per una comunicazione bidirezionale, strutturata tipicamente intorno a interfacce REST/JSON, con cui i client (web, app, servizi, ecc.) interagiscono da remoto con device elettronici che a loro volta fanno capo a sensori, motori, sistemi di comunicazione periferici, ecc.

Quello che di fatto però servizi quali Pachube e ThingSpeak non permettono, per motivi legati agli inevitabili compromessi tra costi di infrastruttura e valore dell’implementazione, sono il controllo del sistema fisico veramente in real-time, ossia quello che scherzosamente ho chiamato nel titolo “real-real-time”, in cui ogni azione svolta sul sistema da remoto, ogni variazione autonoma dello stesso così come ogni notifica di variazione vengono gestite in tempi inferiori al decimo di secondo, oltre il quale l’utente perde la percezione di “tempo reale”.

Se ci poniamo come obiettivo inoltre quello di utilizzare client realizzati come applicazioni web in senso tradizionale, ossia che non utilizzino naturalmente un modello di messaging “push”, le scelte tecnologiche possibili restano poche, anzi pochissime: Flash (poco portabile soprattutto in ambito mobile), Silverlight (anch’esso un po’ troppo vincolante come potenziale platea di client), WebSockets (non ancora stabili né implementati completamente nella maggior parte dei browser, mobile e non), Applet Java (praticamente scomparse dal web e comunque poco portabili sulle nuove piattaforme) e, l’attuale miglior candidato, lo straordinario SignaR.

Senza addentrarci nei dettagli, per i quali vi rimando al Wiki del progetto su GitHub e ad un post di “recensione” di Scott Hanselman, SignalR è un framework per ASP.NET (lato server) e HTML+JS, .NET4 e WP7 (lato client) che implementa il supporto per la comunicazione bidirezionale tra client e server. Sfruttando un modello costruito intorno ai tipi “dynamic”, SignalR rende ad esempio possibile chiamare un metodo di una classe server-side da parte di uno script JavaScript e, decisamente più stupefacente, chiamare una funzione JavaScript del client da parte del server! Quello che è ancora più interessante è che SignalR semplifica in maniera imbarazzante la creazione di “concentratori" (chiamati Hub) che, oltre a ricevere chiamate dai client,  possono diramare in broadcasting messaggi a tutti i client collegati a quell’Hub.

Nell’esempio che ho realizzato, finalizzato proprio al controllo di N uscite digitali, N ingressi digitali e N ingressi analogici di un sistema, ho creato un Hub SignalR che comunica, con la mediazione di un oggetto Singleton che serializza le comunicazioni che per loro natura sarebbero parallele proveniendo da più client simultaneamente, con una scheda .NET MF (un FezPanda-II con collegati 2 led, un pulsante e un potenziometro) via Socket TCP/IP, bidirezionale e full-duplex per natura.

L’applicazione client è costituita da una singola pagina ASPX che contiene il pochissimo markup relativo ai due pulsanti per il toggling delle uscite digitali e ad un <div> per ciascuna delle grandezze rilevate (gli stati del pulsante, dei led e del potenziometro) e una cinquantina di righe di JavaScript per la attivazione dell’Hub SignalR e per il processing dei messaggi scambiati con il server. Nella sua attuale (e temporanea) implementazione l’applicazione si presenta così, più o meno indipendentemente dal browser (desktop, tablet o smartphone) su cui è stata testata:

Il sistema da controllare invece è illustrato nella foto che segue:

Nel test complessivo del sistema ho tenuto aperti simultaneamente 3 browser (Chrome, Firefox e IE) su due diversi PC, IE su un telefono WP7 e il browser di default su un telefono Android, rilevando ad esempio che (su rete locale) premendo il pulsante rosso lo stato delle otto istanze di browser veniva aggiornato simultaneamente dopo molto meno di un decimo di secondo!

Nei prossimi giorni pubblicherò il sistema su rete pubblica, così da poterr meglio valutare (magari anche grazie al vostro contributo in qualità di utenti client) gli effettivi tempi di risposta su Internet.

Diamo un’occhiata a dietro le quinte, ad iniziare dalla porzione di JavaScript client deputata a stabilire la connessione con il server e a gestire la comunicazione bidirezionale:

 1: var out1state = 0;
 2: var out2state = 0;
 3:  
 4: $(function () {
 5:  
 6:     // Creazione Proxy Automatica
 7:     var miohub = $.connection.mioHub;
 8:  
 9:     // Questa funzione è richiamata dal server all'arrivo di ogni nuova notifica
 10:     miohub.notifyMessage =
 11:     function (msg) {
 12:  
 13:         // E' l'aggiornamento dello stato iniziale (pagina appena caricata)?
 14:         if (msg.indexOf('DUMP:') != -1) {
 15:  
 16:             var dumppayload=msg.split(':')[1];
 17:  
 18:             var dumpparts = dumppayload.split(';');
 19:  
 20:             for (var i = 0; i < dumpparts.length; i++) {
 21:                 processMessage(dumpparts[i]);
 22:             }
 23:  
 24:             // mando un ack al server che mi sono allineato allo stato corrente del sistema
 25:             window.setTimeout(function () {
 26:                 miohub.sendMessage('DUMP_OK');
 27:             }, 1000);
 28:         }
 29:         else {  // E' invece un aggiornamento dello stato?
 30:             
 31:             processMessage(msg);
 32:         }
 33:  
 34:     };
 35:  
 36:     // Invocazione del metodo server SendMessage() da parte del client, al click sui pulsanti
 37:     $('#btnOUT1').click(
 38:     function () {
 39:  
 40:         out1state = (out1state == 1 ? 0 : 1);
 41:  
 42:         miohub.sendMessage('OUT 1,' + out1state);
 43:     });
 44:  
 45:     $('#btnOUT2').click(
 46:     function () {
 47:  
 48:         out2state = (out2state == 1 ? 0 : 1);
 49:  
 50:         miohub.sendMessage('OUT 2,' + out2state);
 51:     });
 52:  
 53:     // Avviamento dell'Hub SignalR
 54:     $.connection.hub.start();
 55:  
 56:     // 1 secondo e poi richiedo l'aggiornamento iniziale
 57:     window.setTimeout(function () {
 58:         miohub.sendMessage('DUMP');
 59:     }, 1000)
 60: });

Il codice server-side (semplificato per renderlo più leggibile) consiste nella definizione di una classe derivata da Hub di SignalR, in cui sia definito il metodo SendMessage(string msg) richiamato dal client e che all’occorrenza richiami la funzione notifyMessage(msg) del client:

 1: using System;
 2: using System.Collections.Generic;
 3: using System.Linq;
 4: using System.Web;
 5: using SignalR.Hubs;
 6: using SocketChannel;
 7: using SignalR;
 8:  
 9: namespace DemoHub
 10: {
 11:     // Definisco un mio Hub
 12:     public class MioHub : Hub
 13:     {
 14:         // Nel costruttore mi sottoscrivo all'evento di ricezione dei messaggi
 15:         // via Socket da parte del dispositivo .NET MF
 16:         public MioHub()
 17:             : base()
 18:         {
 19:             ChannelManager.MessageReceived += new Channels.MessageEventHandler(ChannelManager_MessageReceived);
 20:         }
 21:  
 22:         // All'arrivo di un messaggio da parte del dispositivo...
 23:         void ChannelManager_MessageReceived(object sender, Channels.MessageEventArgs e)
 24:         {
 25:             // ChannelManager è un singleton che gestisce la comunicazione via Socket con il dispositivo,
 26:             // poiché gli Hub sono reistanziati per ogni nuova connessione stabilita da un client,
 27:             // devo assicurarmi di spedire le notifiche solo una volta, evitando invii multipli
 28:             // ChannelManager resetta Notified a false prima di sollevare l'evento MessageReceived
 29:             if (ChannelManager.Notified) return;
 30:  
 31:             string msg=e.Message.Trim('\n','\r','\t');
 32:  
 33:             // Richiesta iniziale per conoscere lo stato corrente del sistema?
 34:             if (msg.ToUpper().StartsWith("DUMP:"))
 35:             {
 36:                 // Notifico con lo stato corrente solo il gruppo dei client
 37:                 // che si sono appena collegati
 38:                 Clients["DUMP"].notifyMessage(msg);
 39:             }
 40:             else
 41:             {
 42:                 // Notifico le variazioni a tutti i client
 43:                 Clients.notifyMessage(msg);
 44:             }
 45:  
 46:             ChannelManager.Notified = true;
 47:         }
 48:  
 49:         // Questo metodo viene chiamato direttamente dai client
 50:         public void SendMessage(string message)
 51:         {
 52:             // Il client ha allineato il proprio stato allo stato corrente del sistema
 53:             if (message == "DUMP_OK")
 54:             {
 55:                 RemoveFromGroup("DUMP");
 56:                 return;
 57:             }
 58:  
 59:             // Il client vuole lo stato corrente del sistema
 60:             if (message == "DUMP")
 61:             {
 62:                 AddToGroup("DUMP");
 63:             }
 64:  
 65:             // Il client ha mandato un messaggio che altera lo stato del sistema
 66:             // Invio il comando al dispositivo .NETMF (ad es. per impostare un'uscita digitale)
 67:             ChannelManager.SendMessage(message);
 68:         }
 69:     }
 70: }

Che dire…prodigioso!

I commenti su questo intervento sono chiusi.