Conexión Ethernet TCP con Arduino

Conexión Ethernet TCP con Arduino

Conexión Ethernet TCP con Arduino

Desde el punto de vista del software, establecer una conexión Ethernet con Arduino es muy sencillo. Para hacerlo se utiliza la librería Ethernet. Esta librería está diseñada para una Ethernet Shield que está basada en el integrado W5100, pero existen otras placas o módulos diferentes y/o que utilizan otros integrados, como el ENC28J60. Para simplificar su uso y aumentar la compatibilidad, otras librerías utilizan (casi) la misma API que la librería Ethernet, sólo habrá que sustituir la librería alternativa por la original o incluirla (cuando el nombre sea diferente) en su lugar aunque en el código se usen las mismas (o muy parecidas) funciones. En mi caso, utilizo la librería UIPEthernet de Norbert Truchsess siguiendo el mismo proceso que voy a describir en este texto.

módulo ENC28J60 para usar con la librería UIPEthernet

1. Definir la conexión Ethernet

Tanto si se va a adoptar el papel de cliente como el de servidor, en primer lugar hay que definir la conexión con la función begin() a la que se le puede pasar como parámetro sólo la dirección MAC y esperar que un servidor DHCP en la red le asigne una dirección IP y el resto de la configuración o también es posible indicar (opcionalmente) más parámetros hasta definir la configuración completa:

  1. Dirección MAC (la que ya se ha citado)
  2. Dirección IP del shield o módulo
  3. Dirección IP del servidor DNS (sólo un servidor)
  4. Dirección IP de la puerta de enlace
  5. Máscara de red

Es recomendable indicar todos los parámetros, salvo que su deducción sea la habitual, para evitar que la configuración no sea correcto (por ejemplo, que la pasarela no sea la primera dirección de la red)

De lo anterior parece que queda claro que hay que usar bastantes veces datos que representen direcciones IP, por eso la librería incluye la clase IPAddress de la que instanciar objetos dirección IP. Los parámetros que la definen son los cuatro bytes de una dirección IPV4

La dirección MAC se define para esta librería como una matriz de 6 bytes. La dirección MAC es (se supone que es) un identificador único en el que los primeros bytes indican el fabricante y el modelo y los últimos al dispositivo en concreto. El integrado ENC28J60 no incluye una dirección MAC salvo que se opte por comprar además un integrado de dirección MAC de Microchip (o todo un bloque OUI de direcciones al IEEE si la tirada de dispositivos es lo bastante grande como para que merezca la pena). Cuando no se dispone de una dirección MAC se puede inventar una cuidando que no entre en conflicto con otras en la red en la que se encuentre el dispositivo.

Si la configuración se realiza con un servidor DHCP en lugar de «a mano», la función localIP() es útil para consultar la dirección que el servidor le ha asignado al módulo. Para renovar la dirección asignada (si el tiempo correspondiente hubiera expirado) la librería Ethernet proporciona la función maintain() que además informará devolviendo un código que corresponde con el estado de la renovación:

  1. La operación no ha tenido ningún efecto
  2. Error al renovar (renew) la dirección IP
    No se ha podido prolongar el uso de la dirección IP asignada en el mismo servidor
  3. Dirección IP renovada correctamente
  4. Error al reasignar (rebind) la dirección IP
    No se ha podido prolongar el uso de la dirección IP asignada en ningún servidor
  5. Dirección IP reasignada correctamente

Con la información vista hasta ahora ya se puede escribir un ejemplo de cómo se iniciaría una conexión Ethernet configurando la dirección IP por medio de un servidor DHCP en la red. En el siguiente código de ejemplo se trata de renovar la dirección IP cada cierto periodo de tiempo y se informa del resultado.

En el ejemplo de abajo se asigna la dirección IP y el resto de la configuración manualmente utilizando objetos IPAddress para que resulte más cómodo leerlo y (en caso de código más complejo) evitar los errores que se podrían producir si se escribiera (mal) la dirección en cada uso.

2. Iniciar la conexión en modo cliente o servidor

Al iniciar una conexión en modo servidor, es el sistema microcontrolado que se está desarrollando el que queda a la escucha de las peticiones de otros sistemas. Para iniciar la conexión como servidor se utiliza EthernetServer() y se indica como parámetro el puerto en el que el servidor escuchará. EthernetServer() es el constructor de la clase Server, que soporta todas las operaciones Ethernet como servidor. Aunque lo más ortodoxo es realizar una llamada al constructor EthernetServer(), no es raro encontrar algunos ejemplos que usan directamente la clase Server o librerías alternativas para conexión Ethernet que eligen usar ese sistema de instanciado.

La conexuión como cliente es la que realiza las peticiones al sistema servidor que es el que las espera y las contesta según corresponda. Para inicar una conexión como cliente se utiliza EthernetClient() que es el constructor de la clase Client origen de todas las operaciones Ethernet como cliente.

A diferencia de lo que ocurre con el modo servidor, que se supone funcionando desde que se instancia la clase (aunque responderá a los clientes sólo si lo está realmente), se debe verificar que la conexión cliente está preparada antes de utilizarla. El objeto cliente que se crea al iniciar la conexión puede consultarse para verificar si está disponible. Por ejemplo, las operaciones de consulta pueden incluirse en una estructura if(EthernetClient) para ejecutarlas sólo cuando la conexión cliente esté disponible.

3. Establecer una conexión como cliente

Como se ha dicho, una vez creada la conexión, es el cliente el que toma la iniciativa de realizar las consultas. El servidor estará esperando esa iniciativa y responderá como proceda. Es, por tanto, el cliente el que conecta al servidor, para hacerlo se utiliza connect() indicando como parámetros el servidor (la dirección IP o la URL) y el puerto en el que escucha.

Según el resultado de la operación, la función devolverá los valores

  1. (SUCCESS) Conexión establecida correctamente
  2. Estableciendo la conexión
  3. (TIMED_OUT) Ha pasado el tiempo de espera sin que se establezca la conexión
  4. (INVALID_SERVER) No se ha encontrado el servidor o no responde correctamente
  5. (TRUNCATED) La conexión se ha cortado antes de establecerse completamente
  6. (INVALID_RESPONSE) La respuesta del servidor es incorrecta

Antes de empezar a realizar consultas es necesario verificar que la conexión está operativa con la función connected() que devolverá true si ya está disponible o false en caso contrario.

El ejemplo de abajo ilustra la conexión como cliente verificando cada 10 segundos si existe conexión con el servidor (no pretende ser nada productivo, sólo mostrar la sintaxis de las funciones) algo que, por cierto, no gustaría mucho a un servidor web en producción.

4. Enviar datos

Igual que otras clases más conocidas, como ocurre con Serial, y con un uso equiparable, las clases Client y Server disponen de las funciones

  • write(dato) o write(buffer,longitud)

    Envía información usando el objeto cliente o servidor desde el que se invoque. El parámetro «dato» es un único byte o char mientras que «buffer» es una matriz de byte o char de la que se envía una cantidad igual a «longitud» Esta función es la que se utiliza para las operaciones binarias, frente a las dos siguientes que suelen reservarse para enviar texto.

  • print(dato,base)

    Envía como cliente o servidor (según la clase desde la que se use) la información correspondiente a «dato» como texto. Si la información no está expresada como texto (por ejemplo es un número entero) puede utilizarse el parámetro opcional «base» con el que elegir la de la conversión que podrá ser una de las constantes BIN, OCT, DEC o HEX que indican, respectivamente las bases correspondientes a binario (base 2), octal (base 8), decimal (base 10) y hexadecimal (base 16)

  • println(dato,base)

    El funcionamiento es idéntico a la anterior excepto por enviar, después de la información indicada expresamente por el parámetro «dato», un retorno de carro (el código 13 que se puede representar como \r) y un final de línea (el código 10, que se puede representar por \n) Frecuentemente se hace referencia a estos códigos, respectivamente, por las siglas CR (Carriage Return) y LF (Line Feed)

Las tres funciones anteriores devuelven el número de bytes que se han enviado, como ocurre también con las funciones equivalentes de la clase Serial; como se dijo arriba, el funcionamiento es equiparable.

5. Recibir datos

Igual que en el caso de las operaciones de envío de datos, las de recepción son equiparables a las de la ampliamente usada Serial. El protocolo de recepción también es similar: verificar si hay (suficientes) datos disponibles (available) y en tal caso leerlos


  • available()

    Devuelve el número de bytes que hay disponibles para ser leídos. Esta función está presente tanto en la clases Client como Server; en el primer caso informa del número de bytes que ha enviado el servidor en respuesta a una petición y que está disponible para que el cliente la lea (read), y en el segundo caso el (objeto) cliente que ha realizado una operación o false en caso de que no haya ninguno.

  • read()

    Sirve para leer la información que se ha recibido. Esta función sólo está disponible en la clase Client. Si la aplicación que se está desarollando cumple con el papel de servidor, para leer la información que ha llegado debe instanciarse un objeto cliente con la respuesta de la función available() comentada en el anterior apartado.

El siguiente ejemplo es un «servidor de mayúsculas» que escucha en el puerto 2000 y responde a las peticiones con lo que se haya enviado pasado a mayúsculas cuando sea posible. Puede probarse, por ejemplo con PuTTY o simplemente con telnet 2000 Ciertamente no es algo muy práctico, su finalidad sólo es mostrar cómo obtener en el servidor los datos enviados al mismo por un cliente.

6. Finalizar la conexión

Mientras que lo habitual es que una aplicación servidor funcione indefinidamente, las conexiones cliente se establece, realizan conexiones y terminan, lo que permite recuperar recursos y emplearlos en otras conexiones o dedicarlos a otros usos del programa. La función stop() de la clase Client se utiliza para terminar una conexión cliente y liberar los recursos que esté utilizando.

De cara al servidor, que el cliente termine la conexión cuando se ha enviado o recibido la información objeto de la consulta también le permite liberar recursos para destinarlos a otras conexiones o distintos fines. En definitiva, aunque parece algo menor, es conveniente terminar la conexión al terminar las operaciones del cliente.

Otra buena práctica al terminar una conexión cliente es vaciar el que utiliza la clase. Para hacerlo se dispone de la función flush() a la debería llamarse después de terminar la conexión cliente con stop()

Ejemplo de consulta HTTP GET

Para aclarar mejor todo lo anterior a continuación se incluye un ejemplo más completo de peticiones TCP usando el peticiones GET usando el protocolo HTTP. En el ejemplo se envían los los valores obtenidos por unos sensores analógicos conectados a una placa Arduino a un servidor web que los almacena en una base de datos.

Puede que te hayas perdido