Connessione Ethernet TCP con Arduino
Da un punto di vista software, stabilire una connessione Ethernet con Arduino È molto semplice. Per fare ciò, utilizzare il Libreria Ethernet. Questa libreria è progettata per a Schermo Ethernet che si basa sull'integrato W5100, ma esistono altre schede o moduli diversi e/o che utilizzano altri integrati, come ad esempio la ENC28J60. Per semplificarne l'utilizzo e aumentarne la compatibilità, altre librerie utilizzano (quasi) lo stesso API che Libreria Ethernet, dovrai solo sostituire la libreria alternativa con quella originale o includerla (quando il nome è diverso) al suo posto anche se nel codice vengono utilizzate le stesse (o molto simili) funzioni. Nel mio caso, utilizzo il file Libreria UIPEthernet de Norbert Truchsess seguendo lo stesso procedimento che andrò a descrivere in questo testo.
1. Definire la connessione Ethernet
Se intendi assumere il ruolo di cliente come server, prima di tutto bisogna definire il collegamento con la funzione begin () che può essere passato come parametro solo il Indirizzo MAC e attendere un server DHCP sulla rete assegnare a indirizzo IP e il resto della configurazione oppure è anche possibile indicare (opzionalmente) più parametri fino a definire la configurazione completa:
- Indirizzo MAC (di cui si è già parlato)
- indirizzo IP dello scudo o del modulo
- Indirizzo IP del server DNS (un solo server)
- Indirizzo IP del porta
- Maschera a rete
Si consiglia di indicare tutti i parametri, a meno che la loro detrazione non sia quella abituale, per evitare che la configurazione non sia corretta (ad esempio, che il gateway non sia il primo indirizzo della rete).
Da quanto sopra sembra chiaro che i dati che rappresentano gli indirizzi IP devono essere utilizzati abbastanza spesso, motivo per cui la libreria include la classe Indirizzo IP da cui istanziare gli oggetti indirizzo IP. I parametri che lo definiscono sono i quattro byte di un indirizzo IPV4
La Indirizzo MAC Per questa libreria è definito come un array da 6 byte. L'indirizzo MAC è (dovrebbe essere) un identificatore univoco in cui i primi byte indicano il produttore e il modello e gli ultimi indicano il dispositivo specifico. L'integrato ENC28J60 non include un indirizzo MAC a meno che tu non scelga di acquistare anche a Indirizzo MAC integrato di Microchip (o un intero isolato sì di indirizzi a IEEE se il numero di dispositivi è abbastanza grande da renderlo utile). Quando non disponi di un indirizzo MAC, puoi inventarne uno, facendo attenzione che non entri in conflitto con altri presenti sulla rete in cui si trova il dispositivo.
Se la configurazione viene eseguita con un server DHCP anziché "a mano", la funzione IPlocale() È utile consultare l'indirizzo che il server ha assegnato al modulo. Per rinnovare l'indirizzo assegnato (se il tempo corrispondente è scaduto) il Libreria Ethernet fornisce la funzione mantenere() che informerà anche restituendo un codice che corrisponde allo stato del rinnovo:
- L'operazione non ha avuto alcun effetto
-
Errore durante il rinnovo dell'indirizzo IP
Non è stato possibile estendere l'utilizzo dell'indirizzo IP assegnato sullo stesso server - Indirizzo IP rinnovato con successo
-
Riassociazione dell'indirizzo IP non riuscita
L'utilizzo dell'indirizzo IP assegnato non potrà essere esteso su nessun server - Indirizzo IP riassegnato correttamente
Con le informazioni viste finora è possibile scrivere un esempio di come verrebbe avviata una connessione Ethernet configurando l'indirizzo IP tramite un server DHCP sulla rete. Il seguente codice di esempio tenta di rinnovare l'indirizzo IP ogni determinato periodo di tempo e segnala il risultato.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
//#include <UIPEthernet.h> // Librería Ethernet que usaré después con el módulo ENC28J60
#include <Ethernet.h> // Librería Ethernet estándar
#define ESPERA_RENOVACION_IP 60000 // Un minuto
unsigned long reloj;
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
byte estado_DHCP;
void setup()
{
Serial.begin(9600);
Ethernet.begin(direccion_mac);
mostrar_direccion_ip();
reloj=millis()+ESPERA_RENOVACION_IP;
}
void loop()
{
if(millis()>reloj) // Tratar de renovar la IP cada ESPERA_RENOVACION_IP milisegundos
{
estado_DHCP=Ethernet.maintain();
switch(estado_DHCP)
{
case 0:
Serial.println(“Sin cambios”);
break;
case 1:
Serial.println(“Error al renovar la dirección IP”);
break;
case 2:
Serial.println(“Dirección IP renovada correctamente”);
break;
case 3:
Serial.println(“Error al reasignar la dirección IP”);
break;
case 4:
Serial.println(“Dirección IP reasignada correctamente”);
break;
default:
Serial.println(“Error desconocido”);
}
mostrar_direccion_ip();
reloj=millis()+ESPERA_RENOVACION_IP;
}
}
void mostrar_direccion_ip()
{
Serial.print(“Dirección IP actual [“);
Serial.print(Ethernet.localIP()); // Mostrará la dirección IP asignada por el servidor DHCP
Serial.println(“]”);
}
|
L'esempio seguente assegna manualmente l'indirizzo IP e il resto della configurazione utilizzando gli oggetti Indirizzo IP per renderlo più comodo da leggere e (nel caso di codice più complesso) per evitare errori che potrebbero verificarsi se l'indirizzo venisse scritto (erroneamente) ad ogni utilizzo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
// #include <Ethernet.h> // Librería Ethernet estándar
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor DNS OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
void setup()
{
Serial.begin(9600);
while(!Serial){;} // He usado una placa Leonardo, me toca esperar a que el puerto serie esté operativo
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
Serial.print(“Dirección IP asignada [“);
Serial.print(Ethernet.localIP()); // Poco misterio, devolverá la dirección IP asignada manualmente
Serial.println(“]”);
}
void loop()
{
// Sólo es un ejemplo de configuración, no hace nada
}
|
2. Avviare la connessione in modalità client o server
Quando si avvia una connessione in modalità server, è il sistema microcontrollato in fase di sviluppo che ascolta le richieste di altri sistemi. Per avviare la connessione come server, utilizzare EthernetServer() e come parametro è indicata la porta su cui il server resterà in ascolto. EthernetServer() è il costruttore della classe server, che supporta tutte le operazioni Ethernet come server. Sebbene la cosa più ortodossa sia effettuare una chiamata al costruttore EthernetServer(), non è raro trovare alcuni esempi che utilizzano direttamente la classe server o librerie alternative per la connessione Ethernet che scelgono di utilizzare quel sistema di istanziazione.
La connessione come client è quella che fa richieste al sistema server, che le attende e risponde di conseguenza. Per avviare una connessione come client, utilizzare ClientEthernet() qual è il costruttore della classe . origine di tutte le operazioni Ethernet come client.
A differenza di quanto accade con la modalità server, che si presume funzioni dal momento in cui viene istanziata la classe (anche se risponderà ai client solo se lo è realmente), è necessario verificare che la connessione client sia pronta prima di utilizzarla. È possibile eseguire una query sull'oggetto client creato quando viene avviata la connessione per verificare se è disponibile. Ad esempio, le operazioni di query possono essere incluse in una struttura se(Cliente Ethernet) per eseguirli solo quando la connessione client è disponibile.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
// #include <Ethernet.h> // Librería Ethernet estándar
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
EthernetServer servidor=EthernetServer(80); // Puerto 80 (típico de un servidor HTTP)
void setup()
{
Serial.begin(9600);
while(!Serial){;} // He usado una placa Leonardo, hay que esperar a que el puerto serie esté operativo
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
servidor.begin();
Serial.println(“Servidor HTTP iniciado”);
}
void loop()
{
// Sólo es un ejemplo de configuración, no hace nada productivo
}
|
3. Stabilire una connessione come client
Come è stato detto, una volta creata la connessione, è il cliente che prende l'iniziativa di effettuare le query. Il server attenderà tale iniziativa e risponderà di conseguenza. È quindi il client che si connette al server, per farlo utilizziamo Collegare() indicando come parametri il server (l'indirizzo IP o il URL) E l' porto in chi ascolta.
In base al risultato dell'operazione, la funzione restituirà i valori
- ( ) Connessione stabilita con successo
- Stabilire la connessione
- ( ) È trascorso il timeout senza che sia stata stabilita la connessione
- ( ) Il server non è stato trovato o non risponde correttamente
- ( ) La connessione è stata interrotta prima di essere stabilita completamente
- ( ) La risposta del server non è corretta
Prima di iniziare ad effettuare interrogazioni è necessario verificare che la connessione con la funzione sia operativa collegato() che ritornerà se è già disponibile o altrimenti.
L'esempio seguente illustra la connessione come client, controllando ogni 10 secondi per vedere se c'è una connessione al server (non vuole essere qualcosa di produttivo, solo per mostrare la sintassi delle funzioni) qualcosa che, tra l'altro, un server web di produzione non piacerebbe molto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
// #include <Ethernet.h> // Librería Ethernet estándar
#define INTERVALO_CONSULTA 10000 // Se comprueba cada 10 segundos si hay conexión
#define LED_CONEXION 13 // Pin del LED que parpadea cuando hay conexión
#define TIEMPO_PARPADEO 300 // Milisegundos entre encendido/apagado del LED que indica conexión
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
IPAddress ip_servidor_web(192,168,1,21); // Dirección IP del servidor web (en la intranet)
//char url_servidor_web[]=”sleepmanager.onironauta.es”; // URL poético para un gestor de sueño (en Internet)
EthernetClient cliente=EthernetClient();
byte estado_conexion;
boolean anteriormente_conectado;
boolean estado_led_conexion;
unsigned long cronometro_parpadeo;
unsigned long cronometro_consulta;
void conectar_ethernet()
{
estado_conexion=cliente.connect(ip_servidor_web,80); // Conexión desde la intranet
//estado_conexion=cliente.connect(url_servidor_web,80); // Conexión desde Internet
delay(100); // Un pequeño retraso para permitir que se active la conexión
anteriormente_conectado=false;
switch(estado_conexion)
{
case 1:
Serial.println(“Conexión con el servidor SleepManager establecida correctamente”);
anteriormente_conectado=true;
break;
case –1:
Serial.println(“Ha pasado el tiempo de espera sin que se establezca la conexión”);
break;
case –2:
Serial.println(“No se ha encontrado el servidor o no responde correctamente”);
break;
case –3:
Serial.println(“La conexión se ha interrumpido antes de establecerse completamente”);
break;
case –4:
Serial.println(“La respuesta del servidor es incorrecta”);
break;
}
}
void setup()
{
pinMode(LED_CONEXION,OUTPUT);
Serial.begin(9600);
while(!Serial){;} // Esperar al puerto serie de la placa Leonardo
Serial.println(“Conectando con el servidor SleepManager…”);
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
conectar_ethernet();
estado_led_conexion=false;
cronometro_parpadeo=0;
cronometro_consulta=millis()+INTERVALO_CONSULTA;
}
void loop()
{
if(anteriormente_conectado&&millis()>cronometro_parpadeo)
{
estado_led_conexion=!estado_led_conexion;
digitalWrite(LED_CONEXION,estado_led_conexion);
cronometro_parpadeo=millis()+TIEMPO_PARPADEO;
}
if(millis()>cronometro_consulta)
{
if(!cliente.connected())
{
conectar_ethernet();
}
cronometro_consulta=millis()+INTERVALO_CONSULTA;
}
}
|
4. Invia dati
Come altre classi più conosciute, come Serialee, con un uso comparabile, le classi . y server hanno le funzioni
-
Invia informazioni utilizzando l'oggetto client o server da cui viene richiamato. Il parametro "data" è unico byte o serbatoio mentre "buffer" è un array di byte o serbatoio di cui viene inviato un importo pari a "lunghezza".Questa funzione è quella utilizzata per le operazioni binarie, rispetto alle due successive che solitamente sono riservate all'invio di testo.
-
Invia come client o server (a seconda della classe da cui viene utilizzato) le informazioni corrispondenti ai "dati" come testo. Se l'informazione non è espressa come testo (ad esempio è un intero) si può utilizzare il parametro opzionale "base" per scegliere la conversione, che potrebbe essere una delle costanti BIN, OCT, DEC o HEX che indicano rispettivamente il valore basi corrispondenti a binario (base 2), ottale (base 8), decimale (base 10) ed esadecimale (base 16)
-
Il funzionamento è identico alla precedente tranne che per l'invio, dopo l'informazione espressamente indicata dal parametro "data", un ritorno a capo (codice 13 che può essere rappresentato come \r) e un fine riga (codice 10, che può essere rappresentato da \n) Questi codici sono spesso indicati rispettivamente con l'acronimo CR (Ritorno a capo) e LF (avanzamento riga)
Le tre funzioni precedenti restituiscono il numero di byte che sono stati inviati, così come le funzioni equivalenti della classe Seriale; Come detto sopra, l’operazione è comparabile.
5. Ricevi dati
Come nel caso delle operazioni di invio dei dati, le operazioni di ricezione sono paragonabili a quelle ampiamente utilizzate Seriale. Anche il protocollo di ricezione è simile: controlla se ci sono (abbastanza) dati disponibili (disponibile) e in tal caso leggeteli
-
Restituisce il numero di byte disponibili per la lettura. Questa funzione è presente in entrambe le classi . come server; Nel primo caso riporta il numero di byte che il server ha inviato in risposta ad una richiesta e che è disponibile per la lettura da parte del client (read), e nel secondo caso il client (oggetto) che ha eseguito un'operazione oppure false se non ce n'è.
-
Viene utilizzato per leggere le informazioni ricevute. Questa funzionalità è disponibile solo in classe .. Se l'applicazione in sviluppo svolge il ruolo di server, per leggere le informazioni arrivate è necessario istanziare un oggetto client con la risposta della funzione a disposizione () discusso nella sezione precedente.
L'esempio seguente è un "server caps" che ascolta sulla porta 2000 e risponde alle richieste con tutto ciò che è stato inviato in maiuscolo quando possibile. Può essere testato, ad esempio, con PuTTY o semplicemente con Sicuramente non è molto pratico, il suo scopo è solo quello di mostrare come ottenere dal server i dati che gli vengono inviati da un client.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
//#include <Ethernet.h> // Librería Ethernet estándar
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
EthernetServer servidor=EthernetServer(2000);
EthernetClient cliente;
char texto_recibido; // Sólo para que se más fácil leer el programa usando varias líneas de código
void setup()
{
Serial.begin(9600);
while(!Serial){;} // Esperar al puerto serie de la placa Leonardo esté operativo
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
Serial.println(“Iniciando el servidor de mayúsculas”);
servidor.begin();
}
void loop()
{
cliente=servidor.available();
// Si hay disponible alguna petición de un cliente leerla y devolverla en mayúsculas
if(cliente)
{
texto_recibido=cliente.read();
if(texto_recibido>96&&texto_recibido<123) // Si se recibe una letra minúscula…
{
texto_recibido-=32; // …convertirla a mayúsculas
}
// Si se tiene la seguridad de recibir texto se puede usar print en lugar de write
servidor.write(texto_recibido); // Responder con lo recibido pasado a mayúsculas si procede
}
}
|
6. Terminare la connessione
Sebbene sia normale che un'applicazione server venga eseguita a tempo indeterminato, le connessioni client vengono stabilite, effettuate e terminate, consentendo alle risorse di essere recuperate e utilizzate in altre connessioni o dedicate ad altri usi del programma. La funzione fermare() della classe . Viene utilizzato per terminare una connessione client e liberare tutte le risorse che sta utilizzando.
Per il server, il fatto che il client termini la connessione quando l'informazione oggetto della query è stata inviata o ricevuta, consente anche di liberare risorse per destinarle ad altre connessioni o scopi diversi. Insomma, anche se sembra una cosa di poco conto, è consigliabile terminare la connessione quando terminano le operazioni del client.
Un'altra buona pratica quando si termina una connessione client è svuotare il file che la classe utilizza. Per fare ciò, la funzione è disponibile flush () dovrebbe essere chiamato dopo aver terminato la connessione client con fermare()
Esempio di query HTTP GET
Per meglio chiarire tutto quanto sopra, si riporta di seguito un esempio più completo di richieste. TCP utilizzando le richieste GET utilizzando il Protocollo HTTP. Nell'esempio i valori ottenuti dai sensori analogici collegati ad una scheda Arduino vengono inviati ad un server web che li memorizza in un database.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
// #include <Ethernet.h> // Librería Ethernet estándar
#define INTERVALO_CONSULTA 60000 // Enviar datos cada minuto
#define INTERVALO_RECONEXION 10000 // Reintentar la conexión 10 segundos más tarde si no ha sido posible hacerlo cuado correspondía
#define CANTIDAD_SENSORES 6 // Número de sensores analógicos empezando en A0
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
//IPAddress ip_servidor_web(192,168,1,21); // Dirección IP del servidor web (en la intranet)
char url_servidor_web[]=“sleepmanager.onironauta.es”; // URL poético para un gestor de sueño (en Internet)
EthernetClient cliente;
byte estado_conexion;
String texto_consulta;
unsigned long cronometro_consulta;
byte contador;
void setup()
{
Serial.begin(9600);
while(!Serial){;} // Esperar al puerto serie de la placa Leonardo
Serial.println(“Conectando con el servidor SleepManager…”);
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
cronometro_consulta=millis()+INTERVALO_CONSULTA;
}
void loop()
{
if(millis()>cronometro_consulta)
{
//estado_conexion=cliente.connect(ip_servidor_web,80); // Conexión desde la intranet
estado_conexion=cliente.connect(url_servidor_web,80); // Conexión desde Internet
while(estado_conexion==0) // esperar a que se establezca la conexión o se produzca un error
{
switch(estado_conexion)
{
case 1:
Serial.println(“Conexión con el servidor SleepManager establecida correctamente”);
break;
case –1:
Serial.println(“Ha pasado el tiempo de espera sin que se establezca la conexión”);
break;
case –2:
Serial.println(“No se ha encontrado el servidor o no responde correctamente”);
break;
case –3:
Serial.println(“La conexión se ha interrumpido antes de establecerse completamente”);
break;
case –4:
Serial.println(“La respuesta del servidor es incorrecta”);
break;
}
}
if(cliente.connected()) // Si ha sido posible conectar realizar la consulta
{
cronometro_consulta=millis()+INTERVALO_CONSULTA;
texto_consulta=“GET /pruebas/guardar_sensores_analogicos.php?origen=SleepManager”;
for(contador=0;contador<CANTIDAD_SENSORES;contador++)
{
texto_consulta=“&sensor_”+String(contador+1,DEC)+“=”+String(analogRead(contador),DEC);
delay(1); // Como tarda 100 μs en obtener el valor analógico, con 1 ms seguro le da tiempo
}
texto_consulta+=” HTTP/1.1\r\nHost: “+String(url_servidor_web)+“\r\nUser-Agent: sleep_inspector\r\n\r\n”;
cliente.print(texto_consulta);
cliente.flush();
cliente.stop();
}
else // Si no ha sido posible conectar reintentarlo más tarde pero no tanto como si hubiera sido posible hacerlo
{
cronometro_consulta=millis()+INTERVALO_RECONEXION;
}
}
}
|
Invia commento