Actualizado: 6 enero 2023
Este artículo sobre IoT te enseñará cómo controlar un chip wifi desde un teléfono móvil usando el protocolo MQTT.
Vamos a crear una app móvil que saca una foto en un dispositivo remoto y la presenta en la pantalla del móvil; pero la puedes modificar para hacer otras cosas (como por ejemplo, encender una luz, abrir una puerta, controlar un robot, responder a la alerta de un sensor etc).
El dispositivo remoto será una placa AI Thinker con una cámara OV2640.
El ejemplo es una aplicación simple de Android creada en Java (aunque se puede conseguir el mismo resultado con otros lenguajes y plataformas).
Ten en cuenta que ésta es una demostración minimalista. Hay muchas variantes y optimizaciones que se pueden aplicar a este proyecto.
(incluye enlaces pagados)
El proceso se trata de 2 topics (mira este artículo para saber como montar esto). El chip ESP32 Cam suscribe al tópico sacar foto y el móvil suscribe al tópico foto tomada para recibir las notificaciones desde el chip.
El ESP32 envia las fotos al servidor FTP y el móvil las descarga desde allí. El ESP32 necesita permisos de lectura/escritura. El móvil no necesita permisos de FTP para este ejemplo sencillo; descargará las fotos por HTTP.
El servidor de tiempo es opcional pero ideal. Proporciona un timestamp la fecha y hora al ESP32 con el fin generar un nombre de fichero único y significante para cada foto (recuerda que, a diferencia de tu PC o Mac, el ESP32 no tiene ni idea de que día es, ni la hora). Incluir la fecha y hora en los nombres de ficheros también lo hace posible ordenar y mostrar tus fotos en orden cronológico.
La configuración del broker MQTT se explica aquí.
El chip ESP32 necesita permisos de escritura; el móvil no los necesita en este ejemplo porque descargará las fotos por HTTP.
Conecta el adaptador USB/TTL a tu PC mediante un cable alargador USB tipo A (también podrías dejar el adaptor colgado directamente desde un puerto del PC pero esto no es tan conveniente y restringía bastante la vista de la cámara); luego conecta el adaptador USB/TTL a la placa ESP32.
A continuación el mapeo pin-a-pin:
Adaptador USB/TTL | Placa AI Thinker/ESP32-CAM |
---|---|
5v | 5v |
GND | GND |
TX | U0R |
RX | U0T |
Io0 - GND * |
* Es necesario hacer cortacircuito entre pin GPIO 0 y GNDmasa (ground para los estadounidenses, earth para los británicos) durante la subida (no cuando tu código se está ejecutando). También puedes conectar GPIO 0 al pin GND libre del adaptador USB/TTL si te va mejor.
IMPORTANTE - Comprueba las etiquetas de tu adaptador USB/TTL. El orden no es universal y podría ser tranquilamente diferente al modelo que se ve aquí. Asegúrate de identificar 5v, GND, RX y TX correctamente.
Ahora deberías tener algo como esto (fíjate en el hilo blanco haciendo cortocircuito entre GPIO0 y GND):
Arranca el IDE de Arduino, crea un programallamado sketch en inglés nuevo, y añade el código. Lo repasaremos paso a paso (el código completo se ve más abajo en esta página):
Incluye las librerías siguientes. Si
falta alguna, simplemente haz una
búsqueda en Tools >
Manage Libraries
y hacer clic en
Install
.
En el caso de que la librería no esté
disponible en el gestor de librerías,
haz una búsqueda por internet, descarga
el zip, descomprimirlo en tu carpeta
de libraries de Arduino y reininiar
el IDE de Arduino.
#include "esp_camera.h" #include "soc/soc.h" // Necesario para gestionar bajones de luz (que vamos a deshabilitar) #include "soc/rtc_cntl_reg.h" // Necesario para gestionar bajones de luz (que vamos a deshabilitar) #include "driver/rtc_io.h" #include <WiFi.h> #include "ESP32_FTPClient.h" #include <WiFiClientSecure.h> #include <PubSubClient.h> // MQTT lib de knolleary (https://github.com/knolleary/pubsubclient) #include <NTPClient.h> #include <WiFiUdp.h> #include "time.h" #include "ArduinoJson.h" // JSON lib de Benoit Blanchon (https://www.arduinolibraries.info/libraries/arduino-json)
Añade los parámetros de conexión de tu servidor FTP y la ruta a la carpeta que usaremos para subir las fotos.
/* Parametros conexión FTP */ char* FTP_URL = "ftp.tu.servidorftp"; char* FTP_USER = "tu@usuarioftp"; char* FTP_PWD = "tu contraseña FTP"; char* FTP_PATH = "/ruta/a/carpeta/de/fotos/"; /* Parámetros conexión HTTP (para suscritores puedan descargar las imagenes) */ char* HTTP_PATH = "https://URL/and/path/to/photos/folder/";
Añade los parámetros del broker MQTT incluidos los nombres de tópico y un prefijo para el identificador de conexión. Este ejemplo usa Hive MQTT (echa un vistazo a este artículo si quieres saber montar esto) pero puedes usar otro broker si prefieres.
/* Parámetros conexión Hive MQTT*/ const char* MQTT_SERVER = "xxxxxxxxxxxxxxxxxxxxxx.s1.eu.hivemq.cloud"; const char* MQTT_USERNAME = "Tu usuario HIVE MQTT"; const char* MQTT_PWD = "Tu contraseña HIVE MQTT"; const int MQTT_PORT = 8883; const char* MQTT_PUB_TOPIC = "yourtopics/photoTaken"; // Publicaremos notificaciones a este tópico cada vez saquemos un foto const char* MQTT_SUB_TOPIC = "yourtopics/takePhoto"; // Recibiremos los comandos de sacar una foto desde este tópico const char* MQTT_CLIENT_ID_PREFIX = "el.prefijo.que quieras."; const int QOS = 1;
Añade las llaves que usaremos en la payload (carga útil) de JSON en nuestros mensajes MQTT y un identificador único para nuestro ESP32 Cam. Éstos son opcionales pero nos permitirán usar multiples cámaras y móviles. Ten en cuenta que tendrás que hardcodear el valor SENSOR_ID único para cada ESP32 que montes. Dale un nombre significativo ("cocina", "jardin", "gatera" etc). En este ejemplo se llama "musicRoom" ("sala de música").
/* Llaves JSON */ const char* SENSOR_ID_KEY = "sensorId"; // The key in the JSON payload for the sensor ID (used for publishing and receiving messages) const char* REQUESTOR_ID_KEY = "requestorId"; // The key in the JSON payload for the requestor device ID (used for publishing and receiving messages) const char* PHOTO_URL_KEY = "photo"; // The key in the JSON payload for the URL of uploaded photos /* El identificador de este ESP32 cam (no haremos caso a mensajes que incluyan este identificador) */ const char* SENSOR_ID = "musicRoom";
Añade los parámetros de tu wifi:
/* Parámetros conexión wifi */ const char* SSID = "your wifi SSID"; const char* WIFI_PWD = "your wifi password";
Constantes para la nomenclatura de ficheros:
/* Nomenclatura de ficheros */ const char* FILE_EXTENSION = ".jpg"; // extension (always JPEG) const String FILENAME_SANITISER = "-"; // to delimit parts of filenames
El certificado raiz para establecer una conexión segura a Hive MQTT (si usas otro broker MQTT, no necesitas esto).
/* Certificado raiz de Hive MQTT */ static const char* ROOT_CA PROGMEM = R"EOF( -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )EOF";
Define los pines del AI Thinkerº para conectar a la cámara OV2640 y declara el objeto de configuración de la OV2640.
/* Estos valores son para CAMERA_MODEL_AI_THINKER (copiados desde camera_pins.h del ejemplo de Arduino CameraWebServer) */ #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 camera_config_t cameraCfg; // Configuración de cámara (asignaremos los parámetros anteriores a esto)
Parámetros de la conexión NTPnetwork time protocol. Solo necesitamos esto para nombrar los ficheros que vayamos a subir. Aquí usamos la librería WiFiUDP con su configuración por defecto.
Parámetro | Valor |
---|---|
servidor | pool.ntp.org |
franja horaria | 0 (es decir, UTC) |
frecuencia de actualización | 60000 (es decir, cada minuto) |
Se puede parameterizar la configuración más si quieres (más información en el repositorio github de NTPClient). Por ejemplo:
NTPClient ntpClient(ntpUDP, "europe.pool.ntp.org", (3600*2), 30000);
NTPClient ntpClient(ntpUDP);
Inicia la sesión FTP:
ESP32_FTPClient ftp (FTP_URL, FTP_USER, FTP_PWD, 5000, 2);
Inicia los clientes wifi y MQTT:
WiFiClientSecure espClient; PubSubClient mqttClient(espClient);
Añade una función para arrancar
la conexión wifi. Invocaremos esta
función de la función setup()
.
void setupWifi() { delay(10); Serial.print("\nConnecting to "); Serial.println(SSID); WiFi.mode(WIFI_STA); WiFi.begin(SSID, WIFI_PWD); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.print("\nWiFi connected\nIP address: "); Serial.println(WiFi.localIP()); }
Añade una función para arrancar la
cámara OV2649, que también llamaremos desde
la función setup()
.
void setupCamera() { cameraCfg.ledc_channel = LEDC_CHANNEL_0; cameraCfg.ledc_timer = LEDC_TIMER_0; cameraCfg.pin_d0 = Y2_GPIO_NUM; cameraCfg.pin_d1 = Y3_GPIO_NUM; cameraCfg.pin_d2 = Y4_GPIO_NUM; cameraCfg.pin_d3 = Y5_GPIO_NUM; cameraCfg.pin_d4 = Y6_GPIO_NUM; cameraCfg.pin_d5 = Y7_GPIO_NUM; cameraCfg.pin_d6 = Y8_GPIO_NUM; cameraCfg.pin_d7 = Y9_GPIO_NUM; cameraCfg.pin_xclk = XCLK_GPIO_NUM; cameraCfg.pin_pclk = PCLK_GPIO_NUM; cameraCfg.pin_vsync = VSYNC_GPIO_NUM; cameraCfg.pin_href = HREF_GPIO_NUM; cameraCfg.pin_sscb_sda = SIOD_GPIO_NUM; cameraCfg.pin_sscb_scl = SIOC_GPIO_NUM; cameraCfg.pin_pwdn = PWDN_GPIO_NUM; cameraCfg.pin_reset = RESET_GPIO_NUM; cameraCfg.xclk_freq_hz = 20000000; cameraCfg.pixel_format = PIXFORMAT_JPEG; // Configurar dimensiones y calidad de imagen modestas para ahorra memoria, ancho de banda, espacio en el servidor y accelerar el proceso (suficiente para esta demostración) cameraCfg.frame_size = FRAMESIZE_SVGA; cameraCfg.jpeg_quality = 12; cameraCfg.fb_count = 1; // Arrancar cámara esp_err_t status = esp_camera_init(&cameraCfg); if (status != ESP_OK) { Serial.printf("Could not start camera: error code 0x%x", status); return; } }
Añade una función para conectar al
broker MQTT, que también se invocará
desde la función setup()
:
void connectMQTT() { randomSeed(micros()); Serial.println("Checking MQTT connection..."); while (!mqttClient.connected()) { Serial.println("Connecting to MQTT broker..."); String clientId = MQTT_CLIENT_ID_PREFIX + String(random(0xffff), HEX); if (mqttClient.connect(clientId.c_str(), MQTT_USERNAME, MQTT_PWD)) { Serial.println("MQTT connected"); mqttClient.subscribe(MQTT_SUB_TOPIC, QOS); } else { Serial.println("Error connecting to " + mqttClient.state()); delay(5000); } } }
Crea una retrollamada (o "callback") que se invocará
por el objeto PubSub cada vez que otro
dispositivo publique un mensaje MQTT
al tópico takePhoto.
El código deserializa el mensaje JSON
recibido, y extrae el identificador del
ESP32 y el identificar del móvil que
solicitó la foto.
Si el valor de SENSOR_ID_KEY en el cuerpo
del JSON corresponde a nuestro SENSOR_ID,
sacaremos y subiremos una foto.
Incluiremos el identificador del móvil en
la notificación al tópico photoTaken
para que cada móvil suscrito al tópico sepa
si ha solicitado la foto.
Esto es redundante, claro, si solo hay
un solo móvil en tu proyecto. La llamada
callback()
también
actualiza la timestamp (marca temporal) del
servidor NTP e inicia la captura de foto y
su subida por FTP:
void callback(char* topic, byte* payload, unsigned int length){ Serial.println("callback() invoked"); StaticJsonDocument<200> json; DeserializationError jsonError = deserializeJson(json, payload); if (jsonError) { Serial.print(F("deserializeJson() failed: ")); Serial.println(jsonError.f_str()); return; } const char* sensorId = json[SENSOR_ID_KEY]; const char* requestorId = json[REQUESTOR_ID_KEY]; Serial.print("sensor: "); Serial.println(sensorId); Serial.print("requestor ID: "); Serial.println(requestorId); ntpClient.update(); camera_fb_t * fb = takePhoto(); uploadPhoto(fb, requestorId); esp_camera_fb_return(fb); Serial.println("Photo taken"); }
Crea una función que saca una foto
y que devuelve los datos de la imagen
en formato JPEG. Esta función será
invocada desde la función
callback()
.
/* Sacar un foto y devolver los datos de imagen como objeto tipo Frame Buffer*/ camera_fb_t * takePhoto() { camera_fb_t * fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Couldn't take photo!!!!"); } return fb; }
Crea un método para nombrar el fichero JPEG, subirlo al servidor y publicar la notificación al tópico photoTaken. La payload del mensaje contendrá el identificador de la ESP32 Cam, el identificador del móvil que solicitó la foto y el URL HTTP del fichero que se ha subido. Ten en cuenta que puedes modificar el código de Android para descargar las imagenes por FTP si prefieres.
void uploadPhoto(camera_fb_t * fb, const char* requestorId){ Serial.println("Uploading image by FTP..."); Serial.println("Starting FTP session.."); ftp.OpenConnection(); Serial.println("FTP session started"); ftp.ChangeWorkDir(FTP_PATH); ftp.InitFile("Type I"); String filename = SENSOR_ID + FILENAME_SANITISER + ntpClient.getFormattedDate() + FILE_EXTENSION; int l = filename.length() + 1; char dst[l]; filename.toCharArray(dst, l); Serial.print("File name: "); Serial.println(dst) ; ftp.NewFile(dst); ftp.WriteData(fb->buf, fb->len); ftp.CloseFile(); ftp.CloseConnection(); if (!mqttClient.connected()) { Serial.println("MQTT not connected. Connecting now..."); connectMQTT(); // Conectar al broker MQTT si no estamos conectados } else{ Serial.println("MQTT already connected"); } Serial.print("Publishing message: "); Serial.print(dst); Serial.print(" to topic: "); Serial.print(MQTT_PUB_TOPIC); Serial.println("..."); char httpURL[strlen(HTTP_PATH) + strlen(dst)]; // objeto array para almacenar el URL HTTP completo a la imagen que acabamos de subir strcpy(httpURL, HTTP_PATH); // añadimos el dominio al URL strcat(httpURL, dst); // añadimos la ruta al URL StaticJsonDocument<200> json; json[SENSOR_ID_KEY] = SENSOR_ID; json[REQUESTOR_ID_KEY] = requestorId; json[PHOTO_URL_KEY] = httpURL; int payloadSize = measureJson(json) + 1; char payload[payloadSize]; serializeJson(json, payload, sizeof(payload)); mqttClient.publish(MQTT_PUB_TOPIC, payload, true); Serial.println("Message published"); }
Crea el código para el método
setup()
de
la ESP32 Cam (que es el método que
se ejecuta cada vez que se reinicia el
chip). En este proyecto inicia la
comunicación serie (para poder depurar
nuestro código), inicia la cámara y
las conexiones wifi, NTP y MQTT.
También deshabilita la detección de bajones de corriente para que el chip no se reinicie con cada bajada de voltaje.
void setup() { WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // deshabilitar detección de bajones de corriente Serial.begin(115200); setupWifi(); setupCamera(); ntpClient.begin(); espClient.setCACert(ROOT_CA); // informar el certificado para la conexión MQTT mqttClient.setServer(MQTT_SERVER, MQTT_PORT); // informar los parámetros del servidor mqttClient.setCallback(callback); // asignar la función "callback" (retrollamada) que se ejuctará cada vez que recibamos un mensaje MQTT }
La función loop()
(que se ejecutará continuamente) asegura
que el ESP32 esté siempre conectado al
broker MQTT y que la sesión MQTT noo caduque.
void loop() { if (!mqttClient.connected()) { connectMQTT(); } mqttClient.loop(); // invocar la llamada "keepalive" del cliente MQTT }
#include "esp_camera.h" #include "soc/soc.h" // Necesario para gestionar bajones de luz (que vamos a deshabilitar) #include "soc/rtc_cntl_reg.h" // Necesario para gestionar bajones de luz (que vamos a deshabilitar) #include "driver/rtc_io.h" #include <WiFi.h> #include "ESP32_FTPClient.h" #include <WiFiClientSecure.h> #include <PubSubClient.h> // MQTT lib de knolleary (https://github.com/knolleary/pubsubclient) #include <NTPClient.h> #include <WiFiUdp.h> #include "time.h" #include "ArduinoJson.h" // JSON lib de Benoit Blanchon (https://www.arduinolibraries.info/libraries/arduino-json) /* Parametros conexión FTP */ char* FTP_URL = "ftp.tu.servidorftp"; char* FTP_USER = "tu@usuarioftp"; char* FTP_PWD = "tu contraseña FTP"; char* FTP_PATH = "/ruta/a/carpeta/de/fotos/"; /* Parámetros conexión HTTP (para suscritores puedan descargar las imagenes) */ char* HTTP_PATH = "https://URL/and/path/to/photos/folder/"; /* Parámetros conexión Hive MQTT*/ /* Parámetros conexión Hive MQTT*/ const char* MQTT_SERVER = "xxxxxxxxxxxxxxxxxxxxxx.s1.eu.hivemq.cloud"; const char* MQTT_USERNAME = "Tu usuario HIVE MQTT"; const char* MQTT_PWD = "Tu contraseña HIVE MQTT"; const int MQTT_PORT = 8883; const char* MQTT_PUB_TOPIC = "yourtopics/photoTaken"; // Publicaremos notificaciones a este tópico cada vez saquemos un foto const char* MQTT_SUB_TOPIC = "yourtopics/takePhoto"; // Recibiremos los comandos de sacar una foto desde este tópico const char* MQTT_CLIENT_ID_PREFIX = "el.prefijo.que quieras."; const int QOS = 1; /* Llaves JSON */ const char* SENSOR_ID_KEY = "sensorId"; // The key in the JSON payload for the sensor ID (used for publishing and receiving messages) const char* REQUESTOR_ID_KEY = "requestorId"; // The key in the JSON payload for the requestor device ID (used for publishing and receiving messages) const char* PHOTO_URL_KEY = "photo"; // The key in the JSON payload for the URL of uploaded photos /* El identificador de este ESP32 cam (no haremos caso a mensajes que incluyan este identificador) */ const char* SENSOR_ID = "musicRoom"; /* Parámetros conexión wifi */ const char* SSID = "tu SSID wifi"; const char* WIFI_PWD = "tu contrseña wifi"; /* Nomenclatura de ficheros */ const char* FILE_EXTENSION = ".jpg"; // extensión (siempre JPEG) const String FILENAME_SANITISER = "-"; // para nombrar los ficheros /* Certificado raiz de Hive MQTT */ static const char* ROOT_CA PROGMEM = R"EOF( -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )EOF"; /* Estos valores son para CAMERA_MODEL_AI_THINKER (copiados desde camera_pins.h del ejemplo de Arduino CameraWebServer) */ #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 camera_config_t cameraCfg; // Configuración de cámara (asignaremos los parámetros anteriores a esto) /* Parámetros NTP (network time protocol) - solo necesitamos esto para nombrar los ficheros que vamos a subir */ WiFiUDP ntpUDP; NTPClient ntpClient(ntpUDP); // valores por defecto: server: pool.ntp.org; timezone offset: 0 (es decir, UTC); update interval: 60000 // ejemplo parámeterizado: NTPClient ntpClient(ntpUDP, "europe.pool.ntp.org", (3600*2), 30000); // Arrancamos sesión FTP ESP32_FTPClient ftp (FTP_URL, FTP_USER, FTP_PWD, 5000, 2); WiFiClientSecure espClient; PubSubClient mqttClient(espClient); // API disponible aquí: https://pubsubclient.knolleary.net/api#subscribe void setupWifi() { delay(10); Serial.print("\nConnecting to "); Serial.println(SSID); WiFi.mode(WIFI_STA); WiFi.begin(SSID, WIFI_PWD); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.print("\nWiFi connected\nIP address: "); Serial.println(WiFi.localIP()); } void setupCamera() { cameraCfg.ledc_channel = LEDC_CHANNEL_0; cameraCfg.ledc_timer = LEDC_TIMER_0; cameraCfg.pin_d0 = Y2_GPIO_NUM; cameraCfg.pin_d1 = Y3_GPIO_NUM; cameraCfg.pin_d2 = Y4_GPIO_NUM; cameraCfg.pin_d3 = Y5_GPIO_NUM; cameraCfg.pin_d4 = Y6_GPIO_NUM; cameraCfg.pin_d5 = Y7_GPIO_NUM; cameraCfg.pin_d6 = Y8_GPIO_NUM; cameraCfg.pin_d7 = Y9_GPIO_NUM; cameraCfg.pin_xclk = XCLK_GPIO_NUM; cameraCfg.pin_pclk = PCLK_GPIO_NUM; cameraCfg.pin_vsync = VSYNC_GPIO_NUM; cameraCfg.pin_href = HREF_GPIO_NUM; cameraCfg.pin_sscb_sda = SIOD_GPIO_NUM; cameraCfg.pin_sscb_scl = SIOC_GPIO_NUM; cameraCfg.pin_pwdn = PWDN_GPIO_NUM; cameraCfg.pin_reset = RESET_GPIO_NUM; cameraCfg.xclk_freq_hz = 20000000; cameraCfg.pixel_format = PIXFORMAT_JPEG; // Configurar dimensiones y calidad de imagen modestas para ahorra memoria, ancho de banda, espacio en el servidor y accelerar el proceso (suficiente para esta demostración) cameraCfg.frame_size = FRAMESIZE_SVGA; cameraCfg.jpeg_quality = 12; cameraCfg.fb_count = 1; // Arrancar cámara esp_err_t status = esp_camera_init(&cameraCfg); if (status != ESP_OK) { Serial.printf("Could not start camera: error code 0x%x", status); return; } } void connectMQTT() { randomSeed(micros()); Serial.println("Checking MQTT connection..."); while (!mqttClient.connected()) { Serial.println("Connecting to MQTT broker..."); String clientId = MQTT_CLIENT_ID_PREFIX + String(random(0xffff), HEX); if (mqttClient.connect(clientId.c_str(), MQTT_USERNAME, MQTT_PWD)) { Serial.println("MQTT connected"); mqttClient.subscribe(MQTT_SUB_TOPIC, QOS); } else { Serial.println("Error connecting to " + mqttClient.state()); delay(5000); } } } void callback(char* topic, byte* payload, unsigned int length){ Serial.println("callback() invoked"); StaticJsonDocument<200> json; // o DynamicJsonDocument json(200); DeserializationError jsonError = deserializeJson(json, payload); if (jsonError) { Serial.print(F("deserializeJson() failed: ")); Serial.println(jsonError.f_str()); return; } const char* sensorId = json[SENSOR_ID_KEY]; const char* requestorId = json[REQUESTOR_ID_KEY]; Serial.print("sensor: "); Serial.println(sensorId); Serial.print("requestor ID: "); Serial.println(requestorId); ntpClient.update(); // Invocar funciones de sacar y subir la foto camera_fb_t * fb = takePhoto(); uploadPhoto(fb, requestorId); esp_camera_fb_return(fb); Serial.println("Photo taken"); } /* Sacar un foto y devolver los datos de imagen como objeto tipo Frame Buffer*/ camera_fb_t * takePhoto() { camera_fb_t * fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Couldn't take photo!!!!"); } return fb; } void uploadPhoto(camera_fb_t * fb, const char* requestorId){ Serial.println("Uploading image by FTP..."); Serial.println("Starting FTP session.."); ftp.OpenConnection(); Serial.println("FTP session started"); ftp.ChangeWorkDir(FTP_PATH); ftp.InitFile("Type I"); String filename = SENSOR_ID + FILENAME_SANITISER + ntpClient.getFormattedDate() + FILE_EXTENSION; int l = filename.length() + 1; char dst[l]; filename.toCharArray(dst, l); Serial.print("File name: "); Serial.println(dst) ; ftp.NewFile(dst); ftp.WriteData(fb->buf, fb->len); ftp.CloseFile(); ftp.CloseConnection(); if (!mqttClient.connected()) { Serial.println("MQTT not connected. Connecting now..."); connectMQTT(); // Conectar al broker MQTT si no estamos conectados } else{ Serial.println("MQTT already connected"); } Serial.print("Publishing message: "); Serial.print(dst); Serial.print(" to topic: "); Serial.print(MQTT_PUB_TOPIC); Serial.println("..."); char httpURL[strlen(HTTP_PATH) + strlen(dst)]; // objeto array para almacenar el URL HTTP completo a la imagen que acabamos de subir strcpy(httpURL, HTTP_PATH); // añadimos el dominio al URL strcat(httpURL, dst); // añadimos la ruta al URL StaticJsonDocument<200> json; json[SENSOR_ID_KEY] = SENSOR_ID; json[REQUESTOR_ID_KEY] = requestorId; json[PHOTO_URL_KEY] = httpURL; int payloadSize = measureJson(json) + 1; char payload[payloadSize]; serializeJson(json, payload, sizeof(payload)); mqttClient.publish(MQTT_PUB_TOPIC, payload, true); Serial.println("Message published"); } void setup() { WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // deshabilitar detección de bajones de corriente Serial.begin(115200); setupWifi(); setupCamera(); ntpClient.begin(); espClient.setCACert(ROOT_CA); // informar el certificado para la conexión MQTT mqttClient.setServer(MQTT_SERVER, MQTT_PORT); // informar los parámetros del servidor mqttClient.setCallback(callback); // asignar la función "callback" (retrollamada) que se ejuctará cada vez que recibamos un mensaje MQTT } void loop() { if (!mqttClient.connected()) { connectMQTT(); } mqttClient.loop(); // invocar la llamada "keepalive" del cliente MQTT }
Sigue estos pasos:
Herramientas > Placa
, y seleccionar AI Thinker ESP32-CAM.Herramientas > Puerto
, seleccionar el puerto USB que estás usando.Herramientas > Monitor Serie
y especifica los baudio
a 115200
(necesita corresponder al valor
de esta linea en código ESP32: Serial.begin(115200)
).Programa > Subir
(o Ctrl + U
).File > New > New Project
.Empty Activity
.
Next
y:
Java
como lenguajeFinish
Dos pestañas aparecerán en el IDE: activity_main.xml
y MainActivity.java
.
activity_main.xml
representa los componentes de nuestra aplicación
y MainActivity.java
contiene la lógica.
Selecciona activity_main.xml
y
machaca encima de todo el XML que el IDE nos
ha creado por defecto con lo siguiente:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageButton android:id="@+id/takeMusicRoomPhotoBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="0dp" android:layout_marginTop="28dp" android:onClick="takePhoto" android:tag="musicRoom" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@android:drawable/ic_menu_camera" /> <TextView android:id="@+id/status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Status pending..." android:layout_marginStart="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.162" /> <ImageView android:id="@+id/photoView" android:layout_width="377dp" android:layout_height="445dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.524" app:srcCompat="@android:drawable/ic_menu_camera" /> </androidx.constraintlayout.widget.ConstraintLayout>
De esta forma hemos creado un botón para sacar la foto, una caja de texto para mostrar el estado de las llamadas MQTT y un componente Android tipo ImageView dónde nuestras fotos aparecerán.
Ten en cuenta lo siguiente:
android:id
son importantes porque nuestro código Java
necesitará hacer referencia a estos identificadores.android:tag
es "musicRoom" en este ejemplo. Lo
puedes llamar lo que quieras, por supuesto,
pero ten en cuenta que debe exactamente que
el valor definido aquí
const char* SENSOR_ID = "musicRoom";
en el código de la ESP32 Cam. Si es
diferente, the ESP32 no hará caso al
mensaje MQTT. Esto es para que multiples
cámaras puedan suscribir a nuestro tópico
takePhoto y para que podamos añadir
más botones a nuestra app de Android.
android:onClick="takePhoto"
muestra un error (una linea roja ondulada
por debajo del valor). Esto porque ningún método
takePhoto()
existe todavía.
Es que nos falta picar el código de Java ;)
Añade las propiedades resaltadas a
continuación a
manifests/AndroidManifest.xml
:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="io.friedchips.android.mqttphotodemo"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.MQTTPhotoDemo" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> android:configChanges="orientation|screenSize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="org.eclipse.paho.android.service.MqttService"></service> </application> </manifest>
app/gradle.build
:
plugins { id 'com.android.application' } android { compileSdk 32 defaultConfig { applicationId "io.friedchips.android.mqttphotodemo" minSdk 21 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.squareup.picasso:picasso:2.71828' implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.2' implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.0.2' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }
Ahora importa estas dependencias al proyecto de Android
dando a Sync Project with Gradle Files
en la barra de menús de Android Studio.
En la pestaña activity_main.xml
haz clic
en la vista Design
(1)
para mostrar una representación visual de la maquetación.
Puedes hacer clic en la vista de Code
(2)
para volver al XML.
Copia y pega el siguiente código en MainActivity.java
por debajo de tus declaraciones de package
:
package io.friedchips.android.mqttphotodemo; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.squareup.picasso.Picasso; import org.eclipse.paho.android.service.MqttAndroidClient; import org.eclipse.paho.client.mqttv3.IMqttActionListener; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.IMqttToken; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.json.JSONObject; import java.io.UnsupportedEncodingException; public class MainActivity extends AppCompatActivity { protected static final String TAG = "MQTTPhotoDemo"; // ID for logging purposes protected static final String MQTT_SERVER_URL = "ssl://xxxxxxxxxxxxxxxxxxxs1.eu.hivemq.cloud:8883"; protected static final String MQTT_USER = "your user"; protected static final String MQTT_PWD = "your password"; protected static final String MQTT_TAKE_PHOTO_TOPIC = "yourTopics/takePhoto"; protected static final String MQTT_PHOTO_TAKEN_TOPIC = "ourTopics/photoTaken"; protected static int QOS = 2; protected static final String REQUESTOR_ID_KEY = "requestorId"; protected static final String CLIENT_ID = "myAndroidPhone"; // will be included in incoming messages; indicates whether this device requested a photo protected static final String SENSOR_ID_KEY = "sensorId"; protected static final String PHOTO_URL_KEY = "photo"; MqttAndroidClient mqttClient; MqttConnectOptions mqttConnectionOptions; ImageView photoView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); photoView = (ImageView) findViewById(R.id.photoView); connectMQTT(); } public void connectMQTT(){ mqttConnectionOptions = new MqttConnectOptions(); mqttConnectionOptions.setUserName(MQTT_USER); mqttConnectionOptions.setPassword(MQTT_PWD.toCharArray()); String clientId = MqttClient.generateClientId(); mqttClient = new MqttAndroidClient(this.getApplicationContext(), MQTT_SERVER_URL, clientId); try { IMqttToken token = mqttClient.connect(mqttConnectionOptions); token.setActionCallback(new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { Log.d(TAG, "Connected to " + MQTT_SERVER_URL); try { subscribe(MQTT_PHOTO_TAKEN_TOPIC); } catch (Exception e) { Log.e(TAG, "subscribe() failed"); e.printStackTrace(); } } @Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { Log.d(TAG, "Couldn't connect to " + MQTT_SERVER_URL); Log.e(TAG, "reason: ", exception); } }); } catch (MqttException e) { e.printStackTrace(); } } public void subscribe(String topic) throws MqttException { if(mqttClient.isConnected()){ Log.d(TAG, "subscribing to " + topic + "..."); mqttClient.subscribe(MQTT_PHOTO_TAKEN_TOPIC, QOS); mqttClient.setCallback(new MqttCallback() { @Override public void connectionLost(Throwable cause) { Log.e(TAG, "Connection lost during callback()", cause); Log.i(TAG, "reconnecting..."); updateStatus("reconnecting..."); connectMQTT(); } @Override public void messageArrived(String topic, MqttMessage msg) throws Exception { Log.d(TAG, "Received message from " + topic); JSONObject json = new JSONObject(new String(msg.getPayload())); final String sensorId = json.getString(SENSOR_ID_KEY); final String requestorId = json.getString(REQUESTOR_ID_KEY); final String photoURL = json.getString(PHOTO_URL_KEY); Log.d(TAG, "sensorId = " + sensorId); Log.d(TAG, "requestorId = " + requestorId); Log.d(TAG, "photoURL = " + photoURL); if(!CLIENT_ID.equals(requestorId) && !"".equals(requestorId)){ Log.d(TAG, "Photo requested by some other device."); return; } // else.. Log.d(TAG, new String(msg.getPayload())); Picasso.get() .load(photoURL) .into(photoView); updateStatus("Photo sent (sensor ID: " + sensorId + ")"); } @Override public void deliveryComplete(IMqttDeliveryToken token) {;} } ); } else{ Log.d(TAG, "NOT connected!!!!!!"); } } public void publish(final String topic, final String payload){ if(!mqttClient.isConnected()){ Log.w(TAG, "not connected during publish(). reconnecting..."); updateStatus("reconnecting..."); connectMQTT(); } byte[] bytes = new byte[0]; try { bytes = payload.getBytes("UTF-8"); MqttMessage message = new MqttMessage(bytes); message.setQos(QOS); mqttClient.publish(topic, message); } catch (UnsupportedEncodingException | MqttException e) { e.printStackTrace(); } Log.d(TAG, "published to " + topic); } public void takePhoto(View view){ Log.d(TAG, "takePhoto() "); String sensorId = (String) view.getTag(); // This value comes from the Button component in activity_main.xml Log.d(TAG, "sensor ID " + sensorId); updateStatus("Taking photo (sensor ID: " + sensorId + ")..."); String json = "{\"" + SENSOR_ID_KEY + "\":\"" + sensorId + "\", \"" + REQUESTOR_ID_KEY + "\":\"" + CLIENT_ID + "\"}"; publish(MQTT_TAKE_PHOTO_TOPIC, json); } public void updateStatus(final String s){ ((TextView)findViewById(R.id.status)).setText(s); } }
En pocas palabras: el código Android monta
la conexión MQTT, suscribe al tópico
photoTaken (que es dónde el ESP32
publicará), invoca la librería Picasso
para descargar la foto por HTTP y mostrarla
en nuestro componente
PhotoView
.
Constante | Valor | Observaciones |
---|---|---|
MQTT_SERVER_URL | El URL de tu servidor MQTT | |
MQTT_USER | Tu usuario MQTT | |
MQTT_PWD | Tu contraseña MQTT | |
MQTT_TAKE_PHOTO_TOPIC | yourtopics/takePhoto | Debe ser idéntico al valor en tu código ESP32 exactamente |
MQTT_PHOTO_TAKEN_TOPIC | yourtopics/photoTaken | Debe ser idéntico al valor en tu código ESP32 exactamente |
CLIENT_ID | Un identificador para este móvil | Debe ser único si vas a usar varios móviles. Si varios móviles comparten un identificador, cada un recibirá la fotos solicitadas por los otros. |
Miramos lo que hace el código de Android:
android:onClick="takePhoto"
del botón en activity_main.xml invoca el
método takePhoto()
.
El método takePhoto()
primero genera la payload de JSON
del mensaje MQTT, que incluye:
android:tag
del botón en el fichero
activity_main.xml.
Una vez generado el JSON, el método
llama al método publish()
.
El método publish()
publica el mensaje JSON al tópico
MQTT takePhoto.
Cuando el ESP32 Cam recibe el mensaje sacará una foto, la subirá por FTP y nos notificará del URL HTTP.
Se trata de ejecutar nuestra app en un dispositivo virtual en Android Studio.
Si no tienes un dispositivo virtual instalado, sigue estos pasos. Si ya tienes uno, puedes saltar al paso de pruebas:
El móvil virtual que hayas elegido aparecerá en la ventana de Device Manager y estará disponible en el desplable de Available Devices.
Selecciona tu móvil virtual en el desplegable Available Devices (1) y haz clic en el icono Run (2).
El móvil virtual aparecerá en la ventana de Emulator.
Con el ESP32 Cam encendido (y apuntando a algo interesante), haz clic en el botón take photo (1) en el emulador.
Una foto aparecerá el el componente de ImageView(2) :)
Primero, asegúrate que el ESP32 Cam esté conectado
por el cable USB a tu PC y Depuración por USB
esté habilitada
Si no has hecho ningún desarollo con tu móvil antes,
ves a Ajustes >
Sistema
y toca Número de Compilación siete
veces. Esto habilitará el menú
Opciones para desarrolladores.
Luego ves a Ajustes >
Opciones para desarrolladores
y habilita
Depuración por USB
.
Ahora tu móvil estará disponible en el desplegable Available Devices (y en el Device Manager en la pestaña Physical).
Con tu móvil seleccionado en el desplegable Available Devices, haz clic en el icono Run. Esto instalará tu app (junto con un icon) en tu móvil y lanzarla.
Ahora puedes desconectar tu móvil del PC y simplemente tocar el icono para arrancar la app cuando quieras.
Abre la app en tu móvil tocando el nuevo icono y toca el botón take photo. Aparecerá una foto enviada desde el ESP32 Cam.
Recuerda que el ESP32 Cam ya no tiene que estar conectado a tu PC. Solo necesita tener corriente (una pila de 5v, por ejemplo) y dentro del alcance del wifi que has configurado. De eso se trata IoT.
¡¡¡A disfutar!!!