Antes de empezar
Lo ideal, antes de comenzar a programar es crear un diagrama en el que estén claros los pasas que vamos a seguir de cara a diseñar nuestro Tracker del Sistema Solar. Hemos tratado de recopilar esa información general a través del siguiente diagrama:
Es importante que recuerdes este esquema porque es el mismo que seguiremos cuando realices tu tracker de un sistema exoplanetario.
Primeros pasos: Cargar librerías del proyecto y conexión a internet
Lo primero que tenemos que tener en cuenta es que, cuando programamos un Arduino, estamos utilizando el lenguaje de programación c++.
En primer lugar debemos cargar las librerías necesarias tanto para conectarse a la red como a crear las estructuras JSON. Esas dos librerías son WiFiNINA y Arduino_JSON. Estas dos librerías nos permitirán crear una conexión a Wi-Fi, realizar una solicitud GET y analizar el JSON entrante. Por otro lado necesitaremos crear el objeto portador y cargaremos otra librería para manejar los botones y navegar por el display.
Una vez que hemos cargado las librerías tenemos que conectarnos a nuestra red WiFi para lo que pondremos la red y la contraseña de red (nuestras credenciales) entre comillas. Posteriormente generaremos diferentes arrays, en nuestro caso seis, que usaremos para almacenar la información que nos llegue de la API.
Por último crearemos una cadena, que hemos denominado "objects" en las que almacenaremos los nombres de los diferentes objetos del sistema solar que encontraremos en la API sobre los que haremos peticiones tipo GET. Algunos de esos objetos son, Júpiter, Marte o la Tierra. Es importante tener en cuenta que dado que la API es francesa los nombres de dichos cuerpos celestes están en francés y debemos respetar esta notación para no tener problemas.
A continuación crearemos una cadena de caracteres en la que almacenaremos toda la información proporcionada por la API a través de su URL, es decir, almacenaremos la URL del servidor al que nos conectaremos.
Por últimos debemos establecer la conexión con la API creando un cliente que usaremos para realizar las peticiones GET.
Bajo estas líneas encontrarás el código escrito de acuerdo a todo lo que hemos comentado anteriormente.
// Cargamos las librerías necesarias
#include <WiFiNINA.h> // librería para conexión WiFI
#include <Arduino_JSON.h> // librería Almacenamiento JSON
#include <Arduino_MKRIoTCarrier.h>// librería botones capacitivos
MKRIoTCarrier carrier;
// Nos conectamos a la red WiFi
char ssid[] = ""; // Pon entre las comillas el nombre de tu red WiFi
char pass[] = ""; // Pon entre las comillas la contraseña de tu red WiFi
// Creamos cadenas (núm. según la info recogida en la API) sobre info objetos
String bodyName; // Es una variable String por ser una cadena de texto
String planet; // Es una variable String por ser una cadena de texto
String explorerName;// Es una variable String por ser una cadena de texto
String explorerDate;// Es una variable String por ser una cadena de texto
double gravity; // Es una variable double por ser una variable numérica
double density; // Es una variable double por ser una variable numérica
// Creamos cadena con el nombre de los diferentes objetos dados por la API
char *planets[] = {"jupiter", "io", "europe",
"callisto", "mars", "mercury",
"venus", "terre", "uranus", "neptune",
"saturne", "phoebe", "ganymede", "titan",
"pluton", "triton", "titania", "charon", "ariel",
"tethys", "protee"
};
int status = WL_IDLE_STATUS;
char server[] = "api.le-systeme-solaire.net";
// Creamos cliente que usaremos para hacer la petición GET
WiFiClient client;
Es importante notar que:
-
Podemos añadir tantas cadenas de caracteres sobre información de los objetos a los que vamos a hacer peticiones como queramos siempre que estén recogidas en las API's que utilicemos.
-
Deberemos cambiar la información recopilada en el caso de los sistemas exoplanetarios.
El setup() y el loop() de Arduino
Como ya sabemos, todos los códigos arduino tienen un setup y un loop. En primer lugar escribiremos el código para el setup.
En el setup(), realizaremos las siguientes acciones:
-
Inicializaremos la comunicación por el puerto serie.
-
Nos conectaremos a la red WiFi con los datos proporcionados anteriormente que los proporcionaremos a través de WiFi.begin(ssid, pass).
-
Por último inicializaremos el display y los botones con los que navegaremos por él.
En el loop crearemos una función llamada planetUpdate() para hacer la petición GET y recibir la información de la API.
Puedes ver el ejemplo del código bajo estas línea.
// Creamos el void setup()
void setup() {
//Inicializamos serial y esperamos a la apertura del puerto
Serial.begin(9600);
while (!Serial);
// Intentamos acceder a la conexión WiFi. Mensaje típico "Attempting to connect to SSID"
while (status != WL_CONNECTED) {
Serial.print("Intentando conectar a SSID: ");
Serial.println(ssid);
// Intentamos conectarnos a la WiFi con los datos que le hemos dado
status = WiFi.begin(ssid, pass);
// Esperamos 1s para la conexión 1s=1000ms:
delay(1000);
}
// Conexión con éxito. Imprimimos mensaje típico "Connected to wifi"
Serial.println("Conectado a red wifi");
//Ahora inicializamos los botones capacitivos y el display en el que vamos a mostrar la info
CARRIER_CASE = false; // Si queremos utilizar la carcasa de plástica deberemos marcar =true
carrier.begin();
carrier.display.setRotation(0);
}
La segunda parte del código de arduino, el loop, en este caso es básicamente un código de navegación el display. Utilizaremos los botones para mostrar diferente información asociada al cuerpo celeste en cuestión y refrescando el propio display para poder actualizar la información que muestra.
También utilizaremos uno de los botones para que, cuando sea pulsado, ejecute la función planetUpdate() que seleccionará aleatoriamente (si nos interesa) un cuerpo celeste del sistema solar y actualizará la información asociada a cada uno de los botones para referirla a él.
Dado que los botones que nosotros hemos utilizado permiten asociarles un color diferente, utilizaremos esta característica para distinguir unos de otros y tener claro que tipo de información se reflejará en pantalla cuando los pulsemos.
// Creamos el void loop()
void loop() {
delay(100);
/* Ahora inicializamos los botones de navegación del display que nos permitirán seleccionar aleatoriamente uno de los objetos de la API e indicar que tipo de información nos mostrará el display según el botón que pulsemos */
carrier.Buttons.update();
/* Botón principal. Actualiza la función planetUpdate() */
if (carrier.Button0.onTouchDown()) {
carrier.display.fillScreen(ST77XX_MAGENTA);
carrier.display.setCursor(30, 60);
carrier.display.setTextColor(ST77XX_WHITE);
carrier.display.setTextSize(2);
carrier.display.print("Searching");
delay(500);
carrier.display.print(".");
delay(500);
carrier.display.print(".");
delay(500);
carrier.display.print(".");
delay(500);
carrier.display.print(".");
planetUpdate();
/* Una vez se ha actualizado aparecen diferentes mensajes en pantalla*/
carrier.display.fillScreen(ST77XX_MAGENTA);
carrier.display.setCursor(30, 60);
carrier.display.setTextColor(ST77XX_WHITE);
carrier.display.setTextSize(2);
carrier.display.print("Actualización completa"); // Mensaje "Update complete"
carrier.display.setCursor(20, 90);
carrier.display.print("Datos de: "); // Datos del cuerpo celeste
carrier.display.setCursor(20, 130);
carrier.display.print(bodyName);
}
/* Botón 1: Nombre del objeto. En el caso de luna indica el planeta que orbita */
if (carrier.Button1.onTouchDown()) {
carrier.display.fillScreen(ST77XX_RED);
carrier.display.setCursor(20, 60);
carrier.display.setTextColor(ST77XX_WHITE);
carrier.display.setTextSize(2);
carrier.display.print(bodyName); // Se muestra el nombre del cuerpo celeste
carrier.display.setCursor(20, 90);
carrier.display.print("Es una luna de: "); // Mensaje "It is a moon of: "
carrier.display.setCursor(20, 110);
carrier.display.println(planet); // Se muestra el nombre del planeta que orbita
delay(500);
}
/* Botón 2: Información sobre su descubrimiento. Nombre y fecha */
if (carrier.Button2.onTouchDown()) {
carrier.display.fillScreen(ST77XX_GREEN);
carrier.display.setCursor(20, 60);
carrier.display.setTextColor(ST77XX_WHITE);
carrier.display.setTextSize(2);
carrier.display.print(bodyName); // Se muestra el nombre del objeto
carrier.display.setCursor(20, 90);
carrier.display.print("Descubierto por: "); // Mensaje "Discovered by:"
carrier.display.setCursor(20, 110);
carrier.display.println(explorerName); // Se muestra el nombre del descubridor
carrier.display.setCursor(20, 130);
carrier.display.print("Fecha: "); // Mensaje "Date: "
carrier.display.print(explorerDate); // Se muestra la fecha del descubrimiento
delay(500);
}
/* Botón 3: Información sobre su gravedad. */
if (carrier.Button3.onTouchDown()) {
carrier.display.fillScreen(ST77XX_BLUE);
carrier.display.setCursor(20, 60);
carrier.display.setTextColor(ST77XX_WHITE);
carrier.display.setTextSize(2);
carrier.display.print(bodyName); // Se muestra el nombre del objeto
carrier.display.setCursor(20, 90);
carrier.display.print("Su gravedad es: "); // Mensaje "Gravity is:"
carrier.display.setCursor(20, 130);
carrier.display.print(gravity); // Se muestra el valor de su gravedad
carrier.display.print(" m/s2"); // Mensaje "m/s2 " (unidad)
delay(500);
}
/* Botón 4: Información sobre su gravedad. */
if (carrier.Button4.onTouchDown()) {
carrier.display.fillScreen(ST77XX_BLACK);
carrier.display.setCursor(20, 60);
carrier.display.setTextColor(ST77XX_WHITE);
carrier.display.setTextSize(2);
carrier.display.print(bodyName); // Se muestra el nombre del objeto
carrier.display.setCursor(20, 110);
carrier.display.print("Su gravedad es:: "); // Mensaje "Density is:"
carrier.display.setCursor(20, 130);
carrier.display.print(density); // Se muestra el valor de su densidad
carrier.display.print(" g/cm3"); // Mensaje "g/cm3 " (unidad)
}
}
La función planetUpdate()
Esta función será la encargada de hacer las peticiones al servidor donde se encuentran nuestros datos, la API, y, tal como hemos visto anteriormente, será reclamada tanto al inicio del setup() como cuando se presione el botón principal.
En nuestro caso hemos optado porque el código elija aleatoriamente el cuerpo celeste del Sistema Solar sobre el que vamos a solicitar la información (esto es así debido a que la gran cantidad de cuerpos celeste que pertenecen a él dificultaría la navegación. En el caso de los sistemas exoplanetarios, no programaremos una selección aleatoria del objeto). Programaremos una selección aleatoria entre los enteros 0 y 20 (21 números posibles) porque el número de objetos que había en este array era 21.
char *objects[] = {"jupiter", "io", "europe",
"callisto", "mars", "mercury",
"venus", "terre", "uranus", "neptune",
"saturne", "phoebe", "ganymede", "titan",
"pluton", "triton", "titania", "charon", "ariel",
"tethys", "protee"
};
Debemos tener en cuenta que si introducimos más objetos deberemos aumentar el extremo superior (20) de la selección aleatoria. Por ejemplo, si introducimos 50 objetos, la selección aleatoria la haremos entre 0 y 49.
Dado que la función planetUpdate() hace la petición GET a la API deberemos asegurarnos de que tiene acceso a internet. Para ello escribiremos if(client.connect(server, 80) que nos dará la confirmación de que esa conexión está establecida y si efectivamente lo está el código comenzará a realizar las peticiones GET.
Es importante tener en cuenta que dado que tenemos diferentes tipos de cuerpos celestes, las peticiones GET deberán ser diferentes para cada uno de ellos. Así, primero solicitaremos al cliente "GET", después le diremos que los objetos están en su directorio "/rest/bodies/" y le indicaremos que objeto hemos seleccionado aleatoriamente a través client.print(planets[randomPlanet]). Debemos tener en cuenta que el número 0 de la randomización nos dará los datos de Júpiter mientra que, por ejemplo, el número 4 corresponderá a los de Marte debido a la estructura del array.
Por último usaremos el comando client.println(" HTTP/1.0"); para notificarle al servidor que información le estamos solicitando. Es importante notar que este HTTP/1.0 puede cambiar de un sistema a otro aunque en nuestro caso es así.
El comando client.println("Host: api.le-systeme-solaire.net"); indicará al cliente que host estamos solicitando exactamente y, por último, cuando ya hayamos hecho la solicitud y tengamos los datos, el comando client.println("Connection: close"); indicará qué tipo de conexión estamos usando y si esta ha finalizado.
/*La función planetUpdate(); la encargada de las peticiones */
void planetUpdate() {
int randomPlanet = random(0, 20);
/*Mensaje de comprobación de la conexión al servidor */
Serial.println("\nStarting connection to server...");
/* Si hay conexión se notificará a través del puerto serie: */
if (client.connect(server, 80)) {
Serial.println("connected to server"); /* Hay conexión */
// Hacemos las peticiones GET
client.print("GET "); // Solicita a la API
client.print("/rest/bodies/");
/* rest/bodies/ es el directorio donde están los cuerpos celestes en la API */
client.print(planets[randomPlanet]); /* Selecciona el objeto */
client.println(" HTTP/1.0");
/* HTTP/1.0 puede cambiar de un sistema a otro. Es importante tenerlo en cuenta */
/* Ahora debemos indicar la dirección del host al que hacemos las peticiones y cerrar la conexión */
client.println("Host: api.le-systeme-solaire.net");
client.println("Connection: close");
client.println();
} else {
/* En el caso de que haya sido imposible establecer la conexión, pediremos al sistema que nos imprima un mensaje indicándolo */
Serial.println("unable to connect");
}
delay(1000);
Damos formato a los datos a través de JSON
Una vez que hemos creado la petición GET es necesario manipular los datos que recopilemos de la API. Para ello crearemos una nueva cadena que hemos denominado "line" donde almacenaremos todos los datos provenientes del cliente. Como ya se mencionó anteriormente le daremos a estos datos un formato JSON y una librería que nos permita analizar los datos y recuperar de dentro de un objeto JSON.
En este sentido usaremos el comando bodyName = JSON.stringify(myObject["englishName"]); que lo que hace es localizar el objeto, extraer de él la información de tipo texto que esté escrita en inglés "englishName" (dadas las características del alumnado del centro, es más sencillo interpretar y comprender esta información) y almacenar dicha información en una nueva variable denominada "bodyName". En el caso de que el dato sea de carácter numérico, bastará con usar algo similar a esto gravity = myObject["gravity"]; donde almacenaremos, en este caso, la información sobre la gravedad de dicho objeto en la variable "gravity".
Como hemos señalado anteriormente, hemos creado seis cadenas y valores diferentes que se mostrarán en pantalla y cada uno de ellos está asociado a uno de los botones. Evidentemente, deberemos crear tantos campos diferentes en el JSON como datos queramos sacar por el display, en este caso seis.
Por último, como estamos en un bucle tipo while(), cuando recuperemos la información necesitamos romper dicho bucle. Para ello usaremos el comando if(line.startsWith("{")) { break; }.
Tenemos que recordar que cuando el JSON se almacena en la cadena "line", siempre deberá comenzar con un corchete {. Así, cuando el JSON sea reclamado, el comando anterior se dará cuenta y cerrará el bucle while() y nos llevará de regreso al lugar donde se llamó a la función en el programa, y este se “actualizará”.
/* Creamos una cadena "line" donde almacenaremos los datos */
String line = "";
/* Iniciamos el bucle while() para almacenar los datos JSON */
while (client.connected()) {
line = client.readStringUntil('\n');
Serial.println(line);
/* Iniciamos el parsing de los datos alojados en JSON */
JSONVar myObject = JSON.parse(line);
/* Aquí alojaremos la información de las características del cuerpo celeste elegido */
bodyName = JSON.stringify(myObject["englishName"]);
planet = JSON.stringify(myObject["aroundPlanet"]["planet"]);
explorerName = JSON.stringify(myObject["discoveredBy"]);
explorerDate = JSON.stringify(myObject["discoveryDate"]);
gravity = myObject["gravity"];
density = myObject["density"];
delay(100);
/* Salimos del bucle while() */
if (line.startsWith("{")) {
break;
}
}
}
Descarga el código del Tracker del Sistema Solar
Con esto ya tendríamos el código completo y preparado para subirlo a la placa. Dada la longitud del código podríamos tener algunos errores en él. Los más comunes serían
Podemos comprobar estos errores a través del propio editor que tiene arduino online y descargar el código operativo a través del botón que aparece bajo estas líneas.
La práctica anteriormente referida y el código que aparece en la web ha sido realizado por alumnado de 4ºESO del centro (se trabajó en grupos de 3 con el alumnado de 4ºESO) tomando como base algunos tutoriales mostrados anteriormente y los ejemplos facilitados a través de la web oficial de arduino.