#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
static const char *TAG = "BLE_ENV";
/* ------------------------------------------------------------------ */
/* DHT22 Configuration */
/* ------------------------------------------------------------------ */
#define DHT22_GPIO GPIO_NUM_4
#define SENSOR_READ_INTERVAL_MS 5000
/* ------------------------------------------------------------------ */
/* ------------------------------------------------------------------ */
static const char *device_name = "ESP32-ENV";
static uint16_t conn_handle_active = 0;
static bool is_connected = false;
static bool temp_notify_enabled = false;
static bool humi_notify_enabled = false;
static uint16_t temp_chr_val_handle;
static uint16_t humi_chr_val_handle;
/* Sensor values in BLE format */
static int16_t current_temp_ble = 0; /* 0.01 C units */
static uint16_t current_humi_ble = 0; /* 0.01 % units */
/* ------------------------------------------------------------------ */
/* DHT22 Bit-Bang Driver */
/* ------------------------------------------------------------------ */
static bool dht22_read(float *temperature, float *humidity)
/* Send start signal: pull low 1 ms, then release */
gpio_set_direction(DHT22_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(DHT22_GPIO, 0);
vTaskDelay(pdMS_TO_TICKS(2));
gpio_set_level(DHT22_GPIO, 1);
/* Switch to input and wait for sensor response */
gpio_set_direction(DHT22_GPIO, GPIO_MODE_INPUT);
/* Wait for sensor to pull low (response signal) */
timeout = esp_timer_get_time() + 100;
while (gpio_get_level(DHT22_GPIO) == 1) {
if (esp_timer_get_time() > timeout) return false;
/* Sensor holds low ~80 us, then high ~80 us */
timeout = esp_timer_get_time() + 100;
while (gpio_get_level(DHT22_GPIO) == 0) {
if (esp_timer_get_time() > timeout) return false;
timeout = esp_timer_get_time() + 100;
while (gpio_get_level(DHT22_GPIO) == 1) {
if (esp_timer_get_time() > timeout) return false;
/* Read 40 bits (5 bytes) */
for (int i = 0; i < 40; i++) {
/* Wait for bit start (low ~50 us) */
timeout = esp_timer_get_time() + 100;
while (gpio_get_level(DHT22_GPIO) == 0) {
if (esp_timer_get_time() > timeout) return false;
/* Measure high duration: ~26 us = 0, ~70 us = 1 */
int64_t start = esp_timer_get_time();
while (gpio_get_level(DHT22_GPIO) == 1) {
if (esp_timer_get_time() > timeout) return false;
int64_t duration = esp_timer_get_time() - start;
uint8_t checksum = data[0] + data[1] + data[2] + data[3];
if (checksum != data[4]) {
ESP_LOGW(TAG, "DHT22 checksum mismatch");
/* Parse humidity (unsigned) */
uint16_t raw_humi = (data[0] << 8) | data[1];
*humidity = raw_humi * 0.1f;
/* Parse temperature (signed: bit 15 = sign) */
uint16_t raw_temp = (data[2] << 8) | data[3];
*temperature = raw_temp * -0.1f;
*temperature = raw_temp * 0.1f;
/* ------------------------------------------------------------------ */
/* GATT Access Callback */
/* ------------------------------------------------------------------ */
static int gatt_access_cb(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
uint16_t uuid16 = ble_uuid_u16(ctxt->chr->uuid);
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
rc = os_mbuf_append(ctxt->om, ¤t_temp_ble,
sizeof(current_temp_ble));
ESP_LOGI(TAG, "Temperature read: %d (0.01 C)", current_temp_ble);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
rc = os_mbuf_append(ctxt->om, ¤t_humi_ble,
sizeof(current_humi_ble));
ESP_LOGI(TAG, "Humidity read: %d (0.01%%)", current_humi_ble);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
return BLE_ATT_ERR_UNLIKELY;
/* ------------------------------------------------------------------ */
/* GATT Service Definitions */
/* ------------------------------------------------------------------ */
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(0x181A), /* Environmental Sensing */
.characteristics = (struct ble_gatt_chr_def[]) {
.uuid = BLE_UUID16_DECLARE(0x2A6E), /* Temperature */
.access_cb = gatt_access_cb,
.val_handle = &temp_chr_val_handle,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
.uuid = BLE_UUID16_DECLARE(0x2A6F), /* Humidity */
.access_cb = gatt_access_cb,
.val_handle = &humi_chr_val_handle,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
static int gatt_svr_init(void)
rc = ble_gatts_count_cfg(gatt_svr_svcs);
ESP_LOGE(TAG, "ble_gatts_count_cfg failed, rc=%d", rc);
rc = ble_gatts_add_svcs(gatt_svr_svcs);
ESP_LOGE(TAG, "ble_gatts_add_svcs failed, rc=%d", rc);
/* ------------------------------------------------------------------ */
/* Notification Helper */
/* ------------------------------------------------------------------ */
static void send_notification(uint16_t attr_handle, void *data, uint16_t len)
struct os_mbuf *om = ble_hs_mbuf_from_flat(data, len);
ESP_LOGE(TAG, "Failed to allocate mbuf for notification");
int rc = ble_gatts_notify_custom(conn_handle_active, attr_handle, om);
ESP_LOGW(TAG, "Notification failed, rc=%d", rc);
/* Forward declarations (start_advertising and gap_event_handler reference each other) */
static int gap_event_handler(struct ble_gap_event *event, void *arg);
static void start_advertising(void);
/* ------------------------------------------------------------------ */
/* ------------------------------------------------------------------ */
static void start_advertising(void)
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
memset(&fields, 0, sizeof(fields));
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
fields.name = (uint8_t *)device_name;
fields.name_len = strlen(device_name);
fields.name_is_complete = 1;
ble_uuid16_t ess_uuid = BLE_UUID16_INIT(0x181A);
fields.uuids16 = &ess_uuid;
fields.uuids16_is_complete = 1;
rc = ble_gap_adv_set_fields(&fields);
ESP_LOGE(TAG, "ble_gap_adv_set_fields failed, rc=%d", rc);
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
rc = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER,
&adv_params, gap_event_handler, NULL);
if (rc != 0 && rc != BLE_HS_EALREADY) {
ESP_LOGE(TAG, "ble_gap_adv_start failed, rc=%d", rc);
ESP_LOGI(TAG, "Advertising started");
/* ------------------------------------------------------------------ */
/* ------------------------------------------------------------------ */
static int gap_event_handler(struct ble_gap_event *event, void *arg)
case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0) {
ESP_LOGI(TAG, "Connection established, handle=%d",
event->connect.conn_handle);
conn_handle_active = event->connect.conn_handle;
/* Request preferred connection parameters */
struct ble_gap_upd_params params = {
.itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN,
.itvl_max = 24, /* 30 ms */
.supervision_timeout = 400, /* 4 seconds */
ble_gap_update_params(conn_handle_active, ¶ms);
ESP_LOGW(TAG, "Connection failed, status=%d",
case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGI(TAG, "Disconnected, reason=0x%02x",
event->disconnect.reason);
temp_notify_enabled = false;
humi_notify_enabled = false;
case BLE_GAP_EVENT_SUBSCRIBE:
ESP_LOGI(TAG, "Subscribe: handle=%d, notify=%d, indicate=%d",
event->subscribe.attr_handle,
event->subscribe.cur_notify,
event->subscribe.cur_indicate);
if (event->subscribe.attr_handle == temp_chr_val_handle) {
temp_notify_enabled = event->subscribe.cur_notify;
if (event->subscribe.attr_handle == humi_chr_val_handle) {
humi_notify_enabled = event->subscribe.cur_notify;
case BLE_GAP_EVENT_ADV_COMPLETE:
ESP_LOGI(TAG, "MTU update: conn_handle=%d, mtu=%d",
event->mtu.conn_handle, event->mtu.value);
case BLE_GAP_EVENT_CONN_UPDATE:
ESP_LOGI(TAG, "Connection parameters updated");
/* ------------------------------------------------------------------ */
/* BLE Host Task and Sync Callback */
/* ------------------------------------------------------------------ */
static void ble_on_sync(void)
/* Use the default public address */
rc = ble_hs_util_ensure_addr(0);
ESP_LOGE(TAG, "Failed to ensure address, rc=%d", rc);
static void ble_on_reset(int reason)
ESP_LOGE(TAG, "BLE host reset, reason=%d", reason);
static void ble_host_task(void *param)
ESP_LOGI(TAG, "NimBLE host task started");
nimble_port_freertos_deinit();
/* ------------------------------------------------------------------ */
/* Sensor Reading Task */
/* ------------------------------------------------------------------ */
static void sensor_task(void *param)
float temperature, humidity;
/* Configure DHT22 GPIO */
gpio_reset_pin(DHT22_GPIO);
gpio_set_pull_mode(DHT22_GPIO, GPIO_PULLUP_ONLY);
/* Wait for initial sensor warm-up */
vTaskDelay(pdMS_TO_TICKS(2000));
if (dht22_read(&temperature, &humidity)) {
/* Convert to BLE format */
current_temp_ble = (int16_t)(temperature * 100.0f);
current_humi_ble = (uint16_t)(humidity * 100.0f);
ESP_LOGI(TAG, "Sensor: %.1f C, %.1f%% RH | BLE: temp=%d, humi=%d",
current_temp_ble, current_humi_ble);
/* Send notifications if subscribed */
if (temp_notify_enabled) {
send_notification(temp_chr_val_handle,
¤t_temp_ble, sizeof(current_temp_ble));
if (humi_notify_enabled) {
send_notification(humi_chr_val_handle,
¤t_humi_ble, sizeof(current_humi_ble));
ESP_LOGW(TAG, "DHT22 read failed");
vTaskDelay(pdMS_TO_TICKS(SENSOR_READ_INTERVAL_MS));
/* ------------------------------------------------------------------ */
/* Application Entry Point */
/* ------------------------------------------------------------------ */
ESP_LOGI(TAG, "BLE Environmental Sensor starting");
/* Initialize NVS (required for BLE bonding storage) */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
/* Initialize the NimBLE host */
ret = nimble_port_init();
ESP_LOGE(TAG, "nimble_port_init failed, ret=%d", ret);
/* Configure the NimBLE host */
ble_hs_cfg.sync_cb = ble_on_sync;
ble_hs_cfg.reset_cb = ble_on_reset;
/* Set the device name */
rc = ble_svc_gap_device_name_set(device_name);
ESP_LOGE(TAG, "Failed to set device name, rc=%d", rc);
/* Initialize the GATT server */
ESP_LOGE(TAG, "GATT server init failed, rc=%d", rc);
/* Start the NimBLE host task */
nimble_port_freertos_init(ble_host_task);
/* Start the sensor reading task */
xTaskCreate(sensor_task, "sensor_task", 4096, NULL, 5, NULL);
ESP_LOGI(TAG, "Initialization complete");
Comments