Extendiendo los pasos de primer contacto con ESP32, va tipo receta con anotaciones sin mayores explicaciones, la idea es terminar con una carpeta con el proyecto armado.
Instalación de dependencias
sudo apt install git wget flex bison gperf python3 python3-pip python3-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
Mi entorno suele ser una virtual, por comodidad también hago:
sudo apt install vim kdiff3 dirdiff
Estructura de archivos
mkdir esp http_request
~/esp/esp-idf
esp-idf-lib
http_request
Aunque he puesto el ejemplo http_request a la par de los repositorios, puede estar en cualquier lado.
Repositorios y setup herramientas
El primer repo te provee un montón de dispositivos, nos interesa el componente dht11.
git clone https://github.com/UncleRus/esp-idf-lib.git
git clone -b v4.4 --recursive https://github.com/espressif/esp-idf.git
El segundo es la adaptación de FreeRTOS a extensa de expresif.
cd esp-idf
./install.sh esp32
Eso te instaló lo que haga falta para ESP32. Tanto acá como en pasos posteriores, donde dice esp32 puede ir esp32c3 o esp32s2, pero por ahora sólo probé con esp32, no debería en general pero pueden haber diferencias sutiles con los pines en estos ejemplos básicos.
El código
cd ../http_request
. ../esp-idf/export.sh
Esto es para que funcione el entorno de build.
El código es copia del ejemplo http_request:
cp -r ../esp-idf/examples/protocols/http_request/ .
Ajuste del proyecto a esp32:
idf.py set-target esp32
Sería un buen momento para hacer funcionar el ejemplo así como está, pero vamos directo al ejemplo completo.
Dependencias de código
En CMakeFiles.txt, agregá la dependencia a esp-idf-lib:
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common $ENV{IDF_PATH}/../esp-idf-lib/components)
Menuconfig
idf.py menuconfig
Si está versionando, no te olvides de NO VERSIONAR sdkconfig una vez que hayas puesto los valores de conexión de la WiFi. Lo que hago yo es:idf.py menuconfig -> Save
git add sdkconfig
git commit -m "xxx"
idf.py menuconfig
Example Connection Configuration --->
[*] connect using WiFi interface
(xxxxxxx) WiFi SSID
(xxxxxxx) WiFi Password
Pero si tenés otros valores secretos se empieza a complicar. De un modo u otro, repito, NO VERSIONES SECRETOS.
Ajustes del código
Tomamos como base esp-idf-lib/examples/dht/main/main.c
includes
#include <stdio.h>
#include "dht.h"
constants
static const dht_sensor_type_t sensor_type = DHT_TYPE_DHT11;
static const gpio_num_t dht_gpio = 17;
La URL
#define WEB_SERVER "example.com" -> la IP que uses
#define WEB_PORT "80" -> el puerto que uses
#define WEB_PATH "/" -> según mi ejemplo "/collect.php"
static char *REQUEST_GET =
"GET " WEB_PATH "/?t=%d&h=%d HTTP/1.0\r\n"...
La idea es reemplazar esos dos %d con sprintf(), para eso incluí <stdio.h>, con los valores leídos.
Las variables
char send_buf[256];
int16_t temperature = 0;
int16_t humidity = 0;
La lectura del sensor
while(1) {
if (dht_read_data(sensor_type, dht_gpio, &humidity, &temperature) == ESP_OK) {
ESP_LOGI(TAG,"Humidity: %d%% Temp: %dC\n", humidity, temperature);
sprintf(send_buf, REQUEST_GET, temperature, humidity);
ESP_LOGI(TAG,"sending: \n%s\n",send_buf);
} else {
ESP_LOGE(TAG,"Could not read data from sensor\n");
}
y la escritura, al buffer lo preparamos con el sprintf() anterior.
if (write(s, send_buf, strlen(send_buf)) < 0) {...
Acá el código entero resultante con propuestas de ejercicios intercalados, no deberías necesitarlo, pero no molesta. El proyecto completo en github
#include <string.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
#include "dht.h"
/* Constants that aren't configurable in menuconfig */
#define WEB_SERVER "192.168.1.102"
#define WEB_PORT "8080"
#define WEB_PATH "/collect.php"
static const dht_sensor_type_t sensor_type = DHT_TYPE_DHT11;
static const gpio_num_t dht_gpio = 17;
static const char *TAG = "temp_collector";
static char *REQUEST_GET = "GET " WEB_PATH "/?t=%d&h=%d HTTP/1.0\r\n"
"Host: "WEB_SERVER":"WEB_PORT"\r\n"
"User-Agent: esp-idf/1.0 esp32\r\n"
"\r\n";
// Ejercicio: enviar POST
// Ejercicio: enviar json
static void http_get_task(void *pvParameters)
{
const struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *res;
struct in_addr *addr;
int s, r;
char recv_buf[64];
char send_buf[256];
int16_t temperature = 0;
int16_t humidity = 0;
while(1) {
if (dht_read_data(sensor_type, dht_gpio,
&humidity, &temperature) == ESP_OK) {
ESP_LOGI(TAG,
"Humidity: %d%% Temp: %dC\n", humidity / 10, temperature / 10);
sprintf(send_buf, REQUEST_GET, temperature / 10, humidity / 10);
ESP_LOGI(TAG,"sending: \n%s\n",send_buf);
} else {
ESP_LOGE(TAG,"Could not read data from sensor\n");
// Ejercicio: enviar mensaje
}
int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);
if(err != 0 || res == NULL) {
ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p",
err, res);
vTaskDelay(1000 / portTICK_PERIOD_MS);
continue;
}
addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s",
inet_ntoa(*addr));
s = socket(res->ai_family, res->ai_socktype, 0);
if(s < 0) {
ESP_LOGE(TAG, "... Failed to allocate socket.");
freeaddrinfo(res);
vTaskDelay(1000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... allocated socket");
if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {
ESP_LOGE(TAG,
"... socket connect failed errno=%d", errno);
close(s);
freeaddrinfo(res);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... connected");
freeaddrinfo(res);
if (write(s, send_buf, strlen(send_buf)) < 0) {
ESP_LOGE(TAG, "... socket send failed");
close(s);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... socket send success");
struct timeval receiving_timeout;
receiving_timeout.tv_sec = 5;
receiving_timeout.tv_usec = 0;
if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO,
&receiving_timeout, sizeof(receiving_timeout)) < 0) {
ESP_LOGE(TAG,
"... failed to set socket receiving timeout");
close(s);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... set socket receiving timeout success");
/* Read HTTP response */
do {
bzero(recv_buf, sizeof(recv_buf));
r = read(s, recv_buf, sizeof(recv_buf)-1);
for(int i = 0; i < r; i++) {
putchar(recv_buf[i]);
}
} while(r > 0);
ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);
close(s);
for(int countdown = 10; countdown >= 0; countdown--) {
ESP_LOGI(TAG, "%d... ", countdown);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
ESP_LOGI(TAG, "Starting again!");
}
}
void app_main(void)
{
ESP_ERROR_CHECK( nvs_flash_init() );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect());
xTaskCreate(&http_get_task, "http_get_task", 4096, NULL, 5, NULL);
}
Si hubieras usado el ejemplo http_request sin tocar y vez esa basura no te preocupes, es que el servidor te ha dado la respuesta comprimida, observá el Content-Encoding: gzip, no tenés error.
example.org |
Errata
Me faltó mencionar que hay que conectar el sensor a algún pin, en este caso usé el 17
static const gpio_num_t dht_gpio = 17;
y no olvides poner la resistencia entre el positivo (3.3v) y el cable de datos.