Adsense HTML/JavaScript

Showing posts with label arduino-esp32. Show all posts
Showing posts with label arduino-esp32. Show all posts

Sunday, May 15, 2022

BLE UART communication between ESP32-S3 (arduino-esp32) and HC-42 BLE Module

This post show how to implement BLE UART communication between NodeMCU ESP-S3-12K-Kit (in Arduino framework usiing arduino-esp32) and HC-42 BLE Module.

For HC-42 BLE Module, refer to last post "HC-42 BLE 5 Serial Port Communication Module".

For ESP-S3-12K-Kit (arduino-esp32 2.0.3) side, basically it is modified from "ESP32 BLE Arduino" > "BLE_client" example.

Note that in HC-42:
- Search UUID: FFF0
- Service UUID: FFE0
- Transparent data transmission UUID: FFE1

We have to follow it in arduino code in ESP-S3-12K-Kit.


ESP32S3_BLE_client_HC42.ino, modified from "ESP32 BLE Arduino" > "BLE_client" example, for HC-42.

/**
 * A BLE client example that is rich in capabilities.
 * There is a lot new capabilities implemented.
 * author unknown
 * updated by chegewara
 */

#include "BLEDevice.h"
//#include "BLEScan.h"

#define SEARCH_UUID "FFF0"
#define SERVICE_UUID "FFE0"
#define TRAN_UUID "FFE1"

// The remote service we wish to connect to.
static BLEUUID searchUUID(SEARCH_UUID);
static BLEUUID serviceUUID(SERVICE_UUID);
// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID(TRAN_UUID);

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("Notify callback for characteristic ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    Serial.println((char*)pData);
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    pClient->connect(myDevice); // if you pass BLEAdvertisedDevice
				// instead of address, it will be
				// recognized type of peer device 
				// address (public or private)
    Serial.println(" - Connected to server");
    pClient->setMTU(517); //set client to request maximum MTU from
						  //server (default is 23 otherwise)
  
    // Obtain a reference to the service we are after in the remote 
	// BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service of
	// the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our characteristic");

    // Read the value of the characteristic.
    if(pRemoteCharacteristic->canRead()) {
      std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteCharacteristic->canNotify())
      pRemoteCharacteristic->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and find the first one that advertises 
 * the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    // We have found a device, let us now see if it contains the service
	// we are looking for.
    if (advertisedDevice.haveServiceUUID() && 
		advertisedDevice.isAdvertisingService(searchUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to 
  // be informed when we have detected a new device. 
  // Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for 
  // and found the desired BLE Server with which we wish to connect.  
  // Now we connect to it.  Once we are connected we set the connected
  // flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, 
  // update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {
    String newValue = "Time since boot: " + String(millis()/1000)  +"\n";
    Serial.println("Setting new characteristic value to \"" + newValue + "\"");
    
    // Set the characteristic's value to be the array of bytes
	// that is actually a string.
    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
  }else if(doScan){
    BLEDevice::getScan()->start(0); // this is just example to start scan 
				    // after disconnect, most likely there
				    // is better way to do it in arduino
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop



ESP32S3_BLE_uart_client_HC42.ino, bi-directional BLE UART communication.
/**
 * A BLE UART client example run on ESP32-S3,
 * act as client, connect to HC-42, to establish
 * BLE UART communication.
 *
 * Modified from "ESP32 BLE Arduino" > "BLE_client"
 */

#include "BLEDevice.h"
//#include "BLEScan.h"

#define SEARCH_UUID "FFF0"
#define SERVICE_UUID "FFE0"
#define TRANS_UUID "FFE1"

// The remote service we wish to connect to.
static BLEUUID searchUUID(SEARCH_UUID);
static BLEUUID serviceUUID(SERVICE_UUID);
// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID(TRANS_UUID);

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {

    if (length > 0){
      Serial.printf("%i :\t", length);
      
      for (int i=0; i<length; i++)
          Serial.print((char) pData[i]);

      Serial.println();
    }
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    pClient->connect(myDevice); // if you pass BLEAdvertisedDevice 
				// instead of address, it will be 
				// recognized type of peer device 
				// address (public or private)
    Serial.println(" - Connected to server");
    pClient->setMTU(517); //set client to request maximum MTU from server 
                          //(default is 23 otherwise)
  
    // Obtain a reference to the service we are after in the 
	// remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service 
	// of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our characteristic");

    // Read the value of the characteristic.
    if(pRemoteCharacteristic->canRead()) {
      std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteCharacteristic->canNotify())
      pRemoteCharacteristic->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and find the first one that advertises 
 * the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    // We have found a device, let us now see if it contains the service 
	// we are looking for.
    if (advertisedDevice.haveServiceUUID() 
        && advertisedDevice.isAdvertisingService(searchUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  delay(1000);    //it's seem that adding delay here make it more stable
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be 
  // informed when we have detected a new device.  
  // Specify that we want active scanning and start the scan to 
  // run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for 
  // and found the desired BLE Server with which we wish to connect.  
  // Now we connect to it.  Once we are connected we set the connected 
  //flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, 
  // update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {

    if (Serial.available()) {
      String newValue = "";
      while(Serial.available()){
        char c = Serial.read();
        newValue += c;
      }
      newValue += "\n";
      Serial.println(newValue);
      pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
    } 
  }else if(doScan){
    BLEDevice::getScan()->start(0); // this is just example to start scan 
				    // after disconnect, most likely there 
				    // is better way to do it in arduino
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop



Thursday, May 12, 2022

arduino-esp32 2.0.3 add support for ESP32-S3, to develope in Arduino IDE.

With arduino-esp32 2.0.3, ESP32-S3 support added now. (~release notice)

To install arduino-esp32 to Arduino IDE:
(Install arduino-esp32 2 on Arduino IDE, to program ESP32-C3/S2/S3)

In MENU > File > Preference, make sure "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json" is added in Additional Board Manager URLs field.


In MENU > Tools > Board > Boards Manager...
Now you can search and install for "esp32 by Espressif Systems"


Then you can select ESP32S3 Dev Module



Try run on NodeMCU ESP-S3-12K-Kit

ESP32S3_info.ino, get ESP info.
#include <Esp.h>

void setup() {
  delay(500);
  Serial.begin(115200);
  delay(500);
  Serial.println("\n\n================================");
  Serial.printf("Chip Model: %s\n", ESP.getChipModel());
  Serial.printf("Chip Revision: %d\n", ESP.getChipRevision());
  Serial.printf("with %d core\n", ESP.getChipCores());
  Serial.printf("Flash Chip Size : %d \n", ESP.getFlashChipSize());
  Serial.printf("Flash Chip Speed : %d \n", ESP.getFlashChipSpeed());

  esp_chip_info_t chip_info;
  esp_chip_info(&chip_info);
  Serial.printf("\nFeatures included:\n %s\n %s\n %s\n %s\n %s\n",
      (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded flash" : "",
      (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "2.4GHz WiFi" : "",
      (chip_info.features & CHIP_FEATURE_BLE) ? "Bluetooth LE" : "",
      (chip_info.features & CHIP_FEATURE_BT) ? "Bluetooth Classic" : "",
      (chip_info.features & CHIP_FEATURE_IEEE802154) ? "IEEE 802.15.4" : "");
  
  Serial.println();


  Serial.println();
  Serial.println("\n- end of setup() -");

}

void loop() {
  // put your main code here, to run repeatedly:

}




ESP32S3_pins.ino, list pins with pre-defined function.
void setup() {
  // put your setup code here, to run once:
  delay(500);
  Serial.begin(115200);
  delay(500);
  Serial.println("\n\n================================");
  Serial.printf("Chip Model: %s %s %d\n",
                ESP.getChipModel(),
                "rev.",
                (int)ESP.getChipRevision());
  Serial.printf("with number of cores = %d\n", (int)ESP.getChipCores());
  Serial.println("================================");

#ifdef EXTERNAL_NUM_INTERRUPTS
  Serial.printf("EXTERNAL_NUM_INTERRUPTS = %d\n", EXTERNAL_NUM_INTERRUPTS);
#endif
#ifdef NUM_DIGITAL_PINS
  Serial.printf("NUM_DIGITAL_PINS = %d\n", NUM_DIGITAL_PINS);
#endif
#ifdef NUM_ANALOG_INPUTS
  Serial.printf("NUM_ANALOG_INPUTS = %d\n", NUM_ANALOG_INPUTS);
#endif
  Serial.println();
  Serial.printf("Default TX:   %d\n", TX);
  Serial.printf("Default RX:   %d\n", RX);
  Serial.println();
  Serial.printf("Default SDA:  %d\n", SDA);
  Serial.printf("Default SCL:  %d\n", SCL);
  Serial.println();
  Serial.printf("Default SS:   %d\n", SS);
  Serial.printf("Default MOSI: %d\n", MOSI);
  Serial.printf("Default MISO: %d\n", MISO);
  Serial.printf("Default SCK:  %d\n", SCK);

  Serial.println();
  Serial.printf("A0\tA1\tA2\tA3\tA4\tA5\tA6\tA7\tA8\tA9\n");
  Serial.printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", 
                 A0, A1, A2, A3, A4, A5, A6, A7, A8, A9);
  Serial.println();
  Serial.printf("A10\tA11\tA12\tA13\tA14\tA15\tA16\tA17\tA18\tA19\n");
  Serial.printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", 
                 A10, A11, A12, A13, A14, A15, A16, A17, A18, A19);
  Serial.println();

  Serial.printf("T1\tT2\tT3\tT4\tT5\tT6\tT7\tT8\tT9\tT10\n");
  Serial.printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", 
                 T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
  Serial.println();
  Serial.printf("T11\tT12\tT13\tT14\n");
  Serial.printf("%d\t%d\t%d\t%d\n", 
                 T11, T12, T13, T14);

  Serial.println("================================");
}

void loop() {
  // put your main code here, to run repeatedly:

}



ESP32S3_RGB.ino, control ESP-S3-12K-Kit on-board RGB LED.
#define LED_R 5
#define LED_G 6
#define LED_B 7

void setup() {
  delay(500);
  Serial.begin(115200);
  delay(500);
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);
  digitalWrite(LED_R, LOW);
  digitalWrite(LED_G, LOW);
  digitalWrite(LED_B, LOW);
  delay(1000);
  digitalWrite(LED_R, HIGH);
  digitalWrite(LED_G, HIGH);
  digitalWrite(LED_B, HIGH);
  delay(1000);
  digitalWrite(LED_R, LOW);
  digitalWrite(LED_G, LOW);
  digitalWrite(LED_B, LOW);
  delay(1000);

}

void loop() {
  digitalWrite(LED_R, HIGH);
  delay(2000);
  digitalWrite(LED_R, LOW);
  digitalWrite(LED_G, HIGH);
  delay(2000);
  digitalWrite(LED_G, LOW);
  digitalWrite(LED_B, HIGH);
  delay(2000);
  digitalWrite(LED_B, LOW);
  delay(2000); 
}




more exercise of ESP32-S3 (arduino-esp32):


Tuesday, April 26, 2022

ESP32-S2 (arduino-esp32) display on ILI9341 SPI TFT with Touch, using TFT_eSPI Library.

TFT_eSPI is a Arduino and PlatformIO IDE compatible TFT library optimized for the Raspberry Pi Pico (RP2040), STM32, ESP8266 and ESP32 that supports different driver chips.

Connection:


ILI9488
-------
1  - VCC	VCC
2  - GND	GND
3  - CS		34
4  - RESET	38
5  - DC		39
6  - SDI(MOSI)	35
7  - SCK	36
8  - LED	VCC
9  - SDO (MISO)	37
10 - T_CLK	(SCK)
11 - T_CS	40
12 - T_DIN	(MOSI)
13 - T_DO	(MISO)
14 - T_IRQ	no connection
In arduino-esp32, the default pins of ESP32S2 Dev Module for SPI are:
Default SS:         34
Default MOSI:    35
Default MISO:    37
Default SCK:      36

In TFT_eSPI, the SPI bus for the touch controller is shared with the TFT and only an additional chip select line is needed.

Setup TFT_eSPI Library:

This video show how to setup TFT_eSPI library in Arduino IDE, tested on ESP32-S2-Saola-1, with 2.4inch SPI Module ILI9341 SKU:MSP2402 with Touch.


Refer to "Tips" section in the TFT_eSPI page:

If you load a new copy of TFT_eSPI then it will overwrite your setups if they are kept within the TFT_eSPI folder. One way around this is to create a new folder in your Arduino library folder called "TFT_eSPI_Setups". You then place your custom setup.h files in there. After an upgrade simply edit the User_Setup_Select.h file to point to your custom setup file.

You can take this one step further and have your own setup select file and then you only need to replace the Setup.h line reference in User_Setup_Select.h to.

mySetup70_ESP32_S2_ILI9341.h, my custom setup.h for ESP32-S2 using ILI9341.
// Setup for the ESP32 S2 with ILI9341 display
// Note SPI DMA with ESP32 S2 is not currently supported
#define USER_SETUP_ID 70
// See SetupX_Template.h for all options available
#define ILI9341_DRIVER
/*
                    // Typical board default pins
#define TFT_CS   10 //     10 or 34

#define TFT_MOSI 11 //     11 or 35
#define TFT_SCLK 12 //     12 or 36
#define TFT_MISO 13 //     13 or 37

#define TFT_DC   14
#define TFT_RST  15
*/
#define TFT_MISO 37
#define TFT_MOSI 35
#define TFT_SCLK 36
#define TFT_CS   34
#define TFT_DC   39
#define TFT_RST  38

//#define TOUCH_CS 16 // Optional for touch screen
#define TOUCH_CS 40


#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF

#define SMOOTH_FONT

// FSPI port will be used unless the following is defined
#define USE_HSPI_PORT

//#define SPI_FREQUENCY  27000000
#define SPI_FREQUENCY  40000000   // Maximum for ILI9341

#define SPI_READ_FREQUENCY  6000000 // 6 MHz is the maximum SPI read speed for the ST7789V

#define SPI_TOUCH_FREQUENCY 2500000


Updated User_Setup_Select.h.
// This header file contains a list of user setup files and defines which one the
// compiler uses when the IDE performs a Verify/Compile or Upload.
//
// Users can create configurations for different Espressif boards and TFT displays.
// This makes selecting between hardware setups easy by "uncommenting" one line.

// The advantage of this hardware configuration method is that the examples provided
// with the library should work with different setups immediately without any other
// changes being needed. It also improves the portability of users sketches to other
// hardware configurations and compatible libraries.
//
// Create a shortcut to this file on your desktop to permit quick access for editing.
// Re-compile and upload after making and saving any changes to this file.

// Customised User_Setup files are stored in the "User_Setups" folder.

#ifndef USER_SETUP_LOADED //  Lets PlatformIO users define settings in
                          //  platformio.ini, see notes in "Tools" folder.

// Only ONE line below should be uncommented.  Add extra lines and files as needed.

//#include <User_Setup.h>           // Default setup is root library folder
#include <../TFT_eSPI_Setups/mySetup70_ESP32_S2_ILI9341.h>

//...




Wednesday, December 15, 2021

BLE between ESP32/ESP32C3 (arduino-esp32), notify DHT11 reading of temperature & humidity.

This exercise run on ESP32/ESP32-C3 to perform BLE communication, to notify data update. Both run on arduino-esp32 framework.


The BLE server run on ESP32-DevKitC V4, read DHT11 temperature & humidity, display on ST7789 SPI TFT, and notify connected client. The BLE client run on ESP32-C3-DevKitM-1, connect to BLE server, update SSD1306 I2C OLED once notified data updated.

Basically, the server copy from my former exercise "ESP32 + DHT11 temperature & humidity sensor with display on ST7789 and BLE function", the client copy from BLE_client example. 

But with my original setting (follow BLE_notify example) in server side:

  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);
both advertisedDevice.haveServiceUUID() and advertisedDevice.isAdvertisingService(serviceUUID) in client return false. So the client cannot find the server.

To solve it, I change the setting (follow BLE_server example):
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);
  pAdvertising->setMinPreferred(0x12);
then both advertisedDevice.haveServiceUUID() and advertisedDevice.isAdvertisingService(serviceUUID) in client return true, such that the server can be found.

ESP32_DHT_ST789_graphic_BLE_2021-12-14.ino, server side run on ESP32-DevKitC V4.
/*
   Execise run on ESP32 (ESP32-DevKitC V4) with arduino-esp32 2.0.1,
   read DHT11 Humidity & Temperature Sensor,
   and display on ST7789 SPI TFT, 2" IPS 240x320, with graph.
   BLE function added.

   Library needed:
   - DHT sensor library for ESPx by beegee_tokyo
   - Adafruit ST7735 and ST7789 Library by Adafruit
   - Adafruit GFX Library by Adafruit

    Modify from examples DHT_ESP32 of DHT sensor library for ESPx

    Connection between DHT11 and ESP32 (GPIO#)
    -----------------------------------------------
    DHT11         ESP32
    -----         -----
    VCC*          3V3
    DATA**        32
    NC
    GND           GND

 *  * - depends on module, my DHT11 module is 3V3~5V operate.

 *  ** - depends on your module, maybe you have to add a
    pull-up resistor (~10K Ohm) betwee DATA and VCC.

    Connection between ST7789 SPI and ESP32 (GPIO#)
    -----------------------------------------------
    ST7789 SPI    ESP32
    ----------    -----
    GND           GND
    VCC           3V3
    SCL           18
    SDA           23
    RES           26
    DC            25
    CS            33
    BLK           3V3

*/
#include "DHTesp.h"
#include <Ticker.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <Fonts/FreeMonoBold12pt7b.h>
#include <SPI.h>

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#ifndef ESP32
#pragma message(THIS EXAMPLE IS FOR ESP32 ONLY!)
#error Select ESP32 board.
#endif

DHTesp dht;

void tempTask(void *pvParameters);
bool getTemperature();
void triggerGetTemp();

/** Task handle for the light value read task */
TaskHandle_t tempTaskHandle = NULL;
/** Ticker for temperature reading */
Ticker tempTicker;
/** Comfort profile */
ComfortState cf;
/** Flag if task should run */
bool tasksEnabled = false;
/** Pin number for DHT11 data pin */
int dhtPin = 32;  //17;

//hardware SPI MOSI   23
//hardware SPI SCK    18
#define TFT_CS        33
#define TFT_RST       26
#define TFT_DC        25
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

bool rqsUpdate = false;
TempAndHumidity updateValues;

unsigned long prvUpdateMillis;

#define FRAME_TOPX    0
#define FRAME_TOPY    200
#define FRAME_WIDTH   240
#define FRAME_HEIGHT  100
#define FRAME_BOTTOMY FRAME_TOPY + FRAME_HEIGHT
#define SCR_HEIGHT    320

int idx = 0;
#define IDX_MAX     240

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
BLECharacteristic* pChar_temp = NULL;
BLECharacteristic* pChar_humi = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
#define CHAR_UUID_TEMP "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
#define CHAR_UUID_HUMI "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
      BLEDevice::startAdvertising();
      Serial.println("MyServerCallbacks.onConnect");
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
      Serial.println("MyServerCallbacks.onDisconnect");
    }
};

/**
   initTemp
   Setup DHT library
   Setup task and timer for repeated measurement
   @return bool
      true if task and timer are started
      false if task or timer couldn't be started
*/
bool initTemp() {
  byte resultValue = 0;
  // Initialize temperature sensor
  dht.setup(dhtPin, DHTesp::DHT11);
  Serial.println("DHT initiated");

  // Start task to get temperature
  xTaskCreatePinnedToCore(
    tempTask,                       /* Function to implement the task */
    "tempTask ",                    /* Name of the task */
    4000,                           /* Stack size in words */
    NULL,                           /* Task input parameter */
    5,                              /* Priority of the task */
    &tempTaskHandle,                /* Task handle. */
    1);                             /* Core where the task should run */

  if (tempTaskHandle == NULL) {
    Serial.println("Failed to start task for temperature update");
    return false;
  } else {
    // Start update of environment data every XX seconds
    tempTicker.attach(2, triggerGetTemp);
  }
  return true;
}

/**
   triggerGetTemp
   Sets flag dhtUpdated to true for handling in loop()
   called by Ticker getTempTimer
*/
void triggerGetTemp() {
  if (tempTaskHandle != NULL) {
    xTaskResumeFromISR(tempTaskHandle);
  }
}

/**
   Task to reads temperature from DHT11 sensor
   @param pvParameters
      pointer to task parameters
*/
void tempTask(void *pvParameters) {
  Serial.println("tempTask loop started");
  while (1) // tempTask loop
  {
    if (tasksEnabled) {
      // Get temperature values
      getTemperature();
    }
    // Got sleep again
    vTaskSuspend(NULL);
  }
}

/**
   getTemperature
   Reads temperature from DHT11 sensor
   @return bool
      true if temperature could be aquired
      false if aquisition failed
*/
bool getTemperature() {
  // Reading temperature for humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor)
  TempAndHumidity newValues = dht.getTempAndHumidity();
  // Check if any reads failed and exit early (to try again).
  if (dht.getStatus() != 0) {
    Serial.println("DHT11 error status: " + String(dht.getStatusString()));
    return false;
  }

  rqsUpdate = true;
  updateValues = newValues;
  return true;
}

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("DHT ESP32 example with tasks");

  //init DHT
  initTemp();
  // Signal end of setup() to tasks
  tasksEnabled = true;

  //init BLE
  // Create the BLE Device
  BLEDevice::init("ESP32-DHT11");

  // Create the BLE Server
  pServer = BLEDevice::createServer();

  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic for temp and humi
  pChar_temp = pService->createCharacteristic(
                 CHAR_UUID_TEMP,
                 BLECharacteristic::PROPERTY_READ   |
                 BLECharacteristic::PROPERTY_WRITE  |
                 BLECharacteristic::PROPERTY_NOTIFY |
                 BLECharacteristic::PROPERTY_INDICATE
               );
  pChar_humi = pService->createCharacteristic(
                 CHAR_UUID_HUMI,
                 BLECharacteristic::PROPERTY_READ   |
                 BLECharacteristic::PROPERTY_WRITE  |
                 BLECharacteristic::PROPERTY_NOTIFY |
                 BLECharacteristic::PROPERTY_INDICATE
               );

  pChar_temp->addDescriptor(new BLE2902());
  pChar_humi->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  
  // Erik updated@2021-12-14
  //pAdvertising->setScanResponse(false);
  //pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  //print for information only
  Serial.println("pAdvertising->setScanResponse(true)");
  Serial.println("pAdvertising->setMinPreferred(0x06)");
  Serial.println("pAdvertising->setMinPreferred(0x12)");
  
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");


  //init ST7789
  tft.init(240, 320);           // Init ST7789 320x240
  tft.setRotation(2);
  tft.setFont(&FreeMonoBold12pt7b);
  tft.setTextWrap(true);

  tft.fillScreen(ST77XX_RED);
  delay(300);
  tft.fillScreen(ST77XX_GREEN);
  delay(300);
  tft.fillScreen(ST77XX_BLUE);
  delay(300);

  tft.setCursor(0, 0);
  tft.setTextColor(ST77XX_RED);
  tft.print("\n");
  tft.print("ESP32 + DHT11 + ST7789\n");

  prvUpdateMillis = millis();

}

void loop() {
  if (!tasksEnabled) {
    // Wait 2 seconds to let system settle down
    delay(2000);
    // Enable task that will read values from the DHT sensor
    tasksEnabled = true;
    if (tempTaskHandle != NULL) {
      vTaskResume(tempTaskHandle);
    }
  }

  if (rqsUpdate) {

    unsigned long curUpdateMillis = millis();

    tft.fillRect(0, 53, 240, 75, ST77XX_BLUE );

    tft.setCursor(0, 70);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" temp.: " + String(updateValues.temperature));
    tft.setCursor(0, 95);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" humi.: " + String(updateValues.humidity));
    tft.setCursor(0, 115);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" mills.: " + String(curUpdateMillis - prvUpdateMillis));
    prvUpdateMillis = curUpdateMillis;


    if (idx == 0) {
      tft.fillRect(FRAME_TOPX, FRAME_TOPY,
                   FRAME_WIDTH, SCR_HEIGHT - FRAME_TOPY,
                   ST77XX_BLUE);
    }

    tft.drawLine(
      FRAME_TOPX + idx, FRAME_BOTTOMY,
      FRAME_TOPX + idx, FRAME_BOTTOMY - (int)updateValues.temperature,
      ST77XX_WHITE);

    idx++;
    if (idx >= IDX_MAX)
      idx = 0;

    char bufTemp[5];
    char bufHumi[5];
    //convert floating point value to String
    dtostrf(updateValues.temperature, 0, 2, bufTemp);
    dtostrf(updateValues.humidity, 0, 2, bufHumi);

    pChar_temp->setValue((uint8_t*)bufTemp, 5);
    pChar_temp->notify();
    pChar_humi->setValue((uint8_t*)bufHumi, 5);
    pChar_humi->notify();

    //Serial.println(" T:" + String(updateValues.temperature) + " H:" + String(updateValues.humidity));
    rqsUpdate = false;


  }

  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    Serial.println("disconnecting");
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    Serial.println("connecting");
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }

  yield();
}


ESP32C3_BLE_client.ino, client side run on ESP32-C3-DevKitM-1. Copy from BLE_client example, with UUID updated.
/**
 * A BLE client example that is rich in capabilities.
 * There is a lot new capabilities implemented.
 * author unknown
 * updated by chegewara
 */

#include "BLEDevice.h"
//#include "BLEScan.h"

#define SERVICE_UUID "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
#define CHAR_UUID_TEMP "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
#define CHAR_UUID_HUMI "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

// The remote service we wish to connect to.
static BLEUUID serviceUUID(SERVICE_UUID);
// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID(CHAR_UUID_TEMP);

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("Notify callback for characteristic ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    Serial.println((char*)pData);
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
    Serial.println(" - Connected to server");
    pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)
  
    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our characteristic");

    // Read the value of the characteristic.
    if(pRemoteCharacteristic->canRead()) {
      std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteCharacteristic->canNotify())
      pRemoteCharacteristic->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    if(advertisedDevice.haveServiceUUID()){
      Serial.println("- advertisedDevice.haveServiceUUID()");
    }
    if(advertisedDevice.isAdvertisingService(serviceUUID)){
      Serial.println("- advertisedDevice.isAdvertisingService(serviceUUID)");
    }

    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

      Serial.println("*** device found ***");

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {
    String newValue = "Time since boot: " + String(millis()/1000);
    Serial.println("Setting new characteristic value to \"" + newValue + "\"");
    
    // Set the characteristic's value to be the array of bytes that is actually a string.
    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
  }else if(doScan){
    BLEDevice::getScan()->start(0);  // this is just example to start scan after disconnect, most likely there is better way to do it in arduino
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop




ESP32C3_BLE_DHT11_client.ino, updated to recognize temperature & humidity.
/**
 * modified from BLE_client
 * to work with ESP32_DHT_ST789_graphic_BLE.ino,
 * to monitor temp/humi.
 */

#include "BLEDevice.h"
//#include "BLEScan.h"

#define SERVICE_UUID "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
#define CHAR_UUID_TEMP "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
#define CHAR_UUID_HUMI "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

// The remote service we wish to connect to.
static BLEUUID serviceUUID(SERVICE_UUID);
// The characteristic of the remote service we are interested in.
static BLEUUID  charUUID_TEMP(CHAR_UUID_TEMP);
static BLEUUID  charUUID_HUMI(CHAR_UUID_HUMI);

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteChar_temp;
static BLERemoteCharacteristic* pRemoteChar_humi;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {

    String strCharUUID = pBLERemoteCharacteristic->getUUID().toString().c_str();
    Serial.printf("Notify callback for characteristic: ");
    strCharUUID.toUpperCase();
    Serial.println(strCharUUID);

    if(strCharUUID.equals(CHAR_UUID_TEMP)){
      Serial.print("temp: ");
      for (int i=0; i<length; i++)
          Serial.print((char) pData[i]);
    }else if(strCharUUID.equals(CHAR_UUID_HUMI)){
      Serial.print("humi: ");
      for (int i=0; i<length; i++)
          Serial.print((char) pData[i]);
    }

    Serial.println();
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    // if you pass BLEAdvertisedDevice instead of address, 
    // it will be recognized type of peer device address (public or private)
    pClient->connect(myDevice);  
    Serial.println(" - Connected to server");
    //set client to request maximum MTU from server (default is 23 otherwise)
    pClient->setMTU(517); 
  
    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service 
    // of the remote BLE server.
    pRemoteChar_temp = pRemoteService->getCharacteristic(charUUID_TEMP);
    if (pRemoteChar_temp == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID_TEMP.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found temp characteristic");

    pRemoteChar_humi = pRemoteService->getCharacteristic(charUUID_HUMI);
    if (pRemoteChar_humi == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID_TEMP.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found humi characteristic");

    // Read the value of the characteristic.
    if(pRemoteChar_temp->canRead()) {
      std::string value = pRemoteChar_temp->readValue();
      Serial.print("The characteristic temp value was: ");
      Serial.println(value.c_str());
    }
    if(pRemoteChar_humi->canRead()) {
      std::string value = pRemoteChar_humi->readValue();
      Serial.print("The characteristic humi value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteChar_temp->canNotify())
      pRemoteChar_temp->registerForNotify(notifyCallback);
    if(pRemoteChar_humi->canNotify())
      pRemoteChar_humi->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and 
 * find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    if(advertisedDevice.haveServiceUUID()){
      Serial.println("- haveServiceUUID()");
    }
    if(advertisedDevice.isAdvertisingService(serviceUUID)){
      Serial.println("- isAdvertisingService(serviceUUID)");
    }

    // We have found a device, 
    // let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && 
      advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

      Serial.println("*** device found ***");

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, 
  // update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {

  }else if(doScan){
    // this is just example to start scan after disconnect, 
    // most likely there is better way to do it in arduino
    BLEDevice::getScan()->start(0);  
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop




ESP32C3_BLE_DHT11_SSD1306_client.ino, with display on SSD1306 I2C OLED. For the SSD1306 part, refer to last exercise "ESP32-C3-DevKitM-1 display on ssd1306 I2C OLED using Adafruit SSD1306 library".
/**
 * modified from BLE_client
 * to work with ESP32_DHT_ST789_graphic_BLE.ino,
 * to monitor temp/humi.
 * and display on SSD1306 I2C OLED
 */

#include "BLEDevice.h"
//#include "BLEScan.h"
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define SDA_pin 3
#define SCL_pin 2
#define OLED_RESET     -1
#define SCREEN_ADDRESS 0x3C //0x3D ///
Adafruit_SSD1306 display;

#define SERVICE_UUID "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
#define CHAR_UUID_TEMP "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
#define CHAR_UUID_HUMI "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

// The remote service we wish to connect to.
static BLEUUID serviceUUID(SERVICE_UUID);
// The characteristic of the remote service we are interested in.
static BLEUUID  charUUID_TEMP(CHAR_UUID_TEMP);
static BLEUUID  charUUID_HUMI(CHAR_UUID_HUMI);

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteChar_temp;
static BLERemoteCharacteristic* pRemoteChar_humi;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {

    String strCharUUID = pBLERemoteCharacteristic->getUUID().toString().c_str();
    Serial.printf("Notify callback for characteristic: ");
    strCharUUID.toUpperCase();
    Serial.println(strCharUUID);

    if(strCharUUID.equals(CHAR_UUID_TEMP)){
      Serial.print("temp: ");

      String strTemp = (char*)pData;
      Serial.print(strTemp);
      displayTemp(strTemp);
      
    }else if(strCharUUID.equals(CHAR_UUID_HUMI)){
      Serial.print("humi: ");

      String strHumi = (char*)pData;
      Serial.print(strHumi);
      displayHumi(strHumi);
    }

    Serial.println();
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
    displayPrompt("onConnect");
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
    displayPrompt("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    // if you pass BLEAdvertisedDevice instead of address, 
    // it will be recognized type of peer device address (public or private)
    pClient->connect(myDevice);  
    Serial.println(" - Connected to server");
    //set client to request maximum MTU from server (default is 23 otherwise)
    pClient->setMTU(517); 
  
    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service 
    // of the remote BLE server.
    pRemoteChar_temp = pRemoteService->getCharacteristic(charUUID_TEMP);
    if (pRemoteChar_temp == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID_TEMP.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found temp characteristic");

    pRemoteChar_humi = pRemoteService->getCharacteristic(charUUID_HUMI);
    if (pRemoteChar_humi == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID_TEMP.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found humi characteristic");

    // Read the value of the characteristic.
    if(pRemoteChar_temp->canRead()) {
      std::string value = pRemoteChar_temp->readValue();
      Serial.print("The characteristic temp value was: ");
      Serial.println(value.c_str());
      displayTemp(value.c_str());
    }
    if(pRemoteChar_humi->canRead()) {
      std::string value = pRemoteChar_humi->readValue();
      Serial.print("The characteristic humi value was: ");
      Serial.println(value.c_str());
      displayHumi(value.c_str());
    }

    if(pRemoteChar_temp->canNotify())
      pRemoteChar_temp->registerForNotify(notifyCallback);
    if(pRemoteChar_humi->canNotify())
      pRemoteChar_humi->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and 
 * find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    if(advertisedDevice.haveServiceUUID()){
      Serial.println("- haveServiceUUID()");
    }
    if(advertisedDevice.isAdvertisingService(serviceUUID)){
      Serial.println("- isAdvertisingService(serviceUUID)");
    }

    // We have found a device, 
    // let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && 
      advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

      Serial.println("*** device found ***");
      displayPrompt("found");

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks

void initScreen(){

  Wire.setPins(SDA_pin,SCL_pin);
  display = Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  display.clearDisplay();
  display.display();
  delay(500);

  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  display.setCursor(10, 10);
  display.printf("ESP32C3");
  display.setCursor(10, 28);
  display.printf("BLE");

  display.fillRect(0, 0, display.width()-1, display.height()-1, SSD1306_INVERSE);
  display.display();
  delay(3000);

  // Clear the buffer
  display.clearDisplay();
  display.display();

}


void displayTemp(String temp){

  //erase display of old temp
  display.fillRect(40, 10, 80, 18, SSD1306_BLACK);
  
  display.setCursor(10, 10);
  display.setTextSize(1);
  display.print("temp");
  display.setCursor(40, 10);
  display.setTextSize(2);
  display.print(temp);
  display.display();
}

void displayHumi(String humi){

  //erase display of old humi
  display.fillRect(40, 30, 80, 18, SSD1306_BLACK);
  
  display.setCursor(10, 30);
  display.setTextSize(1);
  display.print("humi");
  display.setCursor(40, 30);
  display.setTextSize(2);
  display.print(humi);
  display.display();
}

void displayPrompt(String prompt){

  //erase display of old temp
  display.fillRect(10, 10, 110, 40, SSD1306_BLACK);

  display.setCursor(40, 10);
  display.setTextSize(1);
  display.print(prompt);
  display.display();
}


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");

  initScreen();
  
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
  
  displayPrompt("Scan");
  Serial.println("Scan");
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, 
  // update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {

  }else if(doScan){
    // this is just example to start scan after disconnect, 
    // most likely there is better way to do it in arduino
    displayPrompt("Scan");
    Serial.println("Scan");
    BLEDevice::getScan()->start(0);  
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop


Sunday, December 12, 2021

arduino-esp32, ESP32-C3-DevKitM-1 display on ssd1306 I2C OLED using Adafruit SSD1306 library

This post show how to driver 128x64 ssd1306 I2C OLED on ESP32-C3-DevKitM-1 (arduino-esp32) using Adafruit SSD1306 library.


In Arduino IDE, install Adafruit SSD1306 library.


Open ssd1306_128x64_i2c example.


This exercise run on ESP32-C3-DevKitM-1, but the default I2C SDA pin (GPIO8) is connected to on-board RGB LED. (ref: arduino-esp32 (ESP32-C3) scan i2c device address, using custom SDA and SCL) In my exercise SDA/SCL are re-allocated to GPIO3 and GPIO2.

Here list the modification I apply on ssd1306_128x64_i2c example,marked in RED:
		.
		.
		.
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...

/* remarked by Erik 2021-12-13
 * On my SSD1306 128x64 OLED
 * - No RESET pin
 * - I2C Address = 0x3C
 * - I have to re-allocate I2C SDA/SCL, due to conflict with onboard RGB LED.
 * - SDA = 3
 * - SCL = 2
 */
#define SDA_pin 3
#define SCL_pin 2
#define OLED_RESET     -1 //4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C //0x3D /// ...
//Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_SSD1306 display;

#define NUMFLAKES     10 // Number of snowflakes in the animation example
		.
		.
		.
void setup() {
  Serial.begin(9600);

  // by Erik 2021-12-13
  // Call Wire.setPins to assign SDA/SCL pins
  Wire.setPins(SDA_pin,SCL_pin);
  display = Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

		.
		.
		.
		


arduino-esp32 (ESP32-C3) scan i2c device address, using custom SDA and SCL.

i2c_scanner is a simple sketch scans the I2C-bus for devices. If a device is found, it is reported to the Arduino serial monitor.

With arduino-esp32, the default I2C of "ESP32C3 Dev Modul" is assigned on:
- SDA: GPIO8
- SCL: GPIO9
(ref: arduino-esp32, list the pre-defined function pins of ESP32C3/S2 Dev Module)

But on ESP32-C3-DevKitM-1,GPIO8 is connected to on-board RGB LED (WS2812).
(ref: ESP32-C3-DevKitM-1 User Guide)

So I have to assign I2C to other custom SDA/SCL pins, by calling   Wire.setPins() before Wire.begin().

  Wire.setPins(SDA_pin, SCL_pin);
  Wire.begin();
How it run on ESP32-C3-DevKitM-1, with SSD1306 conected. The detected I2C address is 0x3C.



Next:
~ how it applied to work with ssd1306 I2C OLED.

Monday, December 6, 2021

arduino-esp32: ESP32 + DHT11 temperature & humidity sensor with display on ST7789 and BLE function

Exercise on ESP32 (Arduino framework) work with DHT11 temperature & humidity sensor, with display on ST7789 SPI LCD and also with BLE function.

The DHT11 (or DHT22 and similar) are cheap temperature and humidity sensors. The communicate with a uC is over a single wire.

The electric connection to the ESP32 is very simple, as the DHT series can be powered direct with 3.3V. Only 3 wires are needed: VCC, GND and the data line.

Important is that a 10kΩ or at least 4.7kΩ resistor is needed between the data line and VCC. Sometimes this resistor is already integrated in the module, sometimes its necessary to add it.

ref: https://desire.giesecke.tk/index.php/2018/01/30/esp32-dht11/



Library used:

DHT sensor library for ESPx by beegee_tokyo is used in this exercise.

To display on ST7789 SPI SPI LCD, Adafruit ST7735 and ST7789 Library and Adafruit GFX Library are used. (related: ESP32-C3/arduino-esp32 to display on ST7735 and ST7789 SPI LCDs)


Connection:


	Connection between DHT11 and ESP32 (GPIO#)
	-----------------------------------------------
	DHT11         ESP32
	-----         -----
	VCC*          3V3
	DATA**        32
	NC    
	GND           GND

	* - depends on module, my DHT11 module is 3V3~5V operate. 

	** - depends on your module, maybe you have to add a 
	     pull-up resistor (~10K Ohm) betwee DATA and VCC.

	Connection between ST7789 SPI and ESP32 (GPIO#)
	-----------------------------------------------
	ST7789 SPI    ESP32
	----------    -----
	GND           GND
	VCC           3V3
	SCL           18
	SDA           23
	RES           26
	DC            25
	CS            33
	BLK           3V3
 

Exercise code:

ESP32_DHT_ST789.ino, modified from DHT_ESP32 example of DHT sensor library for ESPx, with interface to ST7789 SPI LCD.

#include "DHTesp.h"
#include <Ticker.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <Fonts/FreeMonoBold12pt7b.h>
#include <SPI.h>

#ifndef ESP32
#pragma message(THIS EXAMPLE IS FOR ESP32 ONLY!)
#error Select ESP32 board.
#endif

/**************************************************************/
/* Example how to read DHT sensors from an ESP32 using multi- */
/* tasking.                                                   */
/* This example depends on the Ticker library to wake up      */
/* the task every 5  seconds                                  */
/**************************************************************/

DHTesp dht;

void tempTask(void *pvParameters);
bool getTemperature();
void triggerGetTemp();

/** Task handle for the light value read task */
TaskHandle_t tempTaskHandle = NULL;
/** Ticker for temperature reading */
Ticker tempTicker;
/** Comfort profile */
ComfortState cf;
/** Flag if task should run */
bool tasksEnabled = false;
/** Pin number for DHT11 data pin */
int dhtPin = 32;  //17;

#define TFT_CS        33
#define TFT_RST       26
#define TFT_DC        25
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

bool rqsUpdate = false;
TempAndHumidity updateValues;


/**
 * initTemp
 * Setup DHT library
 * Setup task and timer for repeated measurement
 * @return bool
 *    true if task and timer are started
 *    false if task or timer couldn't be started
 */
bool initTemp() {
  byte resultValue = 0;
  // Initialize temperature sensor
	dht.setup(dhtPin, DHTesp::DHT11);
	Serial.println("DHT initiated");

  // Start task to get temperature
	xTaskCreatePinnedToCore(
			tempTask,                       /* Function to implement the task */
			"tempTask ",                    /* Name of the task */
			4000,                           /* Stack size in words */
			NULL,                           /* Task input parameter */
			5,                              /* Priority of the task */
			&tempTaskHandle,                /* Task handle. */
			1);                             /* Core where the task should run */

  if (tempTaskHandle == NULL) {
    Serial.println("Failed to start task for temperature update");
    return false;
  } else {
    // Start update of environment data every 20 seconds
    tempTicker.attach(5, triggerGetTemp);
  }
  return true;
}

/**
 * triggerGetTemp
 * Sets flag dhtUpdated to true for handling in loop()
 * called by Ticker getTempTimer
 */
void triggerGetTemp() {
  if (tempTaskHandle != NULL) {
	   xTaskResumeFromISR(tempTaskHandle);
  }
}

/**
 * Task to reads temperature from DHT11 sensor
 * @param pvParameters
 *    pointer to task parameters
 */
void tempTask(void *pvParameters) {
	Serial.println("tempTask loop started");
	while (1) // tempTask loop
  {
    if (tasksEnabled) {
      // Get temperature values
			getTemperature();
		}
    // Got sleep again
		vTaskSuspend(NULL);
	}
}

/**
 * getTemperature
 * Reads temperature from DHT11 sensor
 * @return bool
 *    true if temperature could be aquired
 *    false if aquisition failed
*/
bool getTemperature() {
	// Reading temperature for humidity takes about 250 milliseconds!
	// Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor)
  TempAndHumidity newValues = dht.getTempAndHumidity();
	// Check if any reads failed and exit early (to try again).
	if (dht.getStatus() != 0) {
		Serial.println("DHT11 error status: " + String(dht.getStatusString()));
		return false;
	}

	float heatIndex = dht.computeHeatIndex(newValues.temperature, newValues.humidity);
  float dewPoint = dht.computeDewPoint(newValues.temperature, newValues.humidity);
  float cr = dht.getComfortRatio(cf, newValues.temperature, newValues.humidity);

  String comfortStatus;
  switch(cf) {
    case Comfort_OK:
      comfortStatus = "Comfort_OK";
      break;
    case Comfort_TooHot:
      comfortStatus = "Comfort_TooHot";
      break;
    case Comfort_TooCold:
      comfortStatus = "Comfort_TooCold";
      break;
    case Comfort_TooDry:
      comfortStatus = "Comfort_TooDry";
      break;
    case Comfort_TooHumid:
      comfortStatus = "Comfort_TooHumid";
      break;
    case Comfort_HotAndHumid:
      comfortStatus = "Comfort_HotAndHumid";
      break;
    case Comfort_HotAndDry:
      comfortStatus = "Comfort_HotAndDry";
      break;
    case Comfort_ColdAndHumid:
      comfortStatus = "Comfort_ColdAndHumid";
      break;
    case Comfort_ColdAndDry:
      comfortStatus = "Comfort_ColdAndDry";
      break;
    default:
      comfortStatus = "Unknown:";
      break;
  };

  Serial.println(" T:" + String(newValues.temperature)
                + " H:" + String(newValues.humidity) 
                + " I:" + String(heatIndex) 
                + " D:" + String(dewPoint) 
                + " " + comfortStatus);
	rqsUpdate = true;
	updateValues = newValues;
	return true;
}

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("DHT ESP32 example with tasks");

  //init DHT
  initTemp();
  // Signal end of setup() to tasks
  tasksEnabled = true;

  //init ST7789
  tft.init(240, 320);           // Init ST7789 320x240
  tft.setRotation(3);
  tft.setFont(&FreeMonoBold12pt7b);
  tft.setTextWrap(true);
  
  tft.fillScreen(ST77XX_RED);
  delay(300);
  tft.fillScreen(ST77XX_GREEN);
  delay(300);
  tft.fillScreen(ST77XX_BLUE);
  delay(300);
  tft.fillScreen(ST77XX_BLACK);
  delay(300);

  tft.setCursor(0, 0);
  tft.setTextColor(ST77XX_RED);
  
  tft.setCursor(0, 0);
  tft.setTextColor(ST77XX_RED);
  tft.print("\n");
  tft.print("ESP32 + DHT11 + ST7789\n");

}

void loop() {
  if (!tasksEnabled) {
    // Wait 2 seconds to let system settle down
    delay(2000);
    // Enable task that will read values from the DHT sensor
    tasksEnabled = true;
    if (tempTaskHandle != NULL) {
			vTaskResume(tempTaskHandle);
		}
  }

  if(rqsUpdate){

    tft.fillRect(10, 30, 300, 53, ST77XX_BLACK);
    
    tft.setCursor(5, 50);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" temperature:  " + String(updateValues.temperature));
    tft.setCursor(5, 75);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" humidity:     " + String(updateValues.humidity));
    
    Serial.println(" T:" + String(updateValues.temperature) + " H:" + String(updateValues.humidity));
    rqsUpdate = false;
  }
  
  yield();
}


ESP32_DHT_ST789_graphic.ino, display on ST7789 SPI LCD with graph.
/*
 * Execise run on ESP32 (ESP32-DevKitC V4) with arduino-esp32 2.0.1,
 * read DHT11 Humidity & Temperature Sensor,
 * and display on ST7789 SPI TFT, 2" IPS 240x320, with graph.
 * 
 * Library needed: 
 * - DHT sensor library for ESPx by beegee_tokyo
 * - Adafruit ST7735 and ST7789 Library by Adafruit 
 * - Adafruit GFX Library by Adafruit
 * 
 *  Modify from examples DHT_ESP32 of DHT sensor library for ESPx
 *  
 *  Connection between DHT11 and ESP32 (GPIO#)
 *  -----------------------------------------------
 *  DHT11         ESP32
 *  -----         -----
 *  VCC*          3V3
 *  DATA**        32
 *  NC    
 *  GND           GND
 *  
 *  * - depends on module, my DHT11 module is 3V3~5V operate. 
 *  
 *  ** - depends on your module, maybe you have to add a 
 *  pull-up resistor (~10K Ohm) betwee DATA and VCC.
 *  
 *  Connection between ST7789 SPI and ESP32 (GPIO#)
 *  -----------------------------------------------
 *  ST7789 SPI    ESP32
 *  ----------    -----
 *  GND           GND
 *  VCC           3V3
 *  SCL           18
 *  SDA           23
 *  RES           26
 *  DC            25
 *  CS            33
 *  BLK           3V3
 *  
 */
#include "DHTesp.h"
#include <Ticker.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <Fonts/FreeMonoBold12pt7b.h>
#include <SPI.h>

#ifndef ESP32
#pragma message(THIS EXAMPLE IS FOR ESP32 ONLY!)
#error Select ESP32 board.
#endif

DHTesp dht;

void tempTask(void *pvParameters);
bool getTemperature();
void triggerGetTemp();

/** Task handle for the light value read task */
TaskHandle_t tempTaskHandle = NULL;
/** Ticker for temperature reading */
Ticker tempTicker;
/** Comfort profile */
ComfortState cf;
/** Flag if task should run */
bool tasksEnabled = false;
/** Pin number for DHT11 data pin */
int dhtPin = 32;  //17;

//hardware SPI MOSI   23
//hardware SPI SCK    18
#define TFT_CS        33
#define TFT_RST       26
#define TFT_DC        25
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

bool rqsUpdate = false;
TempAndHumidity updateValues;

unsigned long prvUpdateMillis;

#define FRAME_TOPX    0
#define FRAME_TOPY    200
#define FRAME_WIDTH   240
#define FRAME_HEIGHT  100
#define FRAME_BOTTOMY FRAME_TOPY + FRAME_HEIGHT
#define SCR_HEIGHT    320

int idx = 0;
#define IDX_MAX     240

/**
 * initTemp
 * Setup DHT library
 * Setup task and timer for repeated measurement
 * @return bool
 *    true if task and timer are started
 *    false if task or timer couldn't be started
 */
bool initTemp() {
  byte resultValue = 0;
  // Initialize temperature sensor
	dht.setup(dhtPin, DHTesp::DHT11);
	Serial.println("DHT initiated");

  // Start task to get temperature
	xTaskCreatePinnedToCore(
			tempTask,                       /* Function to implement the task */
			"tempTask ",                    /* Name of the task */
			4000,                           /* Stack size in words */
			NULL,                           /* Task input parameter */
			5,                              /* Priority of the task */
			&tempTaskHandle,                /* Task handle. */
			1);                             /* Core where the task should run */

  if (tempTaskHandle == NULL) {
    Serial.println("Failed to start task for temperature update");
    return false;
  } else {
    // Start update of environment data every XX seconds
    tempTicker.attach(2, triggerGetTemp);
  }
  return true;
}

/**
 * triggerGetTemp
 * Sets flag dhtUpdated to true for handling in loop()
 * called by Ticker getTempTimer
 */
void triggerGetTemp() {
  if (tempTaskHandle != NULL) {
	   xTaskResumeFromISR(tempTaskHandle);
  }
}

/**
 * Task to reads temperature from DHT11 sensor
 * @param pvParameters
 *    pointer to task parameters
 */
void tempTask(void *pvParameters) {
	Serial.println("tempTask loop started");
	while (1) // tempTask loop
  {
    if (tasksEnabled) {
      // Get temperature values
			getTemperature();
		}
    // Got sleep again
		vTaskSuspend(NULL);
	}
}

/**
 * getTemperature
 * Reads temperature from DHT11 sensor
 * @return bool
 *    true if temperature could be aquired
 *    false if aquisition failed
*/
bool getTemperature() {
	// Reading temperature for humidity takes about 250 milliseconds!
	// Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor)
  TempAndHumidity newValues = dht.getTempAndHumidity();
	// Check if any reads failed and exit early (to try again).
	if (dht.getStatus() != 0) {
		Serial.println("DHT11 error status: " + String(dht.getStatusString()));
		return false;
	}

	float heatIndex = dht.computeHeatIndex(newValues.temperature, newValues.humidity);
  float dewPoint = dht.computeDewPoint(newValues.temperature, newValues.humidity);
  float cr = dht.getComfortRatio(cf, newValues.temperature, newValues.humidity);

  String comfortStatus;
  switch(cf) {
    case Comfort_OK:
      comfortStatus = "Comfort_OK";
      break;
    case Comfort_TooHot:
      comfortStatus = "Comfort_TooHot";
      break;
    case Comfort_TooCold:
      comfortStatus = "Comfort_TooCold";
      break;
    case Comfort_TooDry:
      comfortStatus = "Comfort_TooDry";
      break;
    case Comfort_TooHumid:
      comfortStatus = "Comfort_TooHumid";
      break;
    case Comfort_HotAndHumid:
      comfortStatus = "Comfort_HotAndHumid";
      break;
    case Comfort_HotAndDry:
      comfortStatus = "Comfort_HotAndDry";
      break;
    case Comfort_ColdAndHumid:
      comfortStatus = "Comfort_ColdAndHumid";
      break;
    case Comfort_ColdAndDry:
      comfortStatus = "Comfort_ColdAndDry";
      break;
    default:
      comfortStatus = "Unknown:";
      break;
  };

  Serial.println(" T:" + String(newValues.temperature)
                + " H:" + String(newValues.humidity) 
                + " I:" + String(heatIndex) 
                + " D:" + String(dewPoint) 
                + " " + comfortStatus);
	rqsUpdate = true;
	updateValues = newValues;
	return true;
}

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("DHT ESP32 example with tasks");

  //init DHT
  initTemp();
  // Signal end of setup() to tasks
  tasksEnabled = true;

  //init ST7789
  tft.init(240, 320);           // Init ST7789 320x240
  tft.setRotation(2);
  tft.setFont(&FreeMonoBold12pt7b);
  tft.setTextWrap(true);
  
  tft.fillScreen(ST77XX_RED);
  delay(300);
  tft.fillScreen(ST77XX_GREEN);
  delay(300);
  tft.fillScreen(ST77XX_BLUE);
  delay(300);

  tft.setCursor(0, 0);
  tft.setTextColor(ST77XX_RED);
  tft.print("\n");
  tft.print("ESP32 + DHT11 + ST7789\n");

  prvUpdateMillis = millis();

}

void loop() {
  if (!tasksEnabled) {
    // Wait 2 seconds to let system settle down
    delay(2000);
    // Enable task that will read values from the DHT sensor
    tasksEnabled = true;
    if (tempTaskHandle != NULL) {
			vTaskResume(tempTaskHandle);
		}
  }

  if(rqsUpdate){

    unsigned long curUpdateMillis = millis();

    tft.fillRect(0, 53, 240, 75, ST77XX_BLUE );
    
    tft.setCursor(0, 70);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" temp.: " + String(updateValues.temperature));
    tft.setCursor(0, 95);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" humi.: " + String(updateValues.humidity));
    tft.setCursor(0, 115);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" mills.: " + String(curUpdateMillis-prvUpdateMillis));
    prvUpdateMillis = curUpdateMillis;


    if(idx==0){
      tft.fillRect(FRAME_TOPX, FRAME_TOPY, 
                  FRAME_WIDTH, SCR_HEIGHT-FRAME_TOPY, 
                  ST77XX_BLUE);
    }
  
    tft.drawLine(
      FRAME_TOPX+idx, FRAME_BOTTOMY, 
      FRAME_TOPX+idx, FRAME_BOTTOMY-(int)updateValues.temperature, 
      ST77XX_WHITE);

    idx++;
    if(idx >= IDX_MAX)
      idx = 0;
    
    Serial.println(" T:" + String(updateValues.temperature) + " H:" + String(updateValues.humidity));
    rqsUpdate = false;
  }
  
  yield();
}


ESP32_DHT_ST789_graphic_BLE.ino, with display on ST7789 SPI LCD, and BLE function added.
/*
   Execise run on ESP32 (ESP32-DevKitC V4) with arduino-esp32 2.0.1,
   read DHT11 Humidity & Temperature Sensor,
   and display on ST7789 SPI TFT, 2" IPS 240x320, with graph.
   BLE function added.

   Library needed:
   - DHT sensor library for ESPx by beegee_tokyo
   - Adafruit ST7735 and ST7789 Library by Adafruit
   - Adafruit GFX Library by Adafruit

    Modify from examples DHT_ESP32 of DHT sensor library for ESPx

    Connection between DHT11 and ESP32 (GPIO#)
    -----------------------------------------------
    DHT11         ESP32
    -----         -----
    VCC*          3V3
    DATA**        32
    NC
    GND           GND

 *  * - depends on module, my DHT11 module is 3V3~5V operate.

 *  ** - depends on your module, maybe you have to add a
    pull-up resistor (~10K Ohm) betwee DATA and VCC.

    Connection between ST7789 SPI and ESP32 (GPIO#)
    -----------------------------------------------
    ST7789 SPI    ESP32
    ----------    -----
    GND           GND
    VCC           3V3
    SCL           18
    SDA           23
    RES           26
    DC            25
    CS            33
    BLK           3V3

*/
#include "DHTesp.h"
#include <Ticker.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <Fonts/FreeMonoBold12pt7b.h>
#include <SPI.h>

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#ifndef ESP32
#pragma message(THIS EXAMPLE IS FOR ESP32 ONLY!)
#error Select ESP32 board.
#endif

DHTesp dht;

void tempTask(void *pvParameters);
bool getTemperature();
void triggerGetTemp();

/** Task handle for the light value read task */
TaskHandle_t tempTaskHandle = NULL;
/** Ticker for temperature reading */
Ticker tempTicker;
/** Comfort profile */
ComfortState cf;
/** Flag if task should run */
bool tasksEnabled = false;
/** Pin number for DHT11 data pin */
int dhtPin = 32;  //17;

//hardware SPI MOSI   23
//hardware SPI SCK    18
#define TFT_CS        33
#define TFT_RST       26
#define TFT_DC        25
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

bool rqsUpdate = false;
TempAndHumidity updateValues;

unsigned long prvUpdateMillis;

#define FRAME_TOPX    0
#define FRAME_TOPY    200
#define FRAME_WIDTH   240
#define FRAME_HEIGHT  100
#define FRAME_BOTTOMY FRAME_TOPY + FRAME_HEIGHT
#define SCR_HEIGHT    320

int idx = 0;
#define IDX_MAX     240

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
BLECharacteristic* pChar_temp = NULL;
BLECharacteristic* pChar_humi = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
#define CHAR_UUID_TEMP "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
#define CHAR_UUID_HUMI "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
      BLEDevice::startAdvertising();
      Serial.println("MyServerCallbacks.onConnect");
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
      Serial.println("MyServerCallbacks.onDisconnect");
    }
};

/**
   initTemp
   Setup DHT library
   Setup task and timer for repeated measurement
   @return bool
      true if task and timer are started
      false if task or timer couldn't be started
*/
bool initTemp() {
  byte resultValue = 0;
  // Initialize temperature sensor
  dht.setup(dhtPin, DHTesp::DHT11);
  Serial.println("DHT initiated");

  // Start task to get temperature
  xTaskCreatePinnedToCore(
    tempTask,                       /* Function to implement the task */
    "tempTask ",                    /* Name of the task */
    4000,                           /* Stack size in words */
    NULL,                           /* Task input parameter */
    5,                              /* Priority of the task */
    &tempTaskHandle,                /* Task handle. */
    1);                             /* Core where the task should run */

  if (tempTaskHandle == NULL) {
    Serial.println("Failed to start task for temperature update");
    return false;
  } else {
    // Start update of environment data every XX seconds
    tempTicker.attach(2, triggerGetTemp);
  }
  return true;
}

/**
   triggerGetTemp
   Sets flag dhtUpdated to true for handling in loop()
   called by Ticker getTempTimer
*/
void triggerGetTemp() {
  if (tempTaskHandle != NULL) {
    xTaskResumeFromISR(tempTaskHandle);
  }
}

/**
   Task to reads temperature from DHT11 sensor
   @param pvParameters
      pointer to task parameters
*/
void tempTask(void *pvParameters) {
  Serial.println("tempTask loop started");
  while (1) // tempTask loop
  {
    if (tasksEnabled) {
      // Get temperature values
      getTemperature();
    }
    // Got sleep again
    vTaskSuspend(NULL);
  }
}

/**
   getTemperature
   Reads temperature from DHT11 sensor
   @return bool
      true if temperature could be aquired
      false if aquisition failed
*/
bool getTemperature() {
  // Reading temperature for humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor)
  TempAndHumidity newValues = dht.getTempAndHumidity();
  // Check if any reads failed and exit early (to try again).
  if (dht.getStatus() != 0) {
    Serial.println("DHT11 error status: " + String(dht.getStatusString()));
    return false;
  }

  rqsUpdate = true;
  updateValues = newValues;
  return true;
}

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("DHT ESP32 example with tasks");

  //init DHT
  initTemp();
  // Signal end of setup() to tasks
  tasksEnabled = true;

  //init BLE
  // Create the BLE Device
  BLEDevice::init("ESP32-DHT11");

  // Create the BLE Server
  pServer = BLEDevice::createServer();

  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic for temp and humi
  pChar_temp = pService->createCharacteristic(
                 CHAR_UUID_TEMP,
                 BLECharacteristic::PROPERTY_READ   |
                 BLECharacteristic::PROPERTY_WRITE  |
                 BLECharacteristic::PROPERTY_NOTIFY |
                 BLECharacteristic::PROPERTY_INDICATE
               );
  pChar_humi = pService->createCharacteristic(
                 CHAR_UUID_HUMI,
                 BLECharacteristic::PROPERTY_READ   |
                 BLECharacteristic::PROPERTY_WRITE  |
                 BLECharacteristic::PROPERTY_NOTIFY |
                 BLECharacteristic::PROPERTY_INDICATE
               );

  pChar_temp->addDescriptor(new BLE2902());
  pChar_humi->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");


  //init ST7789
  tft.init(240, 320);           // Init ST7789 320x240
  tft.setRotation(2);
  tft.setFont(&FreeMonoBold12pt7b);
  tft.setTextWrap(true);

  tft.fillScreen(ST77XX_RED);
  delay(300);
  tft.fillScreen(ST77XX_GREEN);
  delay(300);
  tft.fillScreen(ST77XX_BLUE);
  delay(300);

  tft.setCursor(0, 0);
  tft.setTextColor(ST77XX_RED);
  tft.print("\n");
  tft.print("ESP32 + DHT11 + ST7789\n");

  prvUpdateMillis = millis();

}

void loop() {
  if (!tasksEnabled) {
    // Wait 2 seconds to let system settle down
    delay(2000);
    // Enable task that will read values from the DHT sensor
    tasksEnabled = true;
    if (tempTaskHandle != NULL) {
      vTaskResume(tempTaskHandle);
    }
  }

  if (rqsUpdate) {

    unsigned long curUpdateMillis = millis();

    tft.fillRect(0, 53, 240, 75, ST77XX_BLUE );

    tft.setCursor(0, 70);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" temp.: " + String(updateValues.temperature));
    tft.setCursor(0, 95);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" humi.: " + String(updateValues.humidity));
    tft.setCursor(0, 115);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" mills.: " + String(curUpdateMillis - prvUpdateMillis));
    prvUpdateMillis = curUpdateMillis;


    if (idx == 0) {
      tft.fillRect(FRAME_TOPX, FRAME_TOPY,
                   FRAME_WIDTH, SCR_HEIGHT - FRAME_TOPY,
                   ST77XX_BLUE);
    }

    tft.drawLine(
      FRAME_TOPX + idx, FRAME_BOTTOMY,
      FRAME_TOPX + idx, FRAME_BOTTOMY - (int)updateValues.temperature,
      ST77XX_WHITE);

    idx++;
    if (idx >= IDX_MAX)
      idx = 0;

    char bufTemp[5];
    char bufHumi[5];
    //convert floating point value to String
    dtostrf(updateValues.temperature, 0, 2, bufTemp);
    dtostrf(updateValues.humidity, 0, 2, bufHumi);

    pChar_temp->setValue((uint8_t*)bufTemp, 5);
    pChar_temp->notify();
    pChar_humi->setValue((uint8_t*)bufHumi, 5);
    pChar_humi->notify();

    //Serial.println(" T:" + String(updateValues.temperature) + " H:" + String(updateValues.humidity));
    rqsUpdate = false;


  }

  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    Serial.println("disconnecting");
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    Serial.println("connecting");
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }

  yield();
}

ex_pyBLE.py, Python3 code run on Raspberry Pi 4B running 32-bit Raspberry Pi OS (bullseye), to monitor temperature & humidity from ESP32_DHT_ST789_graphic_BLE.ino.

# To install bluepy for Python3:
# $ sudo pip3 install bluepy
from bluepy import btle

from datetime import datetime

class MyDelegate(btle.DefaultDelegate):
    def __init__(self, handleTemp, handleHumi):
        self.handleTemp = handleTemp
        self.handleHumi = handleHumi
        btle.DefaultDelegate.__init__(self)
        # ... initialise here

    def handleNotification(self, cHandle, data):
        now = datetime.now()
        current_time = now.strftime("%H:%M:%S")

        if cHandle == self.handleTemp:
            print("temp:", data, "@", current_time)
        elif cHandle == self.handleHumi:
            print("humi:", data, "@", current_time)

# Initialisation  -------
address = "24:0A:C4:E8:0F:9A"
service_uuid = "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
char_uuid_temp = "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
char_uuid_humi = "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

p = btle.Peripheral(address)
#p.setDelegate(MyDelegate())

# Setup to turn notifications on, e.g.
svc = p.getServiceByUUID(service_uuid)
ch_temp = svc.getCharacteristics(char_uuid_temp)[0]
ch_humi = svc.getCharacteristics(char_uuid_humi)[0]
print("ch_temp handle", ch_temp.getHandle())
print("ch_humi handle", ch_humi.getHandle())
p.setDelegate(MyDelegate(ch_temp.getHandle(), ch_humi.getHandle()))
"""
Remark for setup_data for bluepy noification-
Actually I don't understand how come setup_data = b"\x01\x00",
and ch.valHandle + 1.
Just follow suggestion by searching in internet:
https://stackoverflow.com/questions/32807781/
ble-subscribe-to-notification-using-gatttool-or-bluepy
"""
setup_data = b"\x01\x00"
#ch.write(setup_data)
p.writeCharacteristic(ch_temp.valHandle + 1, setup_data)
p.writeCharacteristic(ch_humi.valHandle + 1, setup_data)

print("=== Main Loop ===")

while True:
    if p.waitForNotifications(1.0):
        # handleNotification() was called
        continue

    #print("Waiting...")
    # Perhaps do something else here



remark:
BUT, I found that ex_pyBLE.py run on Raspberry Pi is sometimes unstable; with error of:
luepy.btle.BTLEDisconnectError: Failed to connect to peripheral ...




Next:
~ BLE between ESP32/ESP32C3 (arduino-esp32), notify DHT11 reading of temperature & humidity.