5. Tarea 2 - Control de Arduinos con una tableta Android
Ahora aprenderemos a controlar una placa Arduino con una tableta. El ejemplo a seguir es el proyecto [client-android-skel] del curso (ver párrafo 2).
5.1. Arquitectura de proyectos
Todo el proyecto tendrá la siguiente arquitectura:
![]() |
- El bloque [1], el servidor web/JSON y los Arduinos, te serán proporcionados;
- usted tendrá que construir el bloque [2], el programa de la tableta Android para comunicarse con el servidor web / JSON.
5.2. Hardware
Tiene a su disposición los siguientes componentes:
- un Arduino con un escudo Ethernet, un LED, y un sensor de temperatura;
- un miniHub para compartir con otro alumno;
- un cable USB para alimentar el Arduino;
- dos cables de red para conectar el Arduino y el PC a la misma red privada;
- una tableta Android;
5.2.1. El Arduino
![]() |
A continuación te explicamos cómo conectar los distintos componentes:
- desconecte el cable de red de su PC;
- Conecta tu PC y el Arduino mediante un cable de red;
- El Arduino que tienes ya estará programado. Su dirección IP será [192.168.2.2]. Para que tu PC reconozca el Arduino, deberás asignarle una dirección IP en la red [192.168.2]. Los Arduinos han sido programados para comunicarse con un PC con la dirección IP [192.168.2.1]. He aquí cómo hacerlo:
Vaya a [Panel de control\NRed e Internet\NCentro de redes y recursos compartidos]:
![]() | ![]() |
- En [1], haga clic en el enlace [Red de área local];
- en [2], haga clic en el botón [Propiedades] de la red local;
![]() | ![]() |
- en [3], haga clic en las propiedades [IPv4] del adaptador [Conexión de área local];
- en [4], asigna a este adaptador la dirección IP [192.168.2.1] y la máscara de subred [255.255.255.0];
- en [5], haga clic en [OK] tantas veces como sea necesario para salir del asistente.
5.2.2. La tableta
- Utilizando su adaptador Wi-Fi, conecte su ordenador a la red Wi-Fi que le proporcionaremos. Haga lo mismo con su tableta;
- Comprueba la dirección Wi-Fi PC de tu IP escribiendo [ipconfig] en una ventana de símbolo del sistema. Encontrará una dirección como [192.168.x.y];
dos>ipconfig
Windows IP Configuration
Wi-Fi wireless network adapter:
Connection-specific DNS suffix . . . :
Local-link IPv6 address. . . . .: fe80::39aa:47f6:7537:f8e1%2
IPv4 address. . . . . . . . . . . . . .: 192.168.1.25
Subnet mask. . . . . . . . . : 255.255.255.0
Default gateway. . . . . . . . . : 192.168.1.1
- Comprueba la dirección IP Wi-Fi de tu tableta. Pregunta a tu instructor cómo hacerlo si no estás seguro. Encontrarás una dirección como [192.168.x.z];
- Desactive el cortafuegos de su PC si está activo [Panel de control\Sistema y seguridad\Windows Firewall];
- En una ventana de símbolo del sistema, compruebe que el PC y la tableta pueden comunicarse escribiendo el comando [ping 192.168.x.z], donde [192.168.x.z] es la dirección IP de la tableta. La tableta debería responder:
dos>ping 192.168.1.26
Sending a 'Ping' request to 192.168.1.26 with 32 bytes of data:
Response from 192.168.1.26: bytes=32 time=102 ms TTL=64
Response from 192.168.1.26: bytes=32 time=134 ms TTL=64
Response from 192.168.1.26: bytes=32 time=168 ms TTL=64
Response from 192.168.1.26: bytes=32 time=208 ms TTL=64
Ping statistics for 192.168.1.26:
Packets: sent = 4, received = 4, lost = 0 (0% loss),
Approximate round-trip times in milliseconds:
Minimum = 102 ms, Maximum = 208 ms, Average = 153 ms
La configuración de red de su sistema ya está lista.
5.2.3. El emulador [Genymotion]
El emulador [Genymotion] (véase la sección 6.9) es una gran alternativa a una tableta. Es casi igual de rápida y no requiere conexión Wi-Fi. Recomendamos utilizar este método. Puedes utilizar la tableta para las pruebas finales de tu aplicación.
5.3. Programación de Arduinos
Aquí nos centraremos en escribir código C para Arduinos:
![]() |
Ver también
- Instalación del IDE de desarrollo de Arduino (véase la sección 6.1);
- Utilización de las bibliotecas JSON (Apéndices, Sección 6.6);
- En el Arduino IDE, prueba el ejemplo de un servidor TCP (e.g., el servidor web) y el de un cliente TCP (e.g., el cliente Telnet);
- los apéndices sobre el entorno de programación Arduino en la sección 6.1.
![]() |
Un Arduino es un conjunto de pines conectados a un hardware. Estos pines son entradas o salidas. Sus valores son binarios o analógicos. Para controlar el Arduino, hay dos operaciones básicas:
- escribir un binario/analógico valor a un pin identificado por su número;
- leer a binario/analógico valor de una clavija identificada por su número;
A estas dos operaciones básicas, añadiremos una tercera:
- hacer parpadear un LED durante un tiempo y con una frecuencia determinados. Esta operación puede realizarse llamando repetidamente a las dos operaciones básicas anteriores. Sin embargo, veremos en las pruebas que los intercambios entre la capa [DAO] y un Arduino duran del orden de un segundo. Por lo tanto, no es posible hacer que un LED parpadee cada 100 milisegundos, por ejemplo. Así que implementaremos esta función de parpadeo en el propio Arduino.
El Arduino funcionará de la siguiente manera:
- La comunicación entre la capa [DAO] y un Arduino tiene lugar a través de una red TCP-IP mediante el intercambio de líneas de texto en formato JSON (JavaScript Object Notation);
- al arrancar, el Arduino se conecta al puerto 100 de un servidor de registro situado en la capa [DAO]. Envía una única línea de texto al servidor:
Esta es una cadena JSON que describe el Arduino que se está conectando:
- id: un identificador para el Arduino;
- desc: una descripción de lo que puede hacer el Arduino. Aquí, simplemente hemos especificado el modelo de Arduino;
- mac: la dirección MAC del Arduino;
- puerto: el número de puerto en el que el Arduino esperará órdenes de la capa [DAO].
Toda esta información está en formato de cadena excepto el puerto, que es un número entero.
- Una vez que el Arduino se ha registrado con el servidor de registro, comienza a escuchar en el puerto que especificó al servidor (102 arriba). Espera comandos JSON en el siguiente formato:
Se trata de una cadena JSON con los siguientes elementos:
- id: un identificador para el comando. Puede ser cualquier cosa;
- ac: una acción. Hay tres:
- pw (escribir pin) para escribir un valor en un pin,
- pr (pin leído) para leer el valor de un pin,
- cl (parpadear) para hacer parpadear un LED;
- pa: los parámetros de la acción. Estos dependen de la acción.
- El Arduino siempre devuelve una respuesta a su cliente. Se trata de una cadena JSON con el siguiente formato:
donde
- id: el identificador del comando al que se responde;
- er (error): un código de error si se ha producido un error, 0 en caso contrario;
- et (estado): un diccionario que siempre está vacío excepto para el comando de lectura pr. El diccionario contiene entonces el valor del número de pin x que se solicitó.
He aquí algunos ejemplos para aclarar las especificaciones anteriores:
Haga que LED #8 parpadee 10 veces con un periodo de 100 milisegundos:
Comando | |
Respuesta |
Los parámetros del cl son: la duración (dur) de un destello en milisegundos, el número (nb) de destellos, y el pin número del LED.
Escriba el valor binario 1 en el pin 7:
Comando | |
Respuesta |
En pa parámetros del pw son: el modo de escritura mod (b para binario o a para analógico), el valor val a escribir, y el pin número. Para una escritura binaria, val es 0 ó 1. Para una escritura analógica, val está en el rango [0,255].
Escribe el valor analógico 120 en el pin 2:
Comando | |
Respuesta |
Lee el valor analógico del pin 0:
Comando | |
Respuesta |
En pa parámetros del pr son: la lectura modo (binario o analógico), y el pin número. Si no hay error, el Arduino coloca el valor del pin solicitado en el campo "et" clave de su respuesta. Toma, pin0 indica que se ha solicitado el valor del pin 0, y 1023 es ese valor. En modo lectura, un valor analógico estará en el rango [0, 1024].
Hemos introducido los tres comandos cl, pw, y pr. Cabe preguntarse por qué no hemos utilizado campos más explícitos en las cadenas JSON, como por ejemplo acción en lugar de ac, pinwrite en lugar de pw, y parámetros en lugar de pa. Un Arduino tiene una memoria muy limitada. Sin embargo, las cadenas JSON intercambiadas con el Arduino contribuyen al uso de memoria. Por lo tanto, hemos optado por acortarlas tanto como sea posible.
Veamos ahora algunos casos de error:
Comando | |
Respuesta |
Se ha enviado un comando que no está en formato JSON. El Arduino ha devuelto el código de error 100.
Comando | |
Respuesta |
A pr se envió sin el comando pin parámetro. El Arduino devolvió el código de error 302.
Comando | |
Respuesta |
Enviamos a un desconocido pinread (es pr). El Arduino devolvió el código de error 104.
No vamos a seguir con los ejemplos. La regla es simple. El Arduino no debe colapsar, independientemente del comando que se le envíe. Antes de ejecutar un comando JSON, se asegura de que el comando es válido. Tan pronto como se produce un error, el Arduino deja de ejecutar el comando y devuelve la cadena de error JSON a su cliente. Una vez más, debido a que el espacio de memoria es limitado, devolvemos un código de error en lugar de un mensaje completo.
El código del programa que se ejecuta en el Arduino se proporciona en los ejemplos de este documento:
![]() |
Para transferirlo al Arduino:
- Conéctalo a tu PC;
![]() | ![]() |
- en [1], abra el archivo [arduino_uno.ino]. El Arduino IDE se iniciará y cargará el archivo;
Nota: El código fue originalmente creado y probado con Arduino IDE 1.5.x. Desde entonces, se han publicado otras versiones del IDE. El código no funciona con Arduino IDE 1.6.x. Parece que hay un problema de compatibilidad entre las versiones 1.6 y 1.5.
- En [2-4], especifique el tipo de Arduino utilizado;
![]() | ![]() |
- En [5-7], especifique a qué puerto serie del PC está conectado;
- en [8], carga el programa [arduino_uno] en el Arduino;
El código del programa está muy comentado. Los lectores interesados pueden consultarlo. Simplemente destacamos las líneas de código que configuran la comunicación bidireccional cliente/servidor entre el Arduino y el PC:
#include <SPI.h>
#include <Ethernet.h>
#include <ajSON.h>
// ---------------------------------- ARDUINO UNO CONFIGURATION
// Arduino UNO MAC address
byte macArduino[] = {
0x90, 0xA2, 0xDA, 0x0D, 0xEE, 0xC7 };
char * strMacArduino="90:A2:DA:0D:EE:C7";
// Arduino IP address
IPAddress ipArduino(192, 168, 2, 2);
// its identifier
char * idArduino="kitchen";
// Arduino server port
int ArduinoPort = 102;
// Arduino description
char * descriptionArduino="home automation control";
// The Arduino server will run on port 102
EthernetServer server(ArduinoPort);
// IP address of the registration server
IPAddress recordingServerIP(192, 168, 2, 1);
// Registration server port
int recordingServerPort = 100;
// Arduino client for the recording server
EthernetClient ArduinoClient;
// client command
char command[100];
// the Arduino's response
char message[100];
// initialization
void setup() {
// The serial monitor will allow you to follow the exchanges
Serial.begin(9600);
// Start the Ethernet connection
Ethernet.begin(macArduino, ipArduino);
// Available memory
Serial.print(F("Available memory: "));
Serial.println(freeRam());
}
// infinite loop
void loop()
{
...
}
- Línea 8: la dirección MAC del Arduino. No importa mucho aquí porque el Arduino estará en una red privada con un PC y uno o más Arduinos. La dirección MAC simplemente necesita ser única en esta red privada. Normalmente, la placa de red del Arduino tiene una pegatina que indica la dirección MAC de la placa. Si esta pegatina no está y no conoces la dirección MAC de la placa, puedes introducir lo que quieras en la línea 8 siempre que se cumpla la regla de que la dirección MAC sea única en la red privada;
- línea 11: la dirección IP de la tarjeta. De nuevo, puedes introducir cualquier valor de la forma [192.168.2.x] y variar la x para los diferentes Arduinos de la red privada;
- Línea 13: Identificador del Arduino. Debe ser único entre los identificadores de Arduinos en la misma red privada;
- línea 15: el puerto de servicio del Arduino. Puedes introducir lo que quieras;
- línea 17: descripción de la función del Arduino. Puedes introducir lo que quieras. Tenga cuidado con las cadenas largas debido a la memoria limitada del Arduino;
- línea 21: IP dirección del servidor de registro del Arduino en el PC. No debe modificarse;
- línea 23: puerto para este servicio de registro. No debe modificarse;
5.4. El servidor Web/JSON
5.4.1. Instalación

Se proporciona el binario Java para el servidor web/jSON:
![]() |
Abra un símbolo del sistema y escriba el siguiente comando:
If [java.exe] is not in the command prompt’s PATH, you will need to type the full path to [java.exe] (usually C:\Program Files\java\...).
Se abrirá una ventana DOS que mostrará los registros:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v0.5.0.M6)
2014-01-06 11:11:35.550 INFO 8408 --- [ main] arduino.rest.metier.Application : Starting Application on Gportpers3 with PID 8408 (C:\Users\SergeTahÚ\Desktop\part2\server.jar started by ST)
2014-01-06 11:11:35.587 INFO 8408 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6a4ba620: startup date [Mon Jan 06 11:11:35 CET 2014]; root of context hierarchy
2014-01-06 11:11:36.765 INFO 8408 --- [ main] org.apache.catalina.core.StandardService : Starting Tomcat service
2014-01-06 11:11:36.766 INFO 8408 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.42
2014-01-06 11:11:36.876 INFO 8408 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2014-01-06 11:11:36.877 INFO 8408 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1293 ms
2014-01-06 11:11:37.084 INFO 8408 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2014-01-06 11:11:37.084 INFO 8408 --- [ost-startStop-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2014-01-06 11:11:37.184 INFO 8408 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-01-06 11:11:37.386 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/blink/{idCommande}/{idArduino}/{pin}/{duree}/{nombre}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.makeLEDBlink(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.388 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/commands/{idArduino}],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.sendCommandesJson(java.lang.String,java.lang.String,javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.388 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.getArduinos(javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.389 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/pinRead/{idCommande}/{idArduino}/{pin}/{mode}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.pinRead(java.lang.String,java.lang.String,java.lang.String,java.lang.String,javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.390 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/pinWrite/{idCommande}/{idArduino}/{pin}/{mode}/{value}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.pinWrite(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.463 INFO 8408 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-01-06 11:11:37.464 INFO 8408 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-01-06 11:11:37.881 INFO 8408 --- [ost-startStop-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 796 ms
Recording server started on 192.168.2.1:100
2014-01-06 11:11:38.101 INFO 8408 --- [ Thread-4] arduino.dao.Recorder : Recorder : [11:11:38:101] : [Recording server: waiting for a client]
2014-01-06 11:11:38.142 INFO 8408 --- [ main] arduino.rest.metier.Application : Started Application in 3.257 seconds
- línea 11: se lanza un servidor Tomcat integrado;
- línea 15: se carga y ejecuta el servlet Spring MVC [dispatcherServlet];
- línea 18: el REST URL [/arduinos/blink/{commandId}/{ArduinoId}/{pin}/{duration}/{count}];
- línea 19: el REST URL [/arduinos/commands/{idArduino}];
- línea 20: el REST URL [/arduinos/];
- línea 21: el REST URL [/arduinos/pinRead/{commandId}/{ArduinoId}/{pin}/{mode}];
- línea 22: el REST URL [/arduinos/pinWrite/{commandId}/{ArduinoId}/{pin}/{mode}/{value}];
- línea 26: se inicia el servidor de registro de Arduino;
Conecta tu Arduino al PC si aún no lo has hecho. El cortafuegos del PC debe estar desactivado. A continuación, en un navegador web, introduzca el URL [http://localhost:8080/arduinos]:
![]() |
Deberías ver aparecer el ID del Arduino conectado. Si no aparece nada, prueba a reiniciar el Arduino. Tiene un botón de reinicio para este propósito.
El servidor web/JSON ya está instalado.
5.4.2. El URLs expuesto por el servicio web/JSON
Véase: proyecto [Ejemplo-15] (véase la sección 1.16.1);
El servicio web/JSON fue implementado usando Spring MVC y expone lo siguiente URLs:
@Controller
public class WebController {
// business layer
@Autowired
private IBusinessModel businessModel;
// list of Arduinos
@RequestMapping(value = "/arduinos", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String getArduinos() throws JsonProcessingException {
...
}
// blinking
@RequestMapping(value = "/arduinos/blink/{commandId}/{ArduinoId}/{pin}/{duration}/{count}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String makeLEDBlink(@PathVariable("commandId") String commandId, @PathVariable("ArduinoId") String ArduinoId, @PathVariable("pin") int pin, @PathVariable("duration") int duration, @PathVariable("count") int count) throws JsonProcessingException {
...
}
// sending JSON commands
@RequestMapping(value = "/arduinos/commands/{idArduino}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String sendJSONCommands(@PathVariable("idArduino") String idArduino, HttpServletRequest request) throws IOException {
...
}
// read pin
@RequestMapping(value = "/arduinos/pinRead/{idCommande}/{idArduino}/{pin}/{mode}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String pinRead(@PathVariable("idCommande") String idCommande, @PathVariable("idArduino") String idArduino, @PathVariable("pin") int pin, @PathVariable("mode") String mode) throws JsonProcessingException {
....
}
// write pin
@RequestMapping(value = "/arduinos/pinWrite/{idCommande}/{idArduino}/{pin}/{mode}/{value}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String pinWrite(@PathVariable("idCommande") String idCommande, @PathVariable("idArduino") String idArduino, @PathVariable("pin") int pin, @PathVariable("mode") String mode, @PathVariable("value") int value) throws JsonProcessingException {
...
}
}
Las respuestas enviadas por el servidor son representaciones JSON de la siguiente clase [Response<T>]:
package client.android.dao.service;
import java.util.List;
public class Response<T> {
// ----------------- properties
// operation status
private int status;
// any status messages
private List<String> messages;
// response body
private T body;
// constructors
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// getters and setters
...
}
El URL [/arduinos] devuelve una respuesta de tipo [Response<List<Arduino>>], donde [Arduino] es la siguiente clase:
package android.arduinos.entities;
import java.io.Serializable;
public class Arduino implements Serializable {
// data
private String id;
private String description;
private String mac;
private String ip;
private int port;
// getters and setters
...
}
- línea 7: [id] es el identificador del Arduino;
- línea 8: su descripción;
- línea 9: su dirección MAC;
- línea 10: su dirección IP;
- línea 11: el puerto en el que escucha comandos;
El URLs:
- [/arduinos/blink/{commandId}/{ArduinoId}/{pin}/{duration}/{count}];
- [/arduinos/pinRead/{commandId}/{ArduinoId}/{pin}/{mode}] ;
- [/arduinos/pinWrite/{commandId}/{ArduinoId}/{pin}/{mode}/{value}] ;
- [/arduinos/commands/{ArduinoID}];
devuelve una respuesta de tipo [Response<ArduinoResponse>], donde la clase [ArduinoResponse] representa la respuesta estándar de Arduino:
public class ArduinoResponse implements Serializable {
private String json;
private String id;
private String error;
private Map<String, Object> status;
// getters and setters
...
}
- [json]: la cadena JSON enviada por un Arduino que no ha podido ser decodificada (en caso de error), null de lo contrario;
- [id]: el identificador del comando al que responde el Arduino;
- [error]: un código de error, 0 si OK, en caso contrario otra cosa;
- [estado]: un diccionario que contiene la respuesta específica del comando. Normalmente está vacío a menos que el comando solicite la lectura de un valor del Arduino, en cuyo caso ese valor se colocará en este diccionario;
5.4.3. Pruebas de servicios web / JSON
Familiarícese con el servidor web / JSON probando el siguiente URLs:
Aquí tienes algunas capturas de pantalla de lo que deberías ver:
Obtener la lista de Arduinos conectados:
![]() |
La cadena JSON recibida del servidor web / JSON es un objeto con los siguientes campos:
- [estado]: 0 indica que no hay error; de lo contrario, se ha producido un error;
- [mensajes]: una lista de mensajes que explican el error si se ha producido un error:
- [cuerpo]: la lista de Arduinos si no se ha producido ningún error. A continuación, cada Arduino se describe mediante un objeto con los siguientes campos:
- [id]: el identificador del Arduino. No puede haber dos Arduinos con el mismo identificador;
- [descripción]: una breve descripción de la funcionalidad del Arduino;
- [mac]: la dirección MAC del Arduino;
- [ip]: la dirección IP del Arduino;
- [puerto]: el puerto en el que escucha comandos;
Haz que el LED del pin 8 del Arduino identificado con [cuisine] parpadee 20 veces cada 100 ms:
![]() |
La cadena JSON recibida del servidor web / JSON es un objeto con los siguientes campos:
- [estado]: 0 indica que no hay error; de lo contrario, se ha producido un error;
- [mensajes]: una lista de mensajes que explican el error si se ha producido un error:
- [cuerpo]: la respuesta del Arduino si no hubo error:
- [id]: identificador del comando. Este identificador es el 1 en [/blink/1]. El Arduino incluye este identificador de comando en su respuesta;
- [error]: un número de error. Un valor distinto de 0 indica un error;
- [Estado]: sólo se utiliza para leer un pin. Su valor es el valor del pin;
- [json]: se utiliza sólo en caso de error JSON entre el cliente y el servidor. Su valor es la cadena JSON errónea enviada por el Arduino;
Lectura analógica del pin 0 del Arduino identificado por [cocina]:
![]() |
La cadena JSON recibida del servidor web /json es similar a la anterior, con la excepción del campo [state], que representa el valor del pin 0.
Lectura binaria del pin 5 del Arduino identificado por [cocina]:
![]() |
La cadena JSON recibida del servidor web /json es similar a la anterior.
Escritura binaria del valor 1 en el pin 8 del Arduino identificado por [cocina]:
![]() |
La cadena JSON recibida del servidor web /json es similar a la anterior.
Probar el URL [http://localhost:8080/arduinos/commands/cuisine] es más complicado. El método /jSON del servidor web que gestiona este URL espera una petición POST, que no puede simularse fácilmente utilizando un navegador. Para probar este URL, puede utilizar un navegador Chrome con la extensión [Advanced REST Client] (consulte la sección 6.13):
![]() |
- en [1], el URL de la web/JSON método a probar;
- en [2], el método POST para enviar la solicitud;
- en [3-4], el valor contabilizado es JSON;
- en [5], contabilizándose la cadena JSON. Fíjese en los corchetes que empiezan y terminan la lista. Aquí, la lista contiene sólo un comando JSON que hace que el pin 8 parpadee 10 veces cada 100 ms;
- en [6], se envía la solicitud;
![]() |
- en [7], la respuesta JSON enviada por el servidor. El objeto recibido es un objeto con los dos campos habituales [estado, mensajes] y un campo [cuerpo] cuyo valor es la lista de Arduino respuestas a cada uno de los comandos JSON enviados.
Veamos qué ocurre cuando enviamos un comando JSON con una sintaxis incorrecta al Arduino:
![]() |
A continuación recibimos la siguiente respuesta:
![]() |
Podemos ver que en la respuesta del Arduino, el código de error es [104], indicando que el comando [xx] no fue reconocido.
5.5. Pruebas de clientes Android
![]() |
A continuación se proporciona el ejecutable del cliente Android terminado:
![]() |
Utilice el ratón para arrastrar el archivo [app-debug.apk] anterior a un emulador de tableta [GenyMotion]. A continuación, se guardará y se ejecutará. Ejecuta también el servidor web/jSON si aún no lo has hecho. Conecta el Arduino al PC con un LED conectado a él. El cliente Android permite gestionar los Arduinos de forma remota. Muestra las siguientes pantallas al usuario.
La pestaña [CONFIG] permite conectarse al servidor y recuperar la lista de Arduinos conectados:

- En [1], introduzca la dirección IP [192.168.2.1] asignada a su PC (consulte la sección 5.2).
La pestaña [PINWRITE] permite escribir un valor en un pin de Arduino:


La pestaña [PINREAD] permite leer el valor de un pin de Arduino:

La pestaña [BLINK] permite hacer parpadear un Arduino LED:

La pestaña [COMMAND] permite enviar un comando JSON a un Arduino:

5.6. El cliente Android para el servicio web / JSON
Ahora discutiremos la escritura del cliente Android.
5.6.1. Arquitectura de clientes
La arquitectura del cliente Android será la del proyecto [Example-15] (véase la sección 1.16.2);
![]() |
- la capa [DAO] se comunica con el servidor web/JSON;
El cliente Android debe ser capaz de controlar múltiples Arduinos simultáneamente. Por ejemplo, queremos ser capaces de hacer que dos LEDs en dos Arduinos parpadeen al mismo tiempo, no uno tras otro. Por lo tanto, nuestro cliente Android utilizará una tarea asíncrona por Arduino, y estas tareas se ejecutarán en paralelo.
5.6.2. El proyecto cliente de Android Studio
Duplique el proyecto [client-android-skel] (consulte la sección 2) en el proyecto [client-arduinos-01] (si es necesario, revise cómo duplicar un proyecto Gradle en la sección 1.15):
5.6.3. Los cinco puntos de vista de XML
![]() |
Habrá cinco XML vistas:
- [blink]: para hacer parpadear un LED de Arduino. Está asociado al fragmento [BlinkFragment];
- [Comandos]: para enviar un comando JSON a un Arduino. Está asociado al fragmento [CommandsFragment];
- [config]: para configurar el servicio web/JSON URL y recuperar la lista inicial de Arduinos conectados. Está asociado al fragmento [ConfigFragment];
- [pinread]: para leer el valor binario o analógico de un pin Arduino. Está asociado al fragmento [PinReadFragment];
- [pinwrite]: para escribir un valor binario o analógico en un pin de Arduino. Está asociado al fragmento [PinWriteFragment];
For now, these five XML views will all have the same empty content:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
</ScrollView>
- La vista está dentro de un contenedor [RelativeLayout] (líneas 7-10), que a su vez está contenido dentro de un contenedor [ScrollView] (líneas 2-11). Esto garantiza que podamos desplazar la vista si supera el tamaño de la pantalla de una tableta;
Asignación: Cree las cinco vistas XML.
5.6.4. El menú de fragmentos
Sabemos que los fragmentos de un proyecto construido con [client-android-skel] deben estar asociados a un menú, aunque esté vacío. En este caso, la aplicación no tendrá menú. El menú vacío ya está en el proyecto;
![]() |
5.6.5. Los cinco fragmentos de la aplicación
![]() | ![]() |
Tarea: Duplique el fragmento [DummyFragment] en los cinco fragmentos de la aplicación, como se muestra en [2].
El fragmento [ConfigFragment] tiene el siguiente esqueleto:
package client.android.fragments.behavior;
import client.android.R;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.custom.CoreState;
import client.android.fragments.state.DummyFragmentState;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.OptionsMenu;
@EFragment
@OptionsMenu(R.menu.menu_vide)
public class ConfigFragment extends AbstractFragment {
// Fields inherited from the parent class -------------------------------------------------------
...
Sustituya la línea 10 por la siguiente
Tarea: Haga lo mismo para los otros cuatro fragmentos adaptando el atributo [@EFragment] de la clase.
Fragmento | Ver |
ConfigFragment | |
PinReadFragment | |
PinWriteFragment | |
CommandsFragment | |
BlinkFragment | |
5.6.6. Estados de los fragmentos
![]() | ![]() |
Cada fragmento tendrá un estado.
Tarea: Duplica la clase [DummyFragmentState] cinco veces para crear los cinco estados mostrados en [2].
5.6.7. Personalización de proyectos
![]() |
El paquete [architecture / custom] contiene los elementos personalizables de la arquitectura de la aplicación.
5.6.7.1. La interfaz [IMainActivity]
La interfaz [IMainActivity] define lo que los fragmentos pueden solicitar a la actividad así como las constantes de la aplicación. Esta interfaz será la siguiente:
package client.android.architecture.custom;
import client.android.architecture.core.ISession;
import client.android.dao.service.IDao;
public interface IMainActivity extends IDao {
// access to the session
ISession getSession();
// change view
void navigateToView(int position, ISession.Action action);
// wait management
void beginWaiting();
void cancelWaiting();
// application constants -------------------------------------
// debug mode
boolean IS_DEBUG_ENABLED = true;
// maximum wait time for server response
int TIMEOUT = 1000;
// wait time before executing the client request
int DELAY = 000;
// Basic authentication
boolean IS_BASIC_AUTHENTIFICATION_NEEDED = false;
// fragment adjacency
int OFF_SCREEN_PAGE_LIMIT = 1;
// tab bar
boolean ARE_TABS_NEEDED = true;
// loading image
boolean IS_WAITING_ICON_NEEDED = true;
// number of fragments
int FRAGMENTS_COUNT = 5;
// number of views
int VIEW_CONFIG = 0;
int VIEW_BLINK = 1;
int VIEW_PINREAD = 2;
int VIEW_PINWRITE = 3;
int VIEW_COMMANDS = 4;
}
- líneas 25, 28, 31, 40: configuración de la capa [DAO]. Esta aplicación consulta un servidor web/JSON;
- línea 37: esta aplicación tiene pestañas;
- línea 43: esta aplicación tiene cinco fragmentos;
- líneas 46-50: los números de los cinco fragmentos;
- línea 34: adyacencia de fragmentos. El desarrollador puede establecer aquí un valor dentro del rango [1, FRAGMENTS_COUNT-1];
5.6.7.2. La clase [CoreState]
La clase [CoreState] es la clase padre de los estados de fragmento:
package client.android.architecture.custom;
import client.android.architecture.core.MenuItemState;
import client.android.fragments.state.*;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = ConfigFragmentState.class),
@JsonSubTypes.Type(value = BlinkFragmentState.class),
@JsonSubTypes.Type(value = PinReadFragmentState.class),
@JsonSubTypes.Type(value = PinWriteFragmentState.class),
@JsonSubTypes.Type(value = CommandsFragmentState.class)}
)
public class CoreState {
// fragment visited or not
protected boolean hasBeenVisited = false;
// state of the fragment's menu (if any)
protected MenuItemState[] menuOptionsState;
// getters and setters
...
}
- líneas 12-16: aquí deben declararse las clases de estado para los cinco fragmentos;
5.6.8. La clase [MainActivity]
![]() |
La clase [MainActivity] será la siguiente:
package client.android.activity;
import android.support.design.widget.TabLayout;
import android.util.Log;
import client.android.R;
import client.android.architecture.core.AbstractActivity;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.core.ISession;
import client.android.architecture.custom.IMainActivity;
import client.android.architecture.custom.Session;
import client.android.dao.entities.Arduino;
import client.android.dao.entities.ArduinoCommand;
import client.android.dao.entities.ArduinoResponse;
import client.android.dao.service.Dao;
import client.android.dao.service.IDao;
import client.android.dao.service.Response;
import client.android.fragments.behavior.*;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.OptionsMenu;
import rx.Observable;
import java.util.List;
import java.util.Locale;
@EActivity
@OptionsMenu(R.menu.menu_main)
public class MainActivity extends AbstractActivity {
// [DAO] layer
@Bean(Dao.class)
protected IDao dao;
// session
private Session session;
// parent class methods -----------------------
@Override
protected void onCreateActivity() {
// log
if (IS_DEBUG_ENABLED) {
Log.d(className, "onCreateActivity");
}
// session
this.session = (Session) super.session;
// create the five tabs
for (int i = 0; i < 5; i++) {
TabLayout.Tab newTab = tabLayout.newTab();
newTab.setText(getFragmentTitle(i));
tabLayout.addTab(newTab);
}
}
@Override
protected IDao getDao() {
return dao;
}
@Override
protected AbstractFragment[] getFragments() {
return new AbstractFragment[]{new ConfigFragment_(), new BlinkFragment_(), new PinReadFragment_(), new PinWriteFragment_(), new CommandsFragment_()};
}
@Override
protected CharSequence getFragmentTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.config_title).toUpperCase(l);
case 1:
return getString(R.string.blink_title).toUpperCase(l);
case 2:
return getString(R.string.pinread_title).toUpperCase(l);
case 3:
return getString(R.string.pinwrite_title).toUpperCase(l);
case 4:
return getString(R.string.commands_title).toUpperCase(l);
}
return null;
}
@Override
protected void navigateOnTabSelected(int position) {
// display fragment position
navigateToView(position, ISession.Action.NAVIGATION);
}
@Override
protected int getFirstView() {
return IMainActivity.VUE_CONFIG;
}
// IDao implementation -----------------------------------------
}
- líneas 46-50: creación de las cinco pestañas de la aplicación;
- línea 48: los títulos de las pestañas son proporcionados por el método de las líneas 63-79;
- los cinco fragmentos se instancian en la línea 60. Debido a las anotaciones AA, las clases de fragmentos son las presentadas anteriormente, sufijadas con un guión bajo;
- líneas 63-79: se define un título para cada fragmento. Estos títulos se recuperarán del archivo [res/values/strings.xml]
![]() |
El contenido de [strings.xml] es el siguiente:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- application name -->
<string name="app_name">[arduinos-client-01]</string>
<!-- Fragments and tabs -->
<string name="config_title">[Config]</string>
<string name="blink_title">[Blink]</string>
<string name="pinread_title">[PinRead]</string>
<string name="pinwrite_title">[PinWrite]</string>
<string name="commands_title">[Commands]</string>
</resources>
Tarea: Cree los elementos enumerados anteriormente y compile el proyecto. No debería haber errores.
Ejecute el proyecto. Deberías ver la siguiente vista en el emulador:

Examine los registros que acompañan a la visualización de la primera vista y siga los distintos pasos que se han ejecutado. Cambia de pestaña y continúa siguiendo los registros.
5.6.9. La vista [config] XML
La vista [config] XML tendrá el siguiente aspecto:
![]() | ![]() |
La vista anterior es generada por el siguiente código XML:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txt_TitreConfig"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="150dp"
android:text="@string/txt_TitreConfig"
android:textSize="@dimen/title"/>
<TextView
android:id="@+id/txt_UrlServiceRest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_TitreConfig"
android:layout_marginTop="50dp"
android:text="@string/txt_UrlServiceRest"
android:textSize="20sp"/>
<EditText
android:id="@+id/edt_UrlServiceRest"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_UrlServiceRest"
android:layout_alignBottom="@+id/txt_UrlServiceRest"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_UrlServiceRest"
android:ems="10"
android:hint="@string/hint_UrlServiceRest"
android:inputType="textUri">
<requestFocus/>
</EditText>
<TextView
android:id="@+id/txt_MsgErreurIpPort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_UrlServiceRest"
android:layout_marginTop="20dp"
android:text="@string/txt_MsgErreurUrlServiceRest"
android:textColor="@color/red"
android:textSize="20sp"/>
<TextView
android:id="@+id/txt_arduinos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_MsgErreurIpPort"
android:layout_marginTop="40dp"
android:text="@string/title_arduinos_list"
android:textColor="@color/blue"
android:textSize="20sp"/>
<Button
android:id="@+id/btn_Refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_arduinos"
android:layout_alignBottom="@+id/txt_arduinos"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_arduinos"
android:text="@string/btn_refresh"/>
<Button
android:id="@+id/btn_Cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_arduinos"
android:layout_alignBottom="@+id/txt_arduinos"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_arduinos"
android:text="@string/btn_cancel"
android:visibility="invisible"/>
<ListView
android:id="@+id/ListViewArduinos"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_arduinos"
android:layout_marginTop="30dp"
android:background="@color/wheat">
</ListView>
</RelativeLayout>
</ScrollView>
La vista utiliza cadenas (android:texto en las líneas 15, 25, 37, 50, 61, 73) que se definen en el archivo [res/values/strings]:
![]() |
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">android-home-automation</string>
<!-- Fragments and tabs -->
<string name="config_title">[Config]</string>
<string name="blink_title">[Blink]</string>
<string name="pinread_title">[PinRead]</string>
<string name="pinwrite_title">[PinWrite]</string>
<string name="commands_title">[Commands]</string>
<!-- Config -->
<string name="txt_ConfigTitle">Connect to the server</string>
<string name="txt_UrlServiceRest">Web service URL / JSON</string>
<string name="txt_MsgErreurUrlServiceRest">The service URL must be entered in the format IP1.IP2.IP3.IP4:Port/context</string>
<string name="hint_UrlServiceRest">e.g. (192.168.1.120:8080/rest)</string>
<string name="btn_cancel">Cancel</string>
<string name="btn_refresh">Refresh</string>
<string name="title_list_arduinos">List of Connected Arduinos</string>
</resources>
La vista utiliza colores (android:textColor en las líneas 51 y 62) definidos en el fichero [res/values/colors]:
![]() |
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="floral_white">#FFFAF0</color>
<!-- app -->
<color name="red">#FF0000</color>
<color name="blue">#0000FF</color>
<color name="wheat">#FFEFD5</color>
</resources>
La vista utiliza dimensiones (android:textSize en la línea 16) que se definen en el archivo [res/values/dimens]:
![]() |
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="appbar_padding_top">8dp</dimen>
<!-- app -->
<dimen name="title">30dp</dimen>
</resources>
Esta técnica no se ha utilizado para todas las dimensiones. Sin embargo, es el método recomendado. Permite cambiar las dimensiones en un único lugar.
Tarea: Crea los elementos anteriores.
Vuelva a ejecutar el proyecto. Debería ver la siguiente vista:

5.6.10. El fragmento [ConfigFragment]
![]() |
Para manejar la nueva vista [config], el código del fragmento [ConfigFragment] cambia como sigue:
package client.android.fragments.behavior;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import client.android.R;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.custom.CoreState;
import client.android.architecture.custom.IMainActivity;
import client.android.fragments.state.ConfigFragmentState;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.ViewById;
@EFragment(R.layout.config)
@OptionsMenu(R.menu.menu_empty)
public class ConfigFragment extends AbstractFragment {
// UI elements
@ViewById(R.id.btn_Refresh)
protected Button btnRefresh;
@ViewById(R.id.btn_Cancel)
protected Button btnCancel;
@ViewById(R.id.edt_UrlServiceRest)
protected EditText edtUrlServiceRest;
@ViewById(R.id.txt_IPPortErrorMessage)
protected TextView txtRestServiceErrorMessage;
@ViewById(R.id.ListViewArduinos)
protected ListView listArduinos;
@Click(R.id.btn_Refresh)
protected void refresh() {
}
// Fragment lifecycle management -------------------------------------
@Override
public CoreState saveFragment() {
return new ConfigFragmentState();
}
@Override
protected int getNumView() {
return IMainActivity.VUE_CONFIG;
}
@Override
protected void initFragment(CoreState previousState) {
}
@Override
protected void initView(CoreState previousState) {
// First visit?
if(previousState==null){
txtMsgErreurUrlServiceRest.setVisibility(View.INVISIBLE);
}
}
@Override
protected void updateOnSubmit(CoreState previousState) {
}
@Override
protected void updateOnRestore(CoreState previousState) {
}
@Override
protected void notifyEndOfUpdates() {
// buttons
initButtons();
}
@Override
protected void notifyEndOfTasks(boolean runningTasksHaveBeenCanceled) {
}
// private methods --------------------------------------------
private void initButtons() {
// The [Run] button replaces the [Cancel] button
btnCancel.setVisibility(View.INVISIBLE);
btnRefresh.setVisibility(View.VISIBLE);
}
}
- líneas 23-32: los elementos de la interfaz visual;
- líneas 58-60: en la primera visita al fragmento, el mensaje de error está oculto;
- líneas 73-76: cada vez que se muestra el fragmento, se oculta el botón [Cancelar] (línea 82) y se muestra el botón [Actualizar] (líneas 86-87). De hecho, en esta aplicación, no se puede mostrar un fragmento mientras está en curso una operación asíncrona y, por lo tanto, el botón [Cancelar] está visible;
Tarea: Crea los elementos anteriores.
Ejecute esta nueva versión. La primera vista debería tener ahora este aspecto:

5.6.10.1. El botón [Actualizar
Por ahora, manejaremos el clic en el botón [Actualizar] de la siguiente manera:
@Click(R.id.btn_Refresh)
protected void doRefresh() {
// We're going to start a task—we're preparing to wait
beginWaiting(1);
}
@Click(R.id.btn_Cancel)
protected void doCancel() {
if (isDebugEnabled) {
Log.d(className, "Cancellation requested");
}
// cancel asynchronous tasks
cancelRunningTasks();
}
protected void beginWaiting(int numberOfRunningTasks) {
// prepare to wait for tasks
beginRunningTasks(numberOfRunningTasks);
// The [Cancel] button replaces the [Refresh] button
btnRefresh.setVisibility(View.INVISIBLE);
btnCancel.setVisibility(View.VISIBLE);
}
// fragment lifecycle management -------------------------------------
...
@Override
protected void notifyEndOfTasks(boolean runningTasksHaveBeenCanceled) {
// buttons in their initial state
initButtons();
}
// private methods --------------------------------------------
private void initButtons() {
// the [Run] button replaces the [Cancel] button
btnCancel.setVisibility(View.INVISIBLE);
btnRefresh.setVisibility(View.VISIBLE);
}
- líneas 1-5: el método que se ejecuta cuando se hace clic en el botón [Actualizar];
- línea 4: comenzamos la espera;
- línea 18: pasamos el número de tareas asíncronas a lanzar a la clase padre. Aparecerá la imagen de carga;
- líneas 20-21: esta espera hará que aparezca el botón [Cancelar], desaparezca el botón [Actualizar] y aparezca la imagen de carga. No ocurre nada más. Sin embargo, el usuario puede hacer clic en el botón [Cancelar]. Entonces se ejecutará el método de las líneas 7-14;
- línea 13: se pide a la clase padre que cancele todas las tareas. La clase lo hará y a su vez llamará al método de las líneas 25-29 para indicar que todas las tareas se han completado. El parámetro [runningTasksHaveBeenCanceled] tendrá el valor verdadero para indicar que las tareas se han cancelado;
- Líneas 35-36: El botón [Cancelar] desaparecerá, mientras que el botón [Actualizar] reaparecerá.
Tarea: Realice estos cambios y, a continuación, ejecute el proyecto. Compruebe que el botón [Actualizar] inicia la espera y que el botón [Cancelar] la detiene. Observa los registros.
5.6.10.2. Validación de entradas
En la versión anterior, no validábamos el URL introducido. Para validarlo, añadimos el siguiente código en [ConfigFragment]:
// the entered values
private String urlServiceRest;
@Click(R.id.btn_Rafraichir)
protected void doRafraichir() {
// we check the inputs
if (!pageValid()) {
return;
}
// we're going to start a task - we prepare for the wait
beginWaiting(1);
}
// checking the inputs
private boolean pageValid() {
// initially, no error message
txtMsgErreurUrlServiceRest.setVisibility(View.INVISIBLE);
// retrieve the server's IP and port
urlServiceRest = String.format("http://%s", edtUrlServiceRest.getText().toString().trim());
// check its validity
try {
URI uri = new URI(urlServiceRest);
String host = uri.getHost();
int port = uri.getPort();
if (host == null || port == -1) {
throw new Exception();
}
} catch (Exception ex) {
// display error message
txtMsgErreurUrlServiceRest.setVisibility(View.VISIBLE);
// return to the UI
return false;
}
// OK
return true;
}
- línea 2: el URL introducido;
- líneas 7-9: antes de hacer nada, comprobamos la validez de la entrada;
- línea 19: recuperar el URL introducido y añadirle el prefijo [http://];
- línea 22: intentamos construir un objeto URI (Uniform Resource Identifier) con él. Si el URL introducido es sintácticamente incorrecto, se lanzará una excepción;
- líneas 23-27: se lanza una excepción si el URI es válido pero [host==null] y [port==-1]. Este es un escenario posible;
- línea 30: se ha producido una excepción. Se muestra el mensaje de error;
- línea 32: devolvemos [false] para indicar que la página no es válida;
- línea 35: no se ha producido ningún error. Devolvemos [true] para indicar que la página es válida;
Asignación: Implementar la funcionalidad anterior.
Pruebe esta nueva versión y compruebe que los URLs no válidos se marcan correctamente.
5.6.10.3. Visualización de la lista de Arduinos
![]() |
Las diferentes vistas tendrán que mostrar la lista de Arduinos conectados. Para ello, definiremos diferentes clases y una vista XML:
- Un Arduino estará representado por la clase [Arduino] [1];
- la clase [CheckedArduino] [1] hereda de la clase [Arduino], a la que hemos añadido un booleano para indicar si el Arduino ha sido seleccionado en una lista;
La clase [Arduino] es la que ya utiliza el servidor y que se presenta en la sección 5.4.2. Es el siguiente:
package android.arduinos.entities;
import java.io.Serializable;
public class Arduino implements Serializable {
// data
private String id;
private String description;
private String mac;
private String ip;
private int port;
// getters and setters
...
}
- línea 7: [id] es el identificador del Arduino;
- línea 8: su descripción;
- línea 9: su dirección MAC;
- línea 10: su dirección IP;
- línea 11: el puerto en el que escucha comandos;
Esta clase corresponde a la cadena JSON recibida del servidor al solicitar la lista de Arduinos conectados:
![]() |
La clase [CheckedArduino] hereda de la clase [Arduino]:
package android.arduinos.entities;
public class CheckedArduino extends Arduino {
private static final long serialVersionUID = 1L;
// an Arduino can be selected
private boolean isChecked;
// constructor
public CheckedArduino(Arduino arduino, boolean isChecked) {
// parent
super(arduino.getId(), arduino.getDescription(), arduino.getMac(), arduino.getIp(), arduino.getPort());
// local
this.isChecked = isChecked;
}
// getters and setters
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
}
}
- Línea 3: La clase [CheckedArduino] hereda de la clase [Arduino];
- línea 6: añadimos una variable booleana que nos dirá si se ha seleccionado un Arduino de la lista de Arduinos mostrada;
En [ConfigFragment], simularemos la recuperación de la lista de Arduinos conectados.
![]() |
@ViewById(R.id.ListViewArduinos)
protected ListView listArduinos;
..
@Click(R.id.btn_Rafraichir)
protected void doRefresh() {
// check the input
if (!pageValid()) {
return;
}
// we're going to start a task - we prepare for the wait
beginWaiting(1);
// clear the list of Arduinos
clearArduinos();
// we request the list of Arduinos in the background
getArduinosInBackground();
}
private void getArduinosInBackground() {
...
}
// Clear the list of Arduinos
private void clearArduinos() {
// create an empty list
List<String> strings = new ArrayList<>();
// display it
listArduinos.setAdapter(new ArrayAdapter<String>(activity, android.R.layout.simple_list_item_1, android.R.id.text1, strings));
}
- línea 2: el ListView que muestra los Arduinos conectados al servidor;
- línea 5: el método que recupera la lista de Arduinos conectados;
- línea 11: le decimos a la clase padre que vamos a lanzar una tarea asíncrona;
- línea 12: borramos la lista de Arduinos mostrada actualmente;
- línea 15: solicitamos la lista de Arduinos conectados como tarea en segundo plano;
- líneas 23-28: el método que borra la lista de Arduinos mostrada actualmente;
El [getArduinosInBackgroundel método [...] es el siguiente:
private void getArduinosInBackground() {
// create a dummy list of Arduinos
List<Arduino> arduinos = new ArrayList<>();
for (int i = 0; i < 20; i++) {
arduinos.add(new Arduino("id" + i, "desc" + i, "mac" + i, "ip" + i, i));
}
// Simulate a server response
Response<List<Arduino>> response = new Response<>();
response.setBody(arduinos);
// cancel the wait
cancelWaitingTasks();
// update the buttons
initButtons();
// process the response
consumeArduinoResponse(response);
}
- líneas 3-6: crear una lista de 20 Arduinos;
- líneas 8-9: construimos la respuesta de tipo [Response<List<Arduino>>] (sección 5.4.2) que encapsulará la lista de Arduinos creada;
- línea 11: cancelar la espera;
- línea 13: restablecer el estado inicial de los botones;
- línea 15: consumir la respuesta;
El método [consumeArduinosResponse] es el siguiente:
// display response
private void consumeArduinosResponse(Response<List<Arduino>> response) {
// error?
if (response.getStatus() != 0) {
// display
showAlert(response.getMessages());
// return to UI
return;
}
// create a list of [CheckedArduino]
List<CheckedArduino> checkedArduinos = new ArrayList<>();
for (Arduino arduino : response.getBody()) {
checkedArduinos.add(new CheckedArduino(arduino, false));
}
// display them
showArduinos(checkedArduinos);
}
- líneas 4-11: comprueba el código de error en la respuesta enviada por el servidor:
- línea 4: si el código de error no es cero;
- línea 6: mostrar los mensajes almacenados por el servidor en el campo [mensajes] de la respuesta;
- línea 8: volver al UI;
- líneas 11-16: si no hubo errores, mostrar la lista de Arduinos recibidos, después de convertirla a un Lista<CheckedArduino> tipo;
El [showArduinosel método [...] es el siguiente:
private void showArduinos(List<CheckedArduino> checkedArduinos) {
// create a list of Strings from the list of Arduinos
List<String> strings = new ArrayList<>();
for (CheckedArduino checkedArduino : checkedArduinos) {
strings.add(checkedArduino.toString());
}
// display it
listArduinos.setAdapter(new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, android.R.id.text1, strings));
}
Tarea: Realice los cambios anteriores y ejecute su proyecto.
Debería ver la siguiente vista cuando haga clic en el botón [Actualizar]:

La entrada en [1] no se utiliza. Por lo tanto, puede introducir cualquier cosa siempre que siga el formato esperado.
5.6.10.4. Una plantilla para mostrar un Arduino
Actualmente, los Arduinos conectados se muestran en la vista [Config] de la siguiente manera:

Ahora queremos mostrarlos de la siguiente manera:

- en [1], una casilla de verificación que le permitirá seleccionar un Arduino. Esta casilla se ocultará cuando desee mostrar una lista de Arduinos no seleccionables;
- en [2], el ID de Arduino;
- en [3], su descripción;
Lo que sigue se basa en los conceptos desarrollados en los proyectos [ejemplo-19] y [ejemplo-19B] de la sección 1.20. Revíselas si es necesario.
En primer lugar, creamos la vista que mostrará un elemento de la lista de Arduinos:
![]() | ![]() |
El código de la vista [listarduinos_item] anterior es el siguiente:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wheat"
android:orientation="vertical" >
<CheckBox
android:id="@+id/checkBoxArduino"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/txt_arduino_description" />
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/checkBoxArduino"
android:layout_marginLeft="40dp"
android:text="@string/txt_arduino_id" />
<TextView
android:id="@+id/txt_arduino_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/checkBoxArduino"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/TextView1"
android:text="@string/dummy"
android:textColor="@color/blue" />
<TextView
android:id="@+id/TextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/checkBoxArduino"
android:layout_alignParentTop="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_arduino_id"
android:text="@string/txt_arduino_description" />
<TextView
android:id="@+id/txt_arduino_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/checkBoxArduino"
android:layout_alignTop="@+id/TextView2"
android:layout_toRightOf="@+id/TextView2"
android:text="@string/dummy"
android:textColor="@color/blue" />
</RelativeLayout>
- líneas 9-15: la casilla de verificación;
- líneas 17-23: el texto [Id: ];
- líneas 25-33: aquí se introducirá el Arduino ID;
- líneas 35-43: el texto [Descripción: ];
- líneas 45-53: aquí se introducirá la descripción del Arduino;
Esta vista utiliza texto (líneas 23, 32, 43) definido en [res/values/strings.xml]:
<string name="dummy">XXXXX</string>
<!-- listarduinos_item -->
<string name="txt_arduino_id">ID: </string>
<string name="txt_arduino_description">Description: </string>
La vista también utiliza un color (líneas 33, 53) definido en [res / values / colors.xml]:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="red">#FF0000</color>
<color name="blue">#0000FF</color>
<color name="wheat">#FFEFD5</color>
<color name="floral_white">#FFFAF0</color>
</resources>
El gestor de vistas de un elemento de la lista Arduino
![]() |
La clase [ListArduinosAdapter] es la clase llamada por el [ListView] para mostrar cada elemento en la lista de Arduino. Su código es el siguiente:
package istia.st.android.vues;
import istia.st.android.R;
...
public class ListArduinosAdapter extends ArrayAdapter<CheckedArduino> {
// the array of Arduinos
private List<CheckedArduino> arduinos;
// the runtime context
private Context context;
// the layout ID for a row in the Arduino list
private int layoutResourceId;
// whether the row contains a checkbox
private Boolean selectable;
// constructor
public ListArduinosAdapter(Context context, int layoutResourceId, List<CheckedArduino> arduinos, Boolean selectable) {
// parent
super(context, layoutResourceId, arduinos);
// store the data
this.arduinos = arduinos;
this.context = context;
this.layoutResourceId = layoutResourceId;
this.selectable = selectable;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
...
}
}
- línea 18: el constructor de la clase toma cuatro parámetros: la actividad actualmente en ejecución, el ID de la vista a mostrar para cada elemento de la fuente de datos, la fuente de datos que rellena la lista, y un booleano que indica si la casilla de verificación asociada a cada Arduino debe mostrarse o no;
- Líneas 8-15: Estos cuatro datos se almacenan localmente;
Línea 29: El método [getView] es responsable de generar la vista #[position] en el [ListView] y manejar sus eventos. Su código es el siguiente:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// the current Arduino
final CheckedArduino arduino = arduinos.get(position);
// create the current line
View row = ((Activity) context).getLayoutInflater().inflate(layoutResourceId, parent, false);
// retrieve references to the [TextView]
TextView txtArduinoId = (TextView) row.findViewById(R.id.txt_arduino_id);
TextView txtArduinoDesc = (TextView) row.findViewById(R.id.txt_arduino_description);
// populate the row
txtArduinoId.setText(arduino.getId());
txtArduinoDesc.setText(arduino.getDescription());
// The CheckBox is not always visible
CheckBox ck = (CheckBox) row.findViewById(R.id.checkBoxArduino);
ck.setVisibility(selectable ? View.VISIBLE : View.INVISIBLE);
if (selectable) {
// Set its value
ck.setChecked(arduino.isChecked());
// handle the click
ck.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
arduino.setChecked(isChecked);
}
});
}
// return the row
return row;
}
- línea 2: el primer parámetro es la posición en el [ListView] de la línea a crear. También es la posición en la lista de Arduinos almacenada localmente;
- línea 4: recuperamos una referencia al Arduino que se asociará a la fila construida;
- línea 6: la fila actual se construye a partir de la vista [listarduinos_item.xml];
- líneas 8-9: se recuperan las referencias a los dos [TextView]s;
- líneas 11-12: se asignan valores a los dos [TextView]s;
- línea 14: se recupera una referencia a la casilla de verificación;
- línea 15: se hace visible o no, dependiendo del valor [seleccionable] pasado inicialmente al constructor;
- línea 16: si la casilla de verificación está presente;
- línea 18: se le asigna el valor [isChecked] del Arduino actual;
- líneas 20-26: manejamos el clic en la casilla de verificación;
- línea 23: el valor de la casilla de verificación se almacena en el Arduino actual;
Gestión de la lista de Arduinos
La visualización de la lista Arduino es manejada actualmente por dos métodos de la clase [ConfigFragment]:
- [clearArduinos]: que muestra una lista vacía;
- [showArduinos]: que muestra la lista devuelta por el servidor;
Estos dos métodos funcionan de la siguiente manera:
// Clear the list of Arduinos
private void clearArduinos() {
// Display an empty list
ListArduinosAdapter adapter = new ListArduinosAdapter(getActivity(), R.layout.listarduinos_item, new ArrayList<CheckedArduino>(), false);
listArduinos.setAdapter(adapter);
}
// Display list of Arduinos
private void showArduinos(List<CheckedArduino> checkedArduinos) {
// display the Arduinos
ListArduinosAdapter adapter = new ListArduinosAdapter(getActivity(), R.layout.listarduinos_item, checkedArduinos, false);
listArduinos.setAdapter(adapter);
}
Tarea: Realiza estos cambios y prueba la nueva aplicación.

5.6.10.5. La sesión
La sesión es donde almacenamos la información compartida entre los fragmentos y la actividad. Todos los fragmentos necesitan mostrar la lista de Arduinos conectados. Así, una versión inicial de la sesión tendría este aspecto:
package client.android.architecture.custom;
import client.android.activity.CheckedArduino;
import client.android.architecture.core.AbstractSession;
import java.util.ArrayList;
import java.util.List;
public class Session extends AbstractSession {
// data to be shared between fragments themselves and between fragments and the activity
// Elements that cannot be serialized to JSON must have the @JsonIgnore annotation
// Don't forget the getters and setters needed for JSON serialization/deserialization
// the list of Arduinos
private List<CheckedArduino> checkedArduinos = new ArrayList<>();
// getters and setters
...
}
Tarea: Cree la clase [Session] mostrada arriba.
La creación de esta sesión requiere que modifiquemos el código existente de la siguiente manera:
// display response
private void consumeArduinosResponse(Response<List<Arduino>> response) {
// error?
if (response.getStatus() != 0) {
// display
showAlert(response.getMessages());
// cancel
cancel();
// return to UI
return;
}
// create a list of [CheckedArduino]
List<CheckedArduino> checkedArduinos = new ArrayList<>();
for (Arduino arduino : response.getBody()) {
checkedArduinos.add(new CheckedArduino(arduino, false));
}
// Set it in the session
session.setCheckedArduinos(checkedArduinos);
// display them
showArduinos(checkedArduinos);
// cancel the wait
cancelWaitingTasks();
}
- línea 18: se coloca en la sesión la lista de Arduinos creada por las líneas anteriores;
5.6.10.6. Gestión del estado de los fragmentos
Cuando se gira el dispositivo, los componentes visuales de la vista se muestran (por defecto) en el estado en que estaban cuando se diseñó la vista:
- el [ListView] contiene los elementos que el diseñador colocó allí;
- el mensaje de error está en el estado visible o no visible en el que lo colocó el diseñador;
Los estados de los componentes visuales en tiempo de diseño pueden ser apropiados o no a la hora de restaurar un fragmento. ¿Cuál es el caso aquí?
- el [ListView] debe mostrar la lista de Arduinos conectados. Por lo tanto, no se puede utilizar el valor de [ListView] en tiempo de diseño;
- El [TextView] del mensaje de error debe restaurarse al estado visible u oculto que tenía en el momento de guardar. Su valor en tiempo de diseño puede no ser adecuado para estos dos casos;
Por lo tanto, debemos guardar el estado de estos dos componentes al guardar el estado del fragmento:
- la lista de Arduinos conectados;
- la visibilidad (mostrada/oculta) del mensaje de error al entrar en el servicio web URL/JSON;
Como la lista de Arduinos está presente en la sesión, se guardará automáticamente. La visibilidad del mensaje de error se guardará en la siguiente clase [ConfigFragmentState]:
![]() |
package client.android.fragments.state;
import client.android.architecture.custom.CoreState;
public class ConfigFragmentState extends CoreState {
// error message visibility
private boolean txtMsgErrorUrlServiceRestVisible;
// getters and setters
...
}
Tarea: Crea la clase [ConfigFragmentState] mostrada arriba.
Para restablecer correctamente los estados de los fragmentos, es necesario modificar sus métodos [getNumView] y [saveFragment]. Por ejemplo, el del fragmento [BlinkFragment] es actualmente el siguiente:
@Override
public CoreState saveFragment() {
// the fragment must be saved
DummyFragmentState state = new DummyFragmentState();
// ...
return state;
// if there is nothing to save, use [return new CoreState();] and remove the [DummyFragmentState] class
}
@Override
protected int getNumView() {
// Return the fragment number in the array of fragments managed by the activity (see MainActivity)
return 0;
}
Si no se hace nada, el estado representado en la línea 6 se guardará en el elemento 0 (línea 13) del archivo CoreState[] coreStates de la clase [AbstractSession] (línea 5 más abajo):
public class AbstractSession implements ISession {
...
// view state
private CoreState[] coreStates = new CoreState[0];
...
Sin embargo, debe guardarse en el elemento correspondiente al fragmento ID [BlinkFragment] en la matriz de fragmentos definida en la clase [MainActivity] (línea 9 más abajo):
@EActivity
@OptionsMenu(R.menu.menu_main)
public class MainActivity extends AbstractActivity {
...
@Override
protected AbstractFragment[] getFragments() {
return new AbstractFragment[]{new ConfigFragment_(), new BlinkFragment_(), new PinReadFragment_(), new PinWriteFragment_(), new CommandsFragment_()};
}
El fragmento IDs se ha definido en la interfaz [IMainActivity]:
public interface IMainActivity extends IDao {
...
// view numbers
int VIEW_CONFIG = 0;
int VIEW_BLINK = 1;
int VIEW_PINREAD = 2;
int VIEW_PINWRITE = 3;
int VIEW_COMMANDS = 4;
}
En definitiva, el estado del fragmento [BlinkFragment] se gestionará correctamente si escribimos:
@Override
public CoreState saveFragment() {
// the fragment must be saved
DummyFragmentState state = new DummyFragmentState();
// ...
return state;
// if there is nothing to save, use [return new CoreState();] and remove the [DummyFragmentState] class
}
@Override
protected int getNumView() {
// Return the fragment ID in the array of fragments managed by the activity (see MainActivity)
return IMainActivity.VUE_BLINK;
}
- Línea 14: Devuelve el fragmento ID [BlinkFragment] en el array de fragmentos gestionados por la actividad;
Además, la clase [CoreState], que es el padre de los estados de fragmento, es actualmente la siguiente (véase la sección 5.6.7.2):
package client.android.architecture.custom;
import client.android.architecture.core.MenuItemState;
import client.android.fragments.state.*;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = ConfigFragmentState.class),
@JsonSubTypes.Type(value = BlinkFragmentState.class),
@JsonSubTypes.Type(value = PinReadFragmentState.class),
@JsonSubTypes.Type(value = PinWriteFragmentState.class),
@JsonSubTypes.Type(value = CommandsFragmentState.class)}
)
public class CoreState {
// fragment visited or not
protected boolean hasBeenVisited = false;
// state of the fragment's menu (if any)
protected MenuItemState[] menuOptionsState;
// getters and setters
....
}
- Líneas 12-16: La clase [DummyFragmentState] no aparece entre las clases hijas de la clase [CoreState]. Sin embargo, el método [saveFragment] de la clase [BlinkFragment] devuelve actualmente un valor tipo [DummyFragmentState]. Si se deja como está, la serialización/deserialización de la sesión fallará y la sesión no se restaurará, provocando un fallo de la aplicación;
El método [saveFragment] del fragmento [BlinkFragment] debe reescribirse como sigue:
@Override
public CoreState saveFragment() {
// the fragment must be saved
BlinkFragmentState state = new BlinkFragmentState();
// ...
return state;
// if there is nothing to save, do [return new CoreState();] and remove the [DummyFragmentState] class
}
Tarea: En cada fragmento, modifica el método [getNumView] para que devuelva el número de fragmento, y el método [saveFragment] para que devuelva una instancia de la clase de estado del fragmento (como se muestra arriba).
5.6.10.7. Gestión del ciclo de vida de los fragmentos
Aquí nos centramos en el ciclo de vida del fragmento [ConfigFragment], concretamente en los cuatro métodos:
- [saveFragment]: debe guardar el estado del fragmento para poder restaurarlo más tarde;
- [initFragment]: que debe inicializar ciertos campos del fragmento si es necesario. Este método se llama cuando se inicia la aplicación y cada vez que se gira el dispositivo. Más concretamente, se llama cuando el fragmento se hace visible tras uno de los dos eventos anteriores;
- [initView]: que debe inicializar ciertos componentes de la vista si es necesario. Se llama a este método cada vez que se ha llamado a [initFragment] y cuando hay que redibujar la vista porque el fragmento se ha movido, en algún momento, fuera de las proximidades del fragmento visualizado. Como antes, se llama cuando el fragmento se hace visible después de uno de estos eventos;
- [updateOnRestore]: que se ejecuta después de los dos métodos anteriores cuando se ha girado el dispositivo, pero también cuando se ha producido la navegación. Su función es restablecer el estado anterior del fragmento;
Estos métodos serán los siguientes:
// Arduino list adapter
private ListArduinosAdapter adapterListArduinos;
...
// fragment lifecycle management -------------------------------------
@Override
public CoreState saveFragment() {
ConfigFragmentState state = new ConfigFragmentState();
state.setTxtMsgErreurUrlServiceRestVisible(txtMsgErreurUrlServiceRest.getVisibility() == View.VISIBLE);
return state;
}
@Override
protected void initFragment(CoreState previousState) {
// listArduinos adapter
adapterListArduinos = new ListArduinosAdapter(activity, R.layout.listarduinos_item, session.getCheckedArduinos(), false);
}
@Override
protected void initView(CoreState previousState) {
// Link listview to adapter
listArduinos.setAdapter(adapterListArduinos);
// First visit?
if (previousState == null) {
// Empty ListView - created by [initFragment]
// hide error message
txtMsgErreurUrlServiceRest.setVisibility(View.INVISIBLE);
} else {
// Restore visibility of the error message
ConfigFragmentState state = (ConfigFragmentState) previousState;
txtMsgErreurUrlServiceRest.setVisibility(state.isTxtMsgErreurUrlServiceRestVisible() ? View.VISIBLE : View.INVISIBLE);
}
}
@Override
protected void updateOnSubmit(CoreState previousState) {
}
@Override
protected void updateOnRestore(CoreState previousState) {
}
@Override
protected void notifyEndOfUpdates() {
// buttons
initButtons();
}
- línea 2: el ListView para los Arduinos. Es una variable global porque se utiliza en diferentes métodos;
- líneas 7-12: el método [saveFragment] guarda la visibilidad de el TextView txtMsgErreurUrlServiceRestVisible (línea 10) a un tipo [ConfigFragmentState];
- líneas 14-19: el método [initFragment] inicializa el adaptador de la línea 2 con la lista de Arduinos actualmente en la sesión (línea 17). Ten en cuenta que el papel de [initFragment] es inicializar los campos del fragmento. Aquí, esta inicialización debe realizarse en todos los casos, tanto si se trata de la primera visita (previousState == null) o no;
- línea 17: vemos que el adaptador está vinculado a la fuente de datos [session.getCheckedArduinos]. Este no debe tener el valor null. Por esta razón, el campo [session.checkedArduinos] se inicializa con una lista vacía en la sesión:
// the list of Arduinos
private List<CheckedArduino> checkedArduinos = new ArrayList<>();
- líneas 21-35: el método [initView] se encarga de inicializar ciertos componentes de la interfaz visual, en particular aquellos cuyos valores no se conservan al rotar el dispositivo;
- línea 24: el Arduino ListView está vinculado al adaptador de la línea 2;
- líneas 28-32: la primera visita se distingue de las demás;
- línea 29: en la primera visita, debe mostrarse un [ListView] vacío. Esto es así porque, en la primera visita, el adaptador [ListView] estaba asociado a una lista vacía (línea 17);
- línea 31: el mensaje de error está oculto;
- líneas 32-36: el caso de que no sea la primera visita;
- el [ListView] ya está en el estado correcto desde la línea 24. No hay nada más que hacer;
- líneas 34-35: el mensaje de error se restaura al estado en que se encontraba la última vez que se guardó el fragmento;
- líneas 31-36: el método [updateOnRestore] debe devolver el fragmento a su estado inicial. Llegamos al método [updateOnRestore] de dos maneras:
- ya sea porque se ha girado el dispositivo. En este caso, todas las inicializaciones necesarias ya se han realizado en [initView];
- ya sea porque estamos navegando desde una pestaña a la pestaña [Config]. Si el fragmento [Config] ha salido de las inmediaciones de los fragmentos mostrados desde que lo abandonamos, el método [initView] ya se ha ejecutado y el fragmento ya se encuentra en el estado deseado. Si el fragmento [Config] no ha abandonado la lista de fragmentos visualizados desde que salimos de ella, sus componentes visuales no han cambiado de estado y no hay nada que hacer;
Vemos que el método [updateOnRestore] no tiene nada que hacer. A veces es así, a veces no. La diferencia viene del método [updateOnSubmit]: si este método hace algo que hace innecesarias ciertas inicializaciones hechas en [initView], entonces esas inicializaciones deberían hacerse en el método [updateOnRestore]. Tomemos el ejemplo de un botón de radio con tres valores: V1, V2 y V3. Quizás en el caso de una navegación asociada a una acción [SUBMIT], el botón de opción seleccionado debe ser siempre el que tiene el valor V1. En este caso, restablecer el valor del botón de opción en el método [initView] es innecesario, ya que en el caso de una acción [SUBMIT], este valor será sustituido por el proporcionado por el método [updateOnSubmit]. Por tanto, es preferible trasladar esta restauración al método [updateOnRestore] para evitar realizar una operación innecesaria.
- líneas 48-52: el método [notifyEndOfUpdates] se ejecuta después de todos los anteriores;
- Línea 51: Los botones vuelven a su estado inicial: se muestra el botón [Actualizar] y se oculta el botón [Cancelar]:
Tarea: Añade el código anterior a [ConfigFragment] y ejecuta la aplicación. Observa que cuando giras el dispositivo, la pestaña [Config] mantiene su estado (mensaje de error, lista de Arduinos). Comprueba que el mismo comportamiento se produce cuando simplemente navegas desde la pestaña [Config] a la pestaña [Comandos] --> pestaña [Config]. En este último caso, si ha establecido la adyacencia de fragmentos en 1 en [IMainActivity], la vista [ConfigFragment] se destruye al cambiar a la pestaña [Comandos] y se vuelve a crear al volver a la pestaña [Config]. Durante las pruebas, examine los registros.
5.6.10.8. Mejora del código
El código del fragmento [ConfigFragment] puede mejorarse. Por ejemplo, escribimos:
// Arduino list adapter
private ListArduinosAdapter adapterListArduinos;
...
// display Arduino list
private void showArduinos(List<CheckedArduino> checkedArduinos) {
// display the Arduinos
ListArduinosAdapter adapter = new ListArduinosAdapter(getActivity(), R.layout.listarduinos_item, checkedArduinos, false);
listArduinos.setAdapter(adapter);
}
// Clear the list of Arduinos
private void clearArduinos() {
// display an empty list
ListArduinosAdapter adapter = new ListArduinosAdapter(getActivity(), R.layout.listarduinos_item, new ArrayList<CheckedArduino>(), false);
listArduinos.setAdapter(adapter);
}
- Podemos ver que en las líneas 9 y 16, estamos utilizando una variable local que está desconectada del campo de la línea 2, a pesar de que estamos tratando de manipular la misma entidad;
Actualizamos el código como sigue:
// adapter for the list of Arduinos
private ListArduinosAdapter adapterListArduinos;
@Click(R.id.btn_Rafraichir)
protected void doRefresh() {
...
}
private void getArduinosInBackground() {
...
// process it
consumeArduinosResponse(response);
}
// display response
private void consumeArduinosResponse(Response<List<Arduino>> response) {
// error?
if (response.getStatus() != 0) {
// display
showAlert(response.getMessages());
// cancel
doCancel();
// return to UI
return;
}
// create a list of [CheckedArduino]
List<CheckedArduino> checkedArduinos = session.getCheckedArduinos();
checkedArduinos.clear();
for (Arduino arduino : response.getBody()) {
checkedArduinos.add(new CheckedArduino(arduino, false));
}
// display them
adapterListArduinos.notifyDataSetChanged();
// Cancel the wait
cancelWaitingTasks();
}
@Override
protected void initFragment(CoreState previousState) {
// listArduinos adapter
listArduinosAdapter = new ListArduinosAdapter(activity, R.layout.listarduinos_item, session.getCheckedArduinos(), false);
}
@Override
protected void initView(CoreState previousState) {
// bind listview / adapter
listArduinos.setAdapter(adapterListArduinos);
...
}
- Cuando se ejecuta el método de la línea 5, el ciclo de vida del fragmento se ha completado. Por lo tanto:
- el adaptador de la línea 2 se ha asociado a su fuente de datos (línea 41);
- el [ListView] de los Arduinos conectados se ha vinculado a este adaptador (línea 48);
Cuando queremos cambiar la visualización del [ListView], tenemos que hacer dos cosas:
- cambiar el contenido de la fuente de datos [session.checkedArduinos];
- notificar al adaptador este cambio mediante la instrucción [adapterListArduinos.notifyDataSetChanged()];
Es importante cambiar el contenido de la fuente de datos, no la fuente de datos en sí. Si cambiamos la fuente de datos en sí, la operación [adapterListArduinos.notifyDataSetChanged()] seguirá mostrando la fuente de datos antigua. Tendríamos entonces que asociar el adaptador con la nueva fuente de datos.
El código es el siguiente:
- línea 27: recuperamos la fuente de datos;
- línea 28: lo borramos. Por esta razón, hemos eliminado el método [clearArduinos];
- líneas 29-31: añadimos nuevos elementos a esta lista ahora vacía;
- línea 33: le decimos al adaptador que refresque. Esto refrescará la pantalla del [ListView] asociado;
Tarea: Realice estos cambios y compruebe que su aplicación sigue funcionando.
5.6.11. Comunicación entre puntos de vista
Para verificar la comunicación entre vistas, haremos que todas las demás vistas muestren la lista de Arduinos obtenida por la vista [Config]. Empecemos por la vista [blink.xml]. Mientras que antes no mostraba nada, ahora mostrará la lista de Arduinos conectados:

![]() |
El código XML para la vista [blink.xml] será el siguiente:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txt_arduinos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginTop="150dp"
android:text="@string/arduinos_list_title"
android:textColor="@color/blue"
android:textSize="20sp" />
<ListView
android:id="@+id/ListViewArduinos"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_arduinos"
android:layout_marginTop="30dp"
android:background="@color/wheat">
</ListView>
</RelativeLayout>
</ScrollView>
Este código fue tomado directamente de la vista [config.xml]. Simplemente modificamos el margen superior en la línea 19.
Tarea: Duplique este código en las vistas [commands.xml, pinread.xml, pinwrite.xml].
También cambia el código del fragmento [BlinkFragment] asociado a la vista [blink.xml]:
![]() |
// visual components
@ViewById(R.id.ListViewArduinos)
protected ListView listArduinos;
// Arduino list adapter
private ListArduinosAdapter adapterListArduinos;
...
// methods required by the parent class -------------------------------------------------------
...
@Override
protected void initFragment(CoreState previousState) {
// listArduinos adapter
adapterListArduinos = new ListArduinosAdapter(activity, R.layout.listarduinos_item, session.getCheckedArduinos(), true);
}
@Override
protected void initView(CoreState previousState) {
// Link listview / adapter
listArduinos.setAdapter(adapterListArduinos);
}
...
- líneas 2-3: el componente [ListView] para los Arduinos conectados;
- línea 6: el adaptador para este [ListView];
- líneas 12-23: el código de los métodos [initFragment] y [initView] es el mismo que el ya utilizado para el fragmento [ConfigFragment];
- línea 15: cuando el fragmento necesita ser reiniciado, reiniciamos el adaptador de la línea 2 asociándolo a la lista de Arduinos almacenada en la sesión. El último parámetro [true] del constructor [ListArduinosAdapter] significa que queremos ver una casilla de verificación junto a cada Arduino;
- línea 22: cuando la vista del fragmento necesita ser reiniciada, enlazamos el [ListView] de los Arduinos conectados al adaptador de la línea 6;
Tarea: Duplique este código en los otros fragmentos [CommandsFragment, PinReadFragment, PinWriteFragment]. Ejecute la aplicación y verifique que cada pestaña muestra ahora la lista de Arduino conectados . Compruebe también que si marca los Arduinos en una pestaña y navega a otra pestaña, permanecen marcados en esta última.
Nota: La razón por la que los Arduinos permanecen controlados es la siguiente. La clase [ListArduinosAdapter] se introdujo en la Sección 5.6.10.4. El código relacionado con la casilla de verificación es el siguiente:
// the current Arduino
final CheckedArduino arduino = arduinos.get(position);
...
// the CheckBox is not always visible
CheckBox ck = (CheckBox) row.findViewById(R.id.checkBoxArduino);
ck.setVisibility(selectable ? View.VISIBLE : View.INVISIBLE);
if (selectable) {
// set its value
ck.setChecked(arduino.isChecked());
// handle the click
ck.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
arduino.setChecked(isChecked);
}
});
}
- Líneas 11-15: Si se marca una casilla de verificación en la pestaña X, la propiedad [checked] del Arduino en la fila 2 se establece en verdadero (línea 14);
- al cambiar a la pestaña Y, se muestra el [ListView] de los Arduinos en esa pestaña. En la línea 9, vemos que si el Arduino de la fila 2 tiene su propiedad [checked] establecida a verdadero, se marcará la casilla [ck] de la fila 5;
5.6.12. La capa [DAO]
![]() |
Nota: Para esta sección, revise la implementación de la capa [DAO] en el proyecto [example-16B] (consulte la sección 2.8.3).
Hasta ahora, hemos generado manualmente la lista de Arduinos conectados. Ahora la solicitaremos al servidor web / jSON. Para ello, vamos a construir la capa [DAO]:
![]() |
5.6.12.1. La interfaz IDao
La interfaz [IDao] de la capa [DAO] será la siguiente:
package client.android.dao.service;
import client.android.dao.entities.Arduino;
import client.android.dao.entities.Response;
import rx.Observable;
import java.util.List;
public interface IDao {
// Web service URL
void setWebServiceJsonUrl(String url);
// User
void setUser(String user, String password);
// Client timeout
void setTimeout(int timeout);
// Basic authentication
void setBasicAuthentication(boolean isBasicAuthenticationNeeded);
// debug mode
void setDebugMode(boolean isDebugEnabled);
// client wait time in milliseconds before request
void setDelay(int delay);
// specific ----------------------------------------
// list of Arduinos
Observable<Response<List<Arduino>>> getArduinos();
}
- líneas 11-26: estas líneas ya están presentes en la interfaz [IDao] del proyecto de plantilla [client-android-skel];
- línea 30: el método [getArduinos] devuelve la lista de Arduinos conectados como un observable de tipo Observable<[Response<List<Arduino><>>] ;
Tenga en cuenta que [Response<T>] es el tipo de todas las respuestas enviadas por el servidor en forma de cadena JSON:
package client.android.dao.entities;
import java.util.List;
public class Response<T> {
// ----------------- properties
// operation status
private int status;
// any error messages
private List<String> messages;
// response body
private T body;
// constructors
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// getters and setters
...
}
5.6.12.2. La interfaz [WebClient]
![]() |
La interfaz [WebClient] es una interfaz para la que la biblioteca AA proporciona una implementación. Esta interfaz será la siguiente:
package client.android.dao.service;
import client.android.dao.entities.Arduino;
import client.android.dao.entities.Response;
import org.androidannotations.rest.spring.annotations.Get;
import org.androidannotations.rest.spring.annotations.Path;
import org.androidannotations.rest.spring.annotations.Rest;
import org.androidannotations.rest.spring.api.RestClientRootUrl;
import org.androidannotations.rest.spring.api.RestClientSupport;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Rest(converters = {MappingJackson2HttpMessageConverter.class})
public interface WebClient extends RestClientRootUrl, RestClientSupport {
// RestTemplate
void setRestTemplate(RestTemplate restTemplate);
// specific --------------------------------------
// list of Arduinos
@Get("/arduinos")
Response<List<Arduino>> getArduinos();
}
- líneas 15-19: estas líneas se incluyen por defecto en la interfaz [WebClient] del proyecto de plantilla [client-android-skel];
- línea 23: el servidor URL utilizado para recuperar la lista de Arduinos a través de una petición GET. Tenga en cuenta que este URL es relativo a la raíz URL [RestClientRootUrl] en la línea 16;
- línea 24: el servidor devuelve una cadena JSON de tipo [Response<List<Arduino>>]. Esta cadena JSON se deserializa automáticamente al tipo [Response<List<Arduino>>] utilizando el conversor JSON [MappingJackson2HttpMessageConverter] de la línea 15;
5.6.12.3. La clase [Dao]
La clase [Dao] implementa la interfaz [IDao] de la siguiente manera:
package client.android.dao.service;
import android.util.Log;
import client.android.dao.entities.Arduino;
import client.android.dao.entities.Response;
import org.androidannotations.annotations.AfterInject;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean;
import org.androidannotations.rest.spring.annotations.RestService;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import rx.Observable;
import java.util.ArrayList;
import java.util.List;
@EBean(scope = EBean.Scope.Singleton)
public class Dao extends AbstractDao implements IDao {
// Web service client
@RestService
protected WebClient webClient;
// security
@Bean
protected MyAuthInterceptor authInterceptor;
// the RestTemplate
private RestTemplate restTemplate;
// RestTemplate factory
private SimpleClientHttpRequestFactory factory;
@AfterInject
public void afterInject() {
// log
Log.d(className, "afterInject");
// create the RestTemplate
factory = new SimpleClientHttpRequestFactory();
restTemplate = new RestTemplate(factory);
// Set the JSON converter
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
// Set the RestTemplate for the web client
webClient.setRestTemplate(restTemplate);
}
@Override
public void setUrlServiceWebJson(String url) {
// Set the web service URL
webClient.setRootUrl(url);
}
@Override
public void setUser(String user, String password) {
// Register the user in the interceptor
authInterceptor.setUser(user, password);
}
@Override
public void setTimeout(int timeout) {
if (isDebugEnabled) {
Log.d(className, String.format("setTimeout thread=%s, timeout=%s", Thread.currentThread().getName(), timeout));
}
// configuration factory
factory.setReadTimeout(timeout);
factory.setConnectTimeout(timeout);
}
@Override
public void setBasicAuthentication(boolean isBasicAuthenticationNeeded) {
if (isDebugEnabled) {
Log.d(className, String.format("setBasicAuthentication thread=%s, isBasicAuthenticationNeeded=%s", Thread.currentThread().getName(), isBasicAuthenticationNeeded));
}
// authentication interceptor?
if (isBasicAuthenticationNeeded) {
// add the authentication interceptor
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(authInterceptor);
restTemplate.setInterceptors(interceptors);
}
}
// private methods -------------------------------------------------
private void log(String message) {
if (isDebugEnabled) {
Log.d(className, message);
}
}
// Specific IDao implementation -----------------------------------------------
@Override
public Observable<Response<List<Arduino>>> getArduinos() {
// web client execution
return getResponse(new IRequest<Response<List<Arduino>>>() {
@Override
public Response<List<Arduino>> getResponse() {
return webClient.getArduinos();
}
});
}
}
- líneas 19-87: estas líneas son parte de la clase [Dao] en el proyecto [client-android-skel];
- líneas 91-100: implementación del método [getArduinos];
- línea 94: se llama al método [getResponse] de la clase padre. El único parámetro de este método es una instancia de la interfaz [IRequest<T>];
- líneas 95-99: el único método de la interfaz [IRequest<T>] es el método [T getResponse()];
- línea 94: el tipo T de [IRequest<T>] debe ser el tipo T de la variable Observable<T> resultado del método en la línea 92, así que aquí, un tipo [Response<List<Arduino>>];
- línea 97: el método [IRequest.getResponse()] delega el trabajo al método [webClient.getArduinos()] que hemos introducido. [webClient], definido en la línea 24, es instanciado por la librería AA y es una instancia de la interfaz [WebClient] que hemos introducido;
5.6.13. El [MainActivity]
![]() |
Ya hemos presentado la actividad [MainActivity] en la sección 5.6.8. Amplía la clase [AbstractActivity] y, como tal, implementa la interfaz [IMainActivity], que a su vez amplía la interfaz [IDao]. Siempre que se añada un método a la interfaz [IDao], deberá implementarse en la clase [MainActivity]. El método [IDao.getArduinos] añadido a la interfaz [IDao] se implementará como sigue en [MainActivity]:
...
@EActivity
@OptionsMenu(R.menu.menu_main)
public class MainActivity extends AbstractActivity {
// [DAO] layer
@Bean(Dao.class)
protected IDao dao;
// session
private Session session;
...
// IDao implementation -----------------------------------------
@Override
public Observable<Response<List<Arduino>>> getArduinos() {
return dao.getArduinos();
}
}
- líneas 15-18: el método [getArduinos] se implementa delegando el trabajo a la clase [Dao] que acabamos de introducir y a la que tenemos una referencia en la línea 8;
5.6.14. El fragmento [ConfigFragment] revisitado
En la clase [ConfigFragment], el código que se ejecuta cuando se hace clic en el botón [Actualizar] es actualmente el siguiente:
@Click(R.id.btn_Rafraichir)
protected void doRefresh() {
...
// request the list of Arduinos in the background
getArduinosInBackground();
}
private void getArduinosInBackground() {
// Create a dummy list of Arduinos
List<Arduino> arduinos = new ArrayList<>();
for (int i = 0; i < 20; i++) {
arduinos.add(new Arduino("id" + i, "desc" + i, "mac" + i, "ip" + i, i));
}
// Simulate a server response
Response<List<Arduino>> response = new Response<>();
response.setBody(arduinos);
// we consume it
consumeArduinosResponse(response);
}
// display response
private void consumeArduinosResponse(Response<List<Arduino>> response) {
...
}
Tenemos que reescribir las líneas 10-16, que hard-coded una respuesta de tipo [Response<List<Arduino>>]. Ahora tenemos que solicitar esta lista de la capa [DAO] a través de la actividad. El código queda como sigue:
@Click(R.id.btn_Rafraichir)
protected void doRefresh() {
// check the inputs
if (!pageValid()) {
return;
}
// save the input
mainActivity.setUrlServiceWebJson(urlServiceRest);
// prepare to wait
beginWaiting(1);
// execute the asynchronous task
executeInBackground(mainActivity.getArduinos(), new Action1<Response<List<Arduino>>>() {
@Override
public void call(Response<List<Arduino>> response) {
// process the response
consumeArduinosResponse(response);
}
});
}
- línea 8: el URL raíz del servicio web / JSON introducido por el usuario se pasa a la capa [DAO] a través de la actividad. Este será el URL raíz de la interfaz [WebClient] (véase la sección 5.6.12.2);
- línea 10: se notifica a la clase padre que se va a lanzar una tarea asíncrona;
- líneas 12-19: lanzamiento de la tarea asíncrona que devolverá la lista de Arduinos conectados al servidor;
- línea 12: llamar al método [executeInBackground] de la clase padre. Este método espera dos parámetros:
- línea 12: el proceso a observar. Este proceso es proporcionado aquí por el método [mainActivity.getArduinos()];
- líneas 12-19: una instancia de la interfaz [Action1<T>], donde el tipo T es el tipo proporcionado por el proceso, aquí un tipo [Response<List<Arduino>>];
- líneas 14-18: el método llamado cuando la tarea asíncrona devuelve su resultado de tipo [Response<List<Arduino>>];
- línea 17: la respuesta recibida se pasa al método [consumeArduinosResponse] escrito anteriormente;
Tarea: Inicie el servidor como se describe en la sección 5.4. Conecte uno o más Arduinos al PC en el que se ejecuta el servidor. A continuación, ejecute el cliente Android y compruebe que puede recuperar correctamente la lista de Arduinos conectados. Observe los registros.

- Introduzca el URL indicado en [1]. Esta es una de las direcciones IP de su servidor;
- Haz clic en el botón [2];
- Deberías ver la lista de Arduinos conectados en [3];
Compruebe que esta lista también aparece en las demás pestañas.
5.7. Próximos pasos
Siguiendo el mismo procedimiento utilizado para la vista [Config], implemente y, a continuación, pruebe las otras cuatro vistas de la aplicación a su vez: [Parpadeo], [PinRead], [PinWrite] y [Comandos].
Las vistas que deben crearse se presentaron en la sección 5.5.
For each view, you must:
- dibujar la vista XML (ver Sección 5.6.9);
- construir el fragmento asociado (véase la sección 5.6.10);
- añadir un método a la interfaz [WebClient] (véase la sección 5.6.12.2);
- añadir un método a la interfaz [IDao] (véase la sección 5.6.12.2);
- añadir un método a la clase [Dao] (ver sección 5.6.12.3);
- añadir un método a la actividad [MainActivity] (véase la sección 5.6.13);
- escribir los controladores de eventos del fragmento (véase la sección 5.6.14);
- prueba y observa los registros;
Nota 1: El ejemplo a seguir es el proyecto [Ejemplo-16B] del curso (véase la sección 2.8.3).
Nota 2: Los URLs a consultar y el tipo de sus respuestas se presentaron en la sección 5.4.2.
Nota 3:
La clase [CommandsFragment] envía una lista que contiene un único comando para ser ejecutado por uno o más Arduinos. Este comando se encapsulará en el siguiente [ArduinoCommand] clase:
package android.arduinos.dao;
import java.util.Map;
public class ArduinoCommand {
// data
private String id;
private String ac;
private Map<String, Object> pa;
// constructors
public ArduinoCommand() {
}
public ArduinoCommand(String id, String ac, Map<String, Object> pa) {
this.id = id;
this.ac = ac;
this.pa = pa;
}
// getters and setters
...
}
En la interfaz [WebClient], el método para ejecutar esta lista de comandos será el siguiente:
// sending JSON commands
@Post("/arduinos/commands/{idArduino}")
Response<List<ArduinoResponse>> sendCommands(@Body List<ArduinoCommand> commands, @Path String idArduino);
- línea 2: el URL se solicita con una petición HTTP POST;
- línea 3: el valor publicado debe tener la anotación [@Body];
Nota 4: Se recomienda abordar esta tarea del siguiente modo:
- sólo pasará a la siguiente vista una vez que la vista actual haya sido creada y probada;
- sólo gestione el estado de las vistas después de obtener una aplicación funcional en condiciones normales. A continuación, para cada vista, realice un ciclo a través del dispositivo para diferentes estados de la vista y anote cualquier información perdida. Estos son los datos que hay que guardar y luego restaurar. A continuación, verifique la navegación: cuando abandone una pestaña y vuelva a ella más tarde, debe estar en el mismo estado que cuando la dejó;



















































