Da Arduino a C# via socket

luca 31 gennaio 2013 6

Quando Arduino deve comunicare via rete, spesso si sceglie il protocollo HTTP perché consente di utilizzare come client un semplice browser web (IE, Firefox…). A volte però è necessario che Arduino invii dei dati ad altre applicazioni: in questo caso possiamo sfruttare una comunicazione socket.

Questo tutorial è suddiviso in due parti:

  • nella prima, vedremo come sviluppare una applicazione socket in C#
  • nella seconda, vedremo come scrivere uno sketch per Arduino

Socket server in C#

Il framework .Net mette consente di operare sui socket in due modalità: sincronaasincrona.

I metodi sincroni sono bloccanti: il thread nel quale sono eseguiti si ferma fino al termine della chiamata. Se ad esempio chiamiamo il metodo Receive() per leggere dati dal socket, tale metodo bloccherà l’esecuzione finché i dati saranno disponibili o finché scatterà un timeout.

I metodi asincroni invece chiedono al framework di gestire l’operazione, indicandogli quale oggetto callback chiamare quanto tale operazione è terminata. Normalmente tali metodi iniziano con Begin (ad esempio BeginReceive()). Programmare utilizzando metodi asicroni è però più complesso: per questo tutorial utilizzerò quindi i metodi sincroni, creando però un diverso thread per non bloccare la GUI del mio applicativo.

Il nostro SocketServer sarà quindi formato da due elementi:

  • la GUI (Form1.cs) che consentirà all’utente di interagire con il programma

  • la classe SocketHandler (Program.cs) che si occuperà di dialogare via socket con Arduino


Il programma riceverà da Arduino il valore della temperatura rilevata da una sonda e lo visualizzerà sia in forma testuale che in forma grafica (usando una progress bar); è inoltre presente una text area per il log di debug.

La classe Socket

Il framework .Net mette a disposizione l’oggetto Socket per gestire le connessioni in ingresso.

Per prima cosa, includiamo il package System.Net.Sockets, quindi – se l’utente preme il pulsante Start listening – configuriamo l’oggetto Socket perché sia in ascolto sulla porta TCP specificata:

IPAddress listeningIp = IPAddress.Any;
IPEndPoint listeningEndPoint = new IPEndPoint(listeningIp, listeningPort);
listeningSocket = new Socket(listeningIp.AddressFamily, SocketType.Stream, 
  ProtocolType.Tcp);
listeningSocket.Bind(listeningEndPoint);
listeningSocket.Listen(10);

Creiamo una nuova istanza dell’oggetto IPEndPoint specificando l’indirizzo IP su cui essere in ascolto (nel nostro esempio Any, ovvero ogni interfaccia di rete presente) e la porta. Quindi creiamo una istanza dell’oggetto Socket con protocollo TCP. Infine leghiamo (= bind) tale istanza all’endpoint e mettiamola in ascolto, in attesa di nuove connessioni (10 indica il numero massimo di connessioni in coda).

SocketHandler socketHandler = new SocketHandler(listeningSocket, this);
serverThread = new Thread(new ThreadStart(socketHandler.startListening));
serverThread.Start();

Per evitare di bloccare il resto del programma, creiamo un nuovo Thread in cui eseguiamo la classe SocketHandler che gestirà la connessione in ingresso:

try
 {
  // Wait for incoming connection
  handlerSocket = listeningSocket.Accept();
  handlerSocket.ReceiveTimeout = 5000;
  IPAddress clientAddress = ((IPEndPoint)handlerSocket.RemoteEndPoint).Address;

Il metodo Accept() è bloccante: il thread resterà in attesa di una nuova connessione… all’arrivo di questa viene impostato un timeout in lettura di 5 secondi e viene letto l’indirizzo IP del client che si è appena connesso.

while (true)
{
  // Save received bytes
  byte[] buffer = new byte[1024];
  int bytesRec = handlerSocket.Receive(buffer);
  message += Encoding.ASCII.GetString(buffer, 0, bytesRec);
 
  // Message completed? Parse it...
  if (message.Contains("\r"))
  {
    myForm.setTemp(message.Replace('.', ','));
    myForm.log(" message received: " + message);
    break;
  }
}

Leggiamo quindi i dati in ingresso con il metodo Receive (anche questo bloccante: se non arrivano dati entro 5 secondi viene sollevata una timeout exception) fino al carattere di “a capo” (\r), ricevuto il quale possiamo visualizzare la temperatura ricevuta.

Tips

Il sorgente completo del programma è disponibile su Github; prima di passare allo sketch Arduino ecco un paio di suggerimenti:

- utilizzando diversi thread, è necessario prestare attenzione a come si aggiornano gli elementi della GUI: tali elementi infatti possono essere aggiornati solo dal thread che li ha creati. Da thread diversi, dobbiamo quindi chiamare metodi delegati del thread principale:

myForm.setTemp(message.Replace('.', ','));
[...]
public delegate void setTempCallback(string temperature);
public void setTemp(string temperature)
{
  if (InvokeRequired) this.Invoke(new setTempCallback(setTemp), 
    new object[] { temperature });
  else
  {                
    float floatTemp;

- chiamare il metodo Abort() di un thread bloccato in Accept() non è sufficiente per chiudere tale thread, è necessario prima invocare il metodo Close() dell’oggetto Socket:

listeningSocket.Close();
serverThread.Abort();
log("Listening stopped");

Pagine: 1 2

6 Comments »

  1. pixel 14 marzo 2013 at 15:23 - Reply

    Ciao Luca,
    non riesco a trovare lo sketch puoi darmi maggiori indiaczioni? Puoi anche fornire l’eseguibile c#?

    Grazie

    • luca 14 marzo 2013 at 21:18 - Reply

      ciao, trovi tutto nel Repository su GitHub.

  2. igor 3 maggio 2013 at 15:28 - Reply

    Domanda, non ho capito a cosa serve:
    >> Stash::prepare(PSTR(“$H”), sd);
    ?

    • luca 3 maggio 2013 at 20:45 - Reply

      ciao, serve a preparare il pacchetto che poi invierai con tcpSend(). Il contenuto del pacchetto è soltanto “sd”, ovvero l’oggetto stash creato prima che contiene la temperatura e il terminatore di riga \r. $H è un “segnaposto” per la variabile che specifichi dopo (“sd”). Se guardi gli altri esempi vedrai pacchetti più complessi, con una parte fissa (“GET…”) e alcune variabili ($H, $F…).

Leave A Response »