Ethernet TCP connection with Arduino
From a software point of view, establishing a connection Ethernet with Arduino It is very simple. To do this, use the Ethernet library. This library is designed for a Ethernet Shield which is based on the integrated W5100, but there are other different boards or modules and/or that use other integrated ones, such as the ENC28J60. To simplify its use and increase compatibility, other libraries use (almost) the same API that Ethernet library, you will only have to replace the alternative library with the original one or include it (when the name is different) in its place even if the same (or very similar) functions are used in the code. In my case, I use the UIPEthernet library de Norbert Truchsess following the same process that I am going to describe in this text.
1. Define the Ethernet connection
Whether you are going to adopt the role of client like server, first of all you have to define the connection with the function begin () which can be passed as a parameter only the MAC address and wait for a server DHCP on the network assign a IP address and the rest of the configuration or it is also possible to indicate (optionally) more parameters until the complete configuration is defined:
- MAC address (which has already been mentioned)
- IP adress of the shield or module
- IP address of the server DNS (only one server)
- IP address of the Gateway
- Net mask
It is advisable to indicate all the parameters, unless their deduction is the usual one, to avoid that the configuration is not correct (for example, that the gateway is not the first address of the network).
From the above it seems that it is clear that data representing IP addresses must be used quite often, which is why the library includes the class IP Address from which to instantiate IP address objects. The parameters that define it are the four bytes of an address IPV4
La MAC address It is defined for this library as a 6-byte array. The MAC address is (supposed to be) a unique identifier in which the first bytes indicate the manufacturer and model and the last ones indicate the specific device. The integrated ENC28J60 does not include a MAC address unless you choose to also purchase a Integrated MAC address from Microchip (or a whole block YES of addresses to IEEE if the run of devices is large enough to make it worthwhile). When you do not have a MAC address, you can invent one, taking care that it does not conflict with others on the network where the device is located.
If the configuration is done with a DHCP server instead of "by hand", the function localIP() It is useful to consult the address that the server has assigned to the module. To renew the assigned address (if the corresponding time has expired) the Ethernet library provides the function maintain() which will also inform by returning a code that corresponds to the status of the renewal:
- The operation has had no effect
-
Error renewing IP address
The use of the assigned IP address on the same server could not be extended - IP address renewed successfully
-
IP address rebind failed
The use of the assigned IP address could not be extended on any server - IP address reassigned successfully
With the information seen so far, you can write an example of how an Ethernet connection would be initiated by configuring the IP address through a DHCP server on the network. The following example code tries to renew the IP address every certain period of time and reports the result.
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(“]”);
}
|
The example below assigns the IP address and the rest of the configuration manually using objects IP Address to make it more comfortable to read and (in the case of more complex code) to avoid errors that could occur if the address was (mis)written each time it was used.
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. Start the connection in client or server mode
When initiating a connection in server mode, it is the microcontrolled system that is being developed that listens to requests from other systems. To start the connection as a server, use EthernetServer() and the port on which the server will listen is indicated as a parameter. EthernetServer() is the constructor of the class Server & Hosting, which supports all Ethernet operations as a server. Although the most orthodox thing is to make a call to the constructor EthernetServer(), it is not uncommon to find some examples that directly use the class Server & Hosting or alternative libraries for Ethernet connection that choose to use that instantiation system.
The connection as a client is the one that makes requests to the server system, which waits for them and answers them accordingly. To initiate a connection as a client, use EthernetClient() what is the constructor of the class Client origin of all Ethernet operations as a client.
Unlike what happens with server mode, which is assumed to work from the moment the class is instantiated (although it will respond to clients only if it really is), you must verify that the client connection is ready before using it. The client object that is created when the connection is initiated can be queried to see if it is available. For example, query operations can be included in a structure if(EthernetClient) to execute them only when the client connection is available.
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. Establish a connection as a client
As has been said, once the connection is created, it is the client who takes the initiative to make the queries. The server will be waiting for that initiative and will respond accordingly. It is, therefore, the client that connects to the server, to do so we use connect() indicating the server as parameters (the IP address or the URL) And the port in the one who listens.
Based on the result of the operation, the function will return the values
- ( ) Connection established successfully
- Establishing the connection
- ( ) Timeout has passed without connection being established
- ( ) The server was not found or is not responding correctly
- ( ) The connection was dropped before being fully established
- ( ) Server response is incorrect
Before starting to make queries, it is necessary to verify that the connection is operational with the function connected() that will return if it is already available or otherwise.
The example below illustrates the connection as a client, checking every 10 seconds to see if there is a connection to the server (it is not intended to be anything productive, just to show the syntax of the functions) something that, by the way, a production web server would not like very much.
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. Send data
Like other better-known classes, such as Serial, and with comparable use, the classes Client y Server & Hosting have the functions
-
Sends information using the client or server object from which it is invoked. The "data" parameter is a single byte o tank while "buffer" is an array of byte o tank of which an amount equal to "length" is sent. This function is the one used for binary operations, compared to the next two that are usually reserved for sending text.
-
Sends as a client or server (depending on the class from which it is used) the information corresponding to "data" as text. If the information is not expressed as text (for example it is an integer) the optional parameter "base" can be used to choose the conversion, which could be one of the constants BIN, OCT, DEC or HEX that indicate, respectively. the bases corresponding to binary (base 2), octal (base 8), decimal (base 10) and hexadecimal (base 16)
-
The operation is identical to the previous one except for sending, after the information expressly indicated by the "data" parameter, a carriage return (code 13 which can be represented as \r) and an end of line (code 10, which can be represented by \n) These codes are frequently referred to, respectively, by the acronym CR (Carriage Return) and LF (Line Feed)
The three previous functions return the number of bytes that have been sent, as do the equivalent functions of the class Serial; As said above, the operation is comparable.
5. Receive data
As in the case of data sending operations, receiving operations are comparable to those of the widely used Serial. The receiving protocol is also similar: check if there is (enough) data available (available) and in that case read them
-
Returns the number of bytes that are available to be read. This function is present in both classes Client as Server & Hosting; In the first case, it reports the number of bytes that the server has sent in response to a request and that is available for the client to read (read), and in the second case the client (object) that has performed an operation or false if there is none.
-
It is used to read the information that has been received. This feature is only available in class Client. If the application being developed fulfills the role of server, to read the information that has arrived, a client object must be instantiated with the response of the function available () discussed in the previous section.
The following example is a "caps server" that listens on port 2000 and responds to requests with whatever was sent in all caps when possible. It can be tested, for example, with PuTTY or simply with It is certainly not very practical, its purpose is only to show how to obtain the data sent to it by a client from the server.
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. End the connection
While it is usual for a server application to run indefinitely, client connections are established, make connections and terminate, allowing resources to be recovered and used in other connections or dedicated to other uses of the program. The function stop() of the class Client It is used to terminate a client connection and free any resources it is using.
For the server, the fact that the client terminates the connection when the information object of the query has been sent or received also allows it to free up resources to allocate them to other connections or different purposes. In short, although it seems minor, it is advisable to terminate the connection when the client's operations end.
Another good practice when terminating a client connection is to empty the that the class uses. To do this, the function is available flush () should be called after terminating the client connection with stop()
HTTP GET query example
To better clarify all of the above, a more complete example of requests is included below. TCP using the GET requests using the HTTP protocol. In the example, the values obtained by analog sensors connected to an Arduino board are sent to a web server that stores them in a 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;
}
}
}
|
Post Comment