ZigBee/XBee

Valuta questo articolo 1 Voti

Autori: Lorenzo Maiorfi e Gianluca Ruta

Uno dei campi tecnologici in cui è più evidente il fermento da parte di chi realizza sistemi per soluzioni embedded è senza dubbio quello che riguarda le comunicazioni via radio. Gli standard più utilizzati nei sistemi attuali sono Wi-Fi, Bluetooth e ZigBee, ciascuno con le proprie peculiarità in termini di funzionalità, velocità, consumi, complessità, ecc.

In particolare, il protocollo che in questo momento rappresenta il compromesso più interessante nell'ambito dello sviluppo di soluzioni embedded è senza dubbio Zigbee.

ZigBee opera nelle frequenze radio assegnate per scopi industriali, scientifici e medici (ISM); 868 MHz in Europa, 915 MHz negli Stati Uniti e 2,4 GHz nella maggior parte del resto del mondo. Nel 2005 il costo stimato per il ricetrasmettitore di un nodo ZigBee era di circa $1.10 per il produttore, contando grossi volumi. La maggior parte dei dispositivi ZigBee richiedono però anche un microcontrollore, che fa alzare il costo totale. Quando fu lanciato (1998), per il Bluetooth si prevedeva un costo di $4–$6 per grandi volumi, mentre il prezzo attuale per la fascia consumer è oggi sotto i $3. I protocolli ZigBee sono progettati per l'uso in applicazioni embedded che richiedano un basso transfer rate e bassi consumi. L'obiettivo attuale di ZigBee è di definire una ‘wireless mesh network’ economica e autogestita che possa essere utilizzata per scopi quali il controllo industriale, le reti di sensori, domotica, telecomunicazioni, ecc. La rete risultante avrà un consumo energetico talmente basso da poter funzionare per uno o due anni sfruttando la batteria incorporata nei singoli nodi.

Ci sono tre differenti tipi di nodo ZigBee:

  • Coordinator (ZC): è il dispositivo più "intelligente" tra quelli disponibili, costituisce la radice di una rete ZigBee e può operare da ponte tra più reti. Ci può essere un solo Coordinator in ogni rete. Esso è inoltre in grado di memorizzare informazioni riguardo alla sua rete e può agire come deposito per le chiavi di sicurezza.
  • Router (ZR): questi dispositivi agiscono come router intermedi passando i dati da e verso altri dispositivi.
  • End Device (ZED): includono solo le funzionalità minime per dialogare con il suo nodo parente (Coordinator o Router), non possono trasmettere dati provenienti da altri dispositivi; sono i nodi che richiedono il minor quantitativo di memoria e quindi risultano spesso più economici rispetto ai ZR o ai ZC.

I moduli ZigBee più diffusi sono senza dubbio gli Xbee, prodotti dalla società statunitense Digi. Tali moduli contengono al loro interno un transceiver in grado di operare sulle frequenze stabilite dal protocollo con una potenza che a seconda dei modelli varia da 1mW a circa 60mW, un'antenna integrata o un connettore per antenne esterne ed un microcontrollore. I moduli Xbee possono essere collegati ad altri dispositivi embedded, tipicamente all'interno di una scheda a microcontrollore, tramite un'interfaccia UART CMOS con livelli a 3.3V. Tali moduli possono inoltre agire come veri e propri sensori/attuatori remoti indipendenti grazie al fatto che le funzionalità di gestione degli I/O on-board sono pilotabili tramite comunicazione wireless. Questi I/O permettono ad esempio di rilevare lo stato di ingressi analogici o digitali, pilotare uscite digitali e addirittura generare segnali modulati tramite PWM, particolarmente utili ai fini del controllo di sistemi di potenza.

I moduli Xbee prevedono due modalità di comunicazione seriale con il sistema host (ossia tipicamente con il microcontrollore che fa capo alla scheda che li ospita): una modalità detta "trasparente" ed una modalità "API".

Utilizzando la prima modalità due moduli possono essere messi in comunicazione diretta in modo tale che il contenuto del buffer di ingresso di un modulo si rifletta automaticamente nel buffer di uscita dell'altro, e viceversa. Il canale di comunicazione così inizializzato consente quindi di "remotizzare" una qualsiasi comunicazione seriale (con livelli a 3.3V) semplicemente inserendo tra i due interlocutori due moduli wireless. Per far sì che un host possa interagire con il modulo locale sfruttando la medesima interfaccia seriale utilizzata per la trasmissione remota, viene sfruttata una particolare sequenza di caratteri (per default "+++") che, eseguita entro un tempo limite, mette il modulo in modalità "comando". In questo stato è ad esempio possibile eseguire comandi AT di servizio, quali ad esempio quelli che riguardano le funzionalità "mesh", come il discovery di altri nodi della stessa rete nei paraggi.

La modalità API consente invece di implementare una comunicazione  a pacchetti in cui ciascun pacchetto sia descritto da una ben precisa struttura a frame specifica del tipo di operazione che si vuole compiere. Sebbene tale modalità sia più complessa da gestire da parte di un'applicazione embedded, essa consente un margine di flessibilità di gran lunga superiore a quello disponibile in modalità trasparente. Inoltre, alcune funzionalità interessanti, come la remotizzazione degli I/O sono disponibili solamente in modalità API.

L'utilizzo dei moduli Xbee in modalità API all'interno di un firmware sviluppato con .NET Micro Framework è reso particolarmente semplice dalla libreria open-source "MFToolkit", disponibile all’indirizzo http://mftoolkit.codeplex.com

Vediamo un esempio di applicazione divisa in due parti: un'applicazione console  in esecuzione all'interno di un PC ed un firmware per la scheda di sviluppo TahoeII della DeviceSolutions già utilizzata in precedenza. Per poter eseguire il demo completo, avremo bisogno di due moduli Xbee configurati con la stessa versione di firmware (ZIGBEE API per moduli di tipo XB24-ZB) in modo tale da appartenere alla stessa sottorete (PAN, nel gergo ZigBee). La figura seguente mostra la configurazione circuitale usata per questi test.

La configurazione dei moduli Xbee avviene per tramite di un tool denominato X-CTU; in figura successiva è illustrata un'immagine di questo strumento relativa alla configurazione del dispositivo "coordinator". Nell'esempio in questione utilizzeremo come modulo Xbee "coordinator" quello collegato al PC , mentre configureremo quello installato sulla scheda di sviluppo TahoeII come "router/end-device". Per collegare il modulo Xbee ad un PC è necessario utilizzare un'apposita daughter-board che tipicamente incapsula un chip FTDI, Prolific o SiLabs per realizzare funzionalità USB-UART-Bridge. Vale la pena di sottolineare  infine che il sorgente della libreria per la gestione della comunicazione con i moduli Xbee in modalità API è condivisa tra i due progetti rispettivamente destinati al .NET Micro Framework e al .NET Framework "senior".

Il sorgente relativo al Main() dell'applicazione desktop è riportato nel frammento di codice seguente; nel metodo viene semplicemente avviato un thread di background che ha come unico scopo quello di eseguire il metodo "RunMasterModule":

1: class Program

2: {

3:    static void Main(string[] args)

4:    {

5:        // Lancio un thread che gestisca il modulo XBee

6:        Thread thd1 = new Thread(new ThreadStart(RunMasterModule));

7:        thd1.IsBackground = true;

8:        thd1.Start();

9:  

10:        // Con il thread principale resto in attesa...

11:        Console.ReadLine();

12:    }

13:  

14:     // ...

15: }

Il metodo eseguito dal thread si occupa dell'inizializzazione del modulo (collegato alla porta seriale COM23 del PC nel nostro esempio) e dell'esecuzione di un comando di tipo "Node Discovery", il cui scopo è quello di effettuare la ricerca, all'interno di uno spazio delimitato della portata del ricetrasmettitore, di altri nodi iscritti alla propria PAN (nel nostro caso il PAN ID è 234). Il sorgente del metodo in questione è riportato nel frammento di codice che segue:

1: static void RunMasterModule()

2: {

3:     using (XBee xbee = new XBee("COM23", 9600, ApiType.Enabled))

4:     {

5:         xbee.FrameReceived += new FrameReceivedEventHandler(xbee_FrameReceived);

6:         xbee.Open();

7:  

8:         // esecuzione del comando "Node Discovery"

9:         AtCommand at = new NodeDiscoverCommand();

10:         xbee.Execute(at);

11:  

12:         // lascio attivo il modulo Xbee solo 10 secondi

13:         Thread.Sleep(10 * 1000);

14:  

15:         // ...poi lo spengo

16:         xbee.StopReceiveData();

17:  

18:         Console.WriteLine("stopped master");

19:     }

20: }

Ogni comunicazione che il modulo Xbee effettua nei confronti dell'host viene gestita da un evento denominato "FrameReceived". All'interno del gestore di evento è possibile eseguire tutte le operazioni di decodifica necessarie, come ad esempio quelle relative all'individuazione del identificativo della richiesta cui la risposta si riferisce o al riconoscimento del tipo di risposta ricevuta. Nel nostro esempio gestiamo tre tipi di risposta: la prima è quella relativa alla scoperta di nodi all'interno della stessa PAN (per la quale il modulo ci invia più istanze, una per ciascun nodo), la seconda è quella relativa all'esito di una trasmissione (rappresentata dalla stringa "Hello") che il demo effettua nei confronti di un ben preciso nodo (identificato dal nome "SLAVE", secondo quanto previsto dal parametro di configurazione "NI" stabilito dal protocollo) e la terza riguarda la comunicazione effettuata da parte del firmware in esecuzione sulla scheda TahoeII, come verrà illustrato a breve. Il codice relativo al gestore di evento in questione è riportato nel frammento di codice seguente:

1: static void xbee_FrameReceived(object sender, FrameReceivedEventArgs e)

2: {

3:    Console.WriteLine("Ricevuta la seguente risposta: " + e.Response);

4:  

5:    // La risposta riguarda il comando di Node Discovery?

6:    if (e.Response is AtCommandResponse && 

7:         (e.Response as AtCommandResponse).Command == new NodeDiscoverCommand().Command)

8:    {

9:        // interpreto il pacchetto ricevuto (ne arriva uno per nodo)

10:        NodeDiscover nd = NodeDiscover.Parse((e.Response as AtCommandResponse));

11:  

12:        // Se il nodo rilevato si chiama "SLAVE"...

13:        if (nd != null && nd.ShortAddress != null)

14:        {

15:            Console.WriteLine(nd);

16:  

17:            if (nd.NodeIdentifier == "SLAVE")

18:            {

19:                Console.WriteLine("Mando la stringa \"Hello\" a SLAVE...");

20:                (sender as XBee).ExecuteNonQuery(

21:                     new ZNetTxRequest(nd.SerialNumber, nd.ShortAddress, Encoding.ASCII.GetBytes("Hello")));

22:            }

23:        }

24:    }

25:  

26:    // La risposta riguarda l'esito della trasmissione di "Hello"?

27:    if (e.Response is ZNetTxStatusResponse)

28:    {

29:        Console.WriteLine((e.Response as ZNetTxStatusResponse).DeliveryStatus.ToString());

30:    }

31:  

32:    // La risposta riguarda una trasmissione da parte di un altro nodo?

33:    if (e.Response is ZNetRxResponse)

34:    {

35:        Console.Write("Ho ricevuto la stringa: ");

36:        Console.WriteLine(Encoding.ASCII.GetString((e.Response as ZNetRxResponse).Value));

37:    }

38:  

39: }

L'interfaccia utente dell'applicazione in esecuzione sul PC è quella visibile nella figura seguente:

Il codice relativo alla parte embedded della soluzione (si veda il frammento di codice seguente) è ovviamente molto simile a quello appena visto, ad eccezione del fatto che il nodo "SLAVE" di fatto non effettua alcuna operazione di "discovery", limitandosi a rispondere con la stringa "Hello by MF" a ciascun nodo che invii un qualche tipo di comunicazione.

1: public class Program

2: {

3:     public static void Main()

4:     {

5:         using (XBee xbee = new XBee("COM2", 9600,ApiType.Enabled))

6:         {

7:             xbee.FrameReceived += new FrameReceivedEventHandler(xbee_OnPacketReceived);

8:             xbee.Open();

9:  

10:             // Attivo il modulo Xbee solo per 60 secondi

11:             Thread.Sleep(10 * 60 * 1000);

12:  

13:             xbee.StopReceiveData();

14:         }

15:     }

16:  

17:     static void xbee_OnPacketReceived(object sender, FrameReceivedEventArgs e)

18:     {

19:         XBeeResponse response = e.Response;

20:  

21:         // Debug risposta arrivata

22:         Debug.Print(response.ToString());

23:  

24:         // La risposta riguarda una trasmissione da parte di un altro nodo?

25:         if (e.Response is ZNetRxResponse)

26:         {

27:             Debug.Print("Received : " + 

28:                 new string(System.Text.Encoding.UTF8.GetChars((e.Response as ZNetRxResponse).Value)));

29:  

30:             // Rispondo al nodo chiamante con "Hello by MF"

31:             (sender as XBee).ExecuteNonQuery(

32:                     new ZNetTxRequest(

33:                         (e.Response as ZNetRxResponse).SerialNumber,

34:                         (e.Response as ZNetRxResponse).ShortAddress, 

35:                         Encoding.UTF8.GetBytes("Hello by MF")));

36:         }

37:     }

38: }

In questo caso, l'unico output dell'applicazione è visibile solo eseguendo la stessa in modalità di debug  all'interno dell'ambiente di sviluppo, come illustrato nella figura seguente.