Adsense HTML/JavaScript

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.


Saturday, December 4, 2021

arduino-esp32, get BLE MAC address in String form

arduino-esp32 exercise to get BLE MAC address in String form. 
#include <BLEDevice.h>
#include "esp_bt_device.h"
 
String getMACinString() {
  const uint8_t* macAddress = esp_bt_dev_get_address();
  char charMAC[18];

  sprintf(charMAC, "%02X", (int)macAddress[0]);
  charMAC[2] = ':';
  sprintf(charMAC+3, "%02X", (int)macAddress[1]);
  charMAC[5] = ':';

  sprintf(charMAC+6, "%02X", (int)macAddress[2]);
  charMAC[8] = ':';
  sprintf(charMAC+9, "%02X", (int)macAddress[3]);
  charMAC[11] = ':';

  sprintf(charMAC+12, "%02X", (int)macAddress[4]);
  charMAC[14] = ':';
  sprintf(charMAC+15, "%02X", (int)macAddress[5]);

  return (String)charMAC;
}
 
void setup() {
  Serial.begin(115200);
  Serial.println();
  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("==================================");

 
  //initBluetooth();
  BLEDevice::init("ESP32");
  
  String myMACString = getMACinString();
  Serial.println("My MAC address = " + myMACString);
}
 
void loop() {
  // put your main code here, to run repeatedly:

}

Tested on ESP32-DevKitC v4. with arduino-esp32 2.0.1.



Thursday, November 25, 2021

RPi Pico/MicroPython + ESP-C3-12F (as a WiFi co-processor using AT Command)


It's a exercise on Raspberry Pi Pico/MicroPython work with ESP-C3-12F Module. The ESP-C3-12F preloaded with AT Command Firmware (AT version:2.2.0.0 (s-90458f0 - ESP32C3 - Jun 18 2021 10:24:22)), act as a WiFi co-processor. The Raspberry Pi Pico control ESP-C3-12F using AT Command, to connect and send data to TCP and UDP Server running on Raspberry Pi 4B/Python.


Connection:

It's strongly recommended using separate 3V3 Power Supply for ESP-C3-12F.


	Separated
	Power Supply	ESP-C3-12F            Raspberry Pi Pico
	
			+-------------+       +----------------
	3V3 ------------|VCC       RX0|-------| GP0 (UART TX)
			|          TX0|-------| GP1 (UART RX)
			|             |       |
			|          EN |-------| GP2  
			|     GND     |       |
			+------+------+       |
	                       |              |
	GND -------------------+--------------| GND
                                              +----------------
												
												
Exercise Code:

mpyPico_ESP_AT_TCP.py, a simple program to try AT command on ESP.
import usys, uos
import machine
import utime

class color:
    BLACK =   '\033[1;30;48m'
    RED =     '\033[1;31;48m'
    GREEN =   '\033[1;32;48m'
    YELLOW =  '\033[1;33;48m'
    BLUE =    '\033[1;34;48m'
    MAGENTA = '\033[1;35;48m'
    CYAN =    '\033[1;36;48m'
    END =    '\033[1;37;0m'

print("====================================")
print(usys.implementation[0], uos.uname()[3],
      "\nrun on", uos.uname()[4])
print("------------------------------------")


esp_reset = machine.Pin(2, machine.Pin.OUT)

#uart_esp =machine.UART(0, baudrate=115200)
uart_esp =machine.UART(0, timeout=1000)
#print("UART(0) connected to ESP: \n", uart_esp, "\n")

#==================================
"""
esp_sendCMD_waitResp: send comand to ESP, and wait response
if targetResp catched, return True
otherwise return False
"""
def esp_sendCMD_waitResp(cmd, uart=uart_esp, timeout=2000, targetResp="OK"):
    print(color.MAGENTA + cmd + color.END)
    uart.write(cmd)
    return esp_waitResp(uart, timeout, targetResp)

"""
esp_waitResp: wait ESP response
if targetResp catched, return True
otherwise return False
"""
def esp_waitResp(uart=uart_esp, timeout=2000, targetResp="OK"):
    targetCatched = False
    prvMills = utime.ticks_ms()
    print(color.BLUE)
    while (utime.ticks_diff(utime.ticks_ms(), prvMills))<timeout:
        line=uart.readline()
        if line is not None:
            try:
                line_decoded = line.strip().decode()
                if line_decoded == targetResp:
                    print(color.GREEN + line_decoded)
                    targetCatched = True
                    break
                
                #more checking for Response
                elif line_decoded == "OK":
                    print(color.GREEN + line_decoded)
                    break
                elif line_decoded == "ERROR":
                    print(color.RED + line_decoded)
                    break
                
                elif line_decoded == "SEND OK":
                    print(color.GREEN + line_decoded)
                    break
                elif line_decoded == "SEND FAIL":
                    print(color.RED + line_decoded)
                    break
                
                else:
                    print(line_decoded)
            except UnicodeError:
                print(line)
    
    print(color.END)
    return targetCatched
    
# In my test there are something
# like "################################################"
# follow ready of AT+RST/AT+RESTORE or hardware reset,
# Just dummy wait response to clear it
def esp_waitDummtResp(uart=uart_esp, timeout=2000):
    esp_waitResp(uart=uart_esp, timeout=2000)

#A dummy infinity loop
#to monitor any data sent from ESP via UART
def esp_dummyMonitor(uart=uart_esp):
    while True:
        line=uart.readline()
        if line is not None:
            try:
                line_decoded = line.strip().decode()
                print(line_decoded)
            except UnicodeError:
                print(line)
#==================================

print()
print("=== Start ===")


#hardware reset ESP
print("Hardware reset")
esp_reset.value(1)
utime.sleep(0.5)
esp_reset.value(0)
utime.sleep(0.5)
esp_reset.value(1)

print("wait 'ready' from Hardware Reset\n")
esp_waitResp(targetResp='ready')
esp_waitDummtResp()

esp_sendCMD_waitResp('AT\r\n')          #Test AT startup

esp_sendCMD_waitResp('AT+GMR\r\n')      #Check version information

esp_sendCMD_waitResp('AT+RESTORE\r\n')
esp_waitResp(targetResp='ready')    #wait ready
esp_waitDummtResp()

esp_sendCMD_waitResp('AT+CIPAPMAC?\r\n')  #Query MAC address of ESP SoftAP
esp_sendCMD_waitResp('AT+CIPSTAMAC?\r\n') #Query MAC address of ESP station

esp_sendCMD_waitResp('AT+CWMODE?\r\n')  #Query the Wi-Fi mode
esp_sendCMD_waitResp('AT+CWMODE=2\r\n') #Set the Wi-Fi mode = SoftAP mode
esp_sendCMD_waitResp('AT+CWMODE?\r\n')  #Query the Wi-Fi mode again

esp_sendCMD_waitResp('AT+CIPMUX=1\r\n') #Enable multiple connections
esp_sendCMD_waitResp('AT+CIPMUX?\r\n')

esp_sendCMD_waitResp('AT+CWSAP="esp","password",5,3\r\n')  #config ESP SoftAP
esp_sendCMD_waitResp('AT+CWSAP?\r\n')

esp_sendCMD_waitResp('AT+CIPAP?\r\n')  #Query the IP address of the ESP SoftAP

esp_dummyMonitor()

print("\n~ bye ~\n");

mpyPico_ESP_AT_ScanAP.py, scan Access Point.
"""
Raspberry Pi Pico/MicroPython + ESP-C3-12F exercise

ESP-C3-12F with AT-command firmware:
AT version:2.2.0.0(s-90458f0 - ESP32C3 - Jun 18 2021 10:24:22)

Scan Access Point
"""

import usys, uos
import machine
import utime

class color:
    BLACK =   '\033[1;30;48m'
    RED =     '\033[1;31;48m'
    GREEN =   '\033[1;32;48m'
    YELLOW =  '\033[1;33;48m'
    BLUE =    '\033[1;34;48m'
    MAGENTA = '\033[1;35;48m'
    CYAN =    '\033[1;36;48m'
    END =     '\033[1;37;0m'

print("====================================")
print(usys.implementation[0], uos.uname()[3],
      "\nrun on", uos.uname()[4])
print("------------------------------------")


esp_reset = machine.Pin(2, machine.Pin.OUT)

uart_esp =machine.UART(0, timeout=1000)
#print("UART(0) connected to ESP: \n", uart_esp, "\n")

#==================================
"""
esp_sendCMD_waitResp: send comand to ESP, and wait response
if targetResp catched, return True
otherwise return False
"""
def esp_sendCMD_waitResp(cmd, uart=uart_esp, timeout=2000, targetResp="OK"):
    print(color.MAGENTA + cmd + color.END)
    uart.write(cmd)
    return esp_waitResp(uart, timeout, targetResp)

"""
esp_waitResp: wait ESP response
if targetResp catched, return True
otherwise return False
"""
def esp_waitResp(uart=uart_esp, timeout=2000, targetResp="OK"):
    targetCatched = False
    prvMills = utime.ticks_ms()
    print(color.BLUE)
    while (utime.ticks_diff(utime.ticks_ms(), prvMills))<timeout:
        line=uart.readline()
        if line is not None:
            try:
                line_decoded = line.strip().decode()
                if line_decoded == targetResp:
                    print(color.GREEN + line_decoded)
                    targetCatched = True
                    break
                
                #more checking for Response
                elif line_decoded == "OK":
                    print(color.GREEN + line_decoded)
                    break
                elif line_decoded == "ERROR":
                    print(color.RED + line_decoded)
                    break
                
                elif line_decoded == "SEND OK":
                    print(color.GREEN + line_decoded)
                    break
                elif line_decoded == "SEND FAIL":
                    print(color.RED + line_decoded)
                    break
                
                else:
                    print(line_decoded)
            except UnicodeError:
                print(line)
    
    print(color.END)
    return targetCatched

# In my test there are something
# like "################################################"
# follow ready of AT+RST/AT+RESTORE or hardware reset,
# Just dummy wait response to clear it
def esp_waitDummtResp(uart=uart_esp, timeout=2000):
    esp_waitResp(uart=uart_esp, timeout=2000)

#A dummy infinity loop
#to monitor any data sent from ESP via UART
def esp_dummyMonitor(uart=uart_esp):
    while True:
        line=uart.readline()
        if line is not None:
            try:
                line_decoded = line.strip().decode()
                print(line_decoded)
            except UnicodeError:
                print(line)
#==================================

print()
print("=== Start ===")

#hardware reset ESP
print("Hardware reset")
esp_reset.value(1)
utime.sleep(0.5)
esp_reset.value(0)
utime.sleep(0.5)
esp_reset.value(1)

print("wait 'ready' from Hardware Reset\n")
esp_waitResp(targetResp='ready')
esp_waitDummtResp()

esp_sendCMD_waitResp('AT\r\n')          #Test AT startup

esp_sendCMD_waitResp('AT+GMR\r\n')      #Check version information

esp_sendCMD_waitResp('AT+RESTORE\r\n')
esp_waitResp(targetResp='ready')    #wait ready
esp_waitDummtResp()

esp_sendCMD_waitResp('AT+CIPAPMAC?\r\n')  #Query MAC address of ESP SoftAP
esp_sendCMD_waitResp('AT+CIPSTAMAC?\r\n') #Query MAC address of ESP station

esp_sendCMD_waitResp('AT+CWMODE=1\r\n') #Set the Wi-Fi mode = Station mode
esp_sendCMD_waitResp('AT+CWMODE?\r\n')  #Query the Wi-Fi mode again

preScanTime = utime.ticks_ms()
esp_sendCMD_waitResp('AT+CWLAP\r\n', timeout=10000)  #List Available APs
print("Time used to Scan AP: ",
      utime.ticks_diff(utime.ticks_ms(), preScanTime),
      "(ms)")

print("\n~ bye ~\n");

mpyPico_ESP_AT_TCP_Client.py, TCP Client connect to TCP Server on Raspberry Pi/Python, pyMyTCPServer.py below.
"""
Raspberry Pi Pico/MicroPython + ESP-C3-12F exercise

ESP-C3-12F with AT-command firmware:
AT version:2.2.0.0(s-90458f0 - ESP32C3 - Jun 18 2021 10:24:22)

Pico send AT command to ESP-C3-12F via UART,
- set in station mode
- join AP
- connect to server ip:port 9999
- send text and wait response

Modified from exercise in my another blogspot
https://helloraspberrypi.blogspot.com/2021/02/
picomicropython-esp-01s-at-command-act.html
"""

import usys, uos
import machine
import utime

class color:
    BLACK =   '\033[1;30;48m'
    RED =     '\033[1;31;48m'
    GREEN =   '\033[1;32;48m'
    YELLOW =  '\033[1;33;48m'
    BLUE =    '\033[1;34;48m'
    MAGENTA = '\033[1;35;48m'
    CYAN =    '\033[1;36;48m'
    END =     '\033[1;37;0m'
   
#server port & ip hard-coded,
#have to match with server side setting
server_ip="192.168.120.147"
server_port=9999

print("====================================")
print(usys.implementation[0], uos.uname()[3],
      "\nrun on", uos.uname()[4])
print("------------------------------------")


esp_reset = machine.Pin(2, machine.Pin.OUT)

#uart_esp =machine.UART(0, baudrate=115200)
uart_esp =machine.UART(0, timeout=1000)
#print("UART(0) connected to ESP: \n", uart_esp, "\n")

#==================================
"""
esp_sendCMD_waitResp: send comand to ESP, and wait response
if targetResp catched, return True
otherwise return False
"""
def esp_sendCMD_waitResp(cmd, uart=uart_esp, timeout=2000, targetResp="OK"):
    print(color.MAGENTA + cmd + color.END)
    uart.write(cmd)
    return esp_waitResp(uart, timeout, targetResp)

"""
esp_waitResp: wait ESP response
if targetResp catched, return True
otherwise return False
"""
def esp_waitResp(uart=uart_esp, timeout=2000, targetResp="OK"):
    targetCatched = False
    prvMills = utime.ticks_ms()
    print(color.BLUE)
    while (utime.ticks_diff(utime.ticks_ms(), prvMills))<timeout:
        line=uart.readline()
        if line is not None:
            try:
                line_decoded = line.strip().decode()
                if line_decoded == targetResp:
                    print(color.GREEN + line_decoded)
                    targetCatched = True
                    break
                
                #more checking for Response
                elif line_decoded == "OK":
                    print(color.GREEN + line_decoded)
                    break
                elif line_decoded == "ERROR":
                    print(color.RED + line_decoded)
                    break
                
                elif line_decoded == "SEND OK":
                    print(color.GREEN + line_decoded)
                    break
                elif line_decoded == "SEND FAIL":
                    print(color.RED + line_decoded)
                    break
                
                else:
                    print(line_decoded)
            except UnicodeError:
                print(line)
    
    print(color.END)
    return targetCatched


def espSend(text="test", uart=uart_esp):
    esp_sendCMD_waitResp('AT+CIPSEND=' + str(len(text)) + '\r\n')
    esp_waitResp(targetResp=">")
    esp_sendCMD_waitResp(text)

# In my test there are something
# like "################################################"
# follow ready of AT+RST/AT+RESTORE or hardware reset,
# Just dummy wait response to clear it
def esp_waitDummtResp(uart=uart_esp, timeout=2000):
    esp_waitResp(uart=uart_esp, timeout=2000)

#==================================

print()
print("=== Start ===")

#hardware reset ESP
print("Hardware reset")
esp_reset.value(1)
utime.sleep(0.5)
esp_reset.value(0)
utime.sleep(0.5)
esp_reset.value(1)

print("wait 'ready' from Hardware Reset\n")
esp_waitResp(targetResp='ready')
esp_waitDummtResp()

esp_sendCMD_waitResp('AT\r\n')          #Test AT startup

esp_sendCMD_waitResp('AT+GMR\r\n')      #Check version information

esp_sendCMD_waitResp('AT+RESTORE\r\n')
esp_waitResp(targetResp='ready')    #wait ready
esp_waitDummtResp()

esp_sendCMD_waitResp('AT+CIPAPMAC?\r\n')  #Query MAC address of ESP SoftAP
esp_sendCMD_waitResp('AT+CIPSTAMAC?\r\n') #Query MAC address of ESP station

esp_sendCMD_waitResp('AT+CWMODE?\r\n')  #Query the Wi-Fi mode
esp_sendCMD_waitResp('AT+CWMODE=1\r\n') #1 = Station mode
                                    #2 = SoftAP mode
esp_sendCMD_waitResp('AT+CWMODE?\r\n')  #Query the Wi-Fi mode again

esp_sendCMD_waitResp('AT+CWJAP="ssid","password"\r\n', timeout=5000) #Connect to AP
esp_sendCMD_waitResp('AT+CIFSR\r\n')    #Obtain the Local IP Address

esp_sendCMD_waitResp('AT+CIPSTART="TCP","' +
                     server_ip + '",' +
                     str(server_port) + '\r\n')

espSend()

while True:
    print('Enter something:')
    msg = input()
    esp_sendCMD_waitResp('AT+CIPSTART="TCP","' +
                         server_ip + '",' +
                         str(server_port) + '\r\n')
    espSend(msg)
    
print("\n~ bye ~\n");

pyMyTCPServer.py, TCP Server run on Raspberry Pi.
"""
Simple Python TCP Server
tested on Raspberry Pi/Python3

ref:
https://docs.python.org/3/library/socketserver.html
"""
import socketserver
import platform

print("sys info:")
for info in platform.uname():
    print(info)

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The request handler class for our server.

    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.client_address[0].encode())
        self.request.sendall(self.data.upper())
        self.request.sendall(b'\r\n')

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    #with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
    with socketserver.TCPServer(('', PORT), MyTCPHandler) as server:
        # Activate the server; this will keep running until you
        # interrupt the program with Ctrl-C

        server.serve_forever()

mpyPico_ESP_AT_UDP.py, UDP connect to pyUDP_server.py (listed below) on Raspberry Pi.
"""
Raspberry Pi Pico/MicroPython + ESP-C3-12F exercise

ESP-C3-12F with AT-command firmware:
AT version:2.2.0.0(s-90458f0 - ESP32C3 - Jun 18 2021 10:24:22)

Pico send AT command to ESP-C3-12F via UART,
- set in station mode
- join AP
- connect to UDP
- send text and wait response

ref:
https://docs.espressif.com/projects/esp-at/en/release-v2.2.0.0_esp32c3/
AT_Command_Examples/TCP-IP_AT_Examples.html
Example 3.2. UDP Transmission with Changeable Remote IP and Port
"""

import usys, uos
import machine
import utime

class color:
    BLACK =   '\033[1;30;48m'
    RED =     '\033[1;31;48m'
    GREEN =   '\033[1;32;48m'
    YELLOW =  '\033[1;33;48m'
    BLUE =    '\033[1;34;48m'
    MAGENTA = '\033[1;35;48m'
    CYAN =    '\033[1;36;48m'
    END =     '\033[1;37;0m'
   
#server port & ip hard-coded,
#have to match with server side setting
server_ip="192.168.120.147"
server_port=8000

print("====================================")
print(usys.implementation[0], uos.uname()[3],
      "\nrun on", uos.uname()[4])
print("------------------------------------")


esp_reset = machine.Pin(2, machine.Pin.OUT)

#uart_esp =machine.UART(0, baudrate=115200)
uart_esp =machine.UART(0, timeout=1000)
#print("UART(0) connected to ESP: \n", uart_esp, "\n")

#==================================
"""
esp_sendCMD_waitResp: send comand to ESP, and wait response
if targetResp catched, return True
otherwise return False
"""
def esp_sendCMD_waitResp(cmd, uart=uart_esp, timeout=2000, targetResp="OK"):
    print(color.MAGENTA + cmd + color.END)
    uart.write(cmd)
    return esp_waitResp(uart, timeout, targetResp)

"""
esp_waitResp: wait ESP response
if targetResp catched, return True
otherwise return False
"""
def esp_waitResp(uart=uart_esp, timeout=2000, targetResp="OK"):
    targetCatched = False
    prvMills = utime.ticks_ms()
    print(color.BLUE)
    while (utime.ticks_diff(utime.ticks_ms(), prvMills))<timeout:
        line=uart.readline()
        if line is not None:
            try:
                line_decoded = line.strip().decode()
                if line_decoded == targetResp:
                    print(color.GREEN + line_decoded)
                    targetCatched = True
                    break
                
                #more checking for Response
                elif line_decoded == "OK":
                    print(color.GREEN + line_decoded)
                    break
                elif line_decoded == "ERROR":
                    print(color.RED + line_decoded)
                    break
                
                elif line_decoded == "SEND OK":
                    print(color.GREEN + line_decoded)
                    break
                elif line_decoded == "SEND FAIL":
                    print(color.RED + line_decoded)
                    break
                
                else:
                    print(line_decoded)
            except UnicodeError:
                print(line)
    
    print(color.END)
    return targetCatched


def espSend(text="test", uart=uart_esp):
    esp_sendCMD_waitResp('AT+CIPSEND=' + str(len(text)) + '\r\n')
    esp_waitResp(targetResp=">")
    esp_sendCMD_waitResp(text)

# In my test there are something
# like "################################################"
# follow ready of AT+RST/AT+RESTORE or hardware reset,
# Just dummy wait response to clear it
def esp_waitDummtResp(uart=uart_esp, timeout=2000):
    esp_waitResp(uart=uart_esp, timeout=2000)

#==================================

print()
print("=== Start ===")

#hardware reset ESP
print("Hardware reset")
esp_reset.value(1)
utime.sleep(0.5)
esp_reset.value(0)
utime.sleep(0.5)
esp_reset.value(1)

print("wait 'ready' from Hardware Reset\n")
esp_waitResp(targetResp='ready')
esp_waitDummtResp()

esp_sendCMD_waitResp('AT\r\n')          #Test AT startup

esp_sendCMD_waitResp('AT+GMR\r\n')      #Check version information

esp_sendCMD_waitResp('AT+RESTORE\r\n')
esp_waitResp(targetResp='ready')    #wait ready
esp_waitDummtResp()

esp_sendCMD_waitResp('AT+CIPAPMAC?\r\n')  #Query MAC address of ESP SoftAP
esp_sendCMD_waitResp('AT+CIPSTAMAC?\r\n') #Query MAC address of ESP station

esp_sendCMD_waitResp('AT+CWMODE=3\r\n') #3 = SoftAP+Station mode
esp_sendCMD_waitResp('AT+CWMODE?\r\n')  #Query the Wi-Fi mode again

esp_sendCMD_waitResp('AT+CWJAP="ssid","password"\r\n', timeout=5000) #Connect to AP
esp_sendCMD_waitResp('AT+CIFSR\r\n')    #Obtain the Local IP Address
            
esp_sendCMD_waitResp('AT+CIPSTART="UDP","' +
                     server_ip +
                     '",' + str(server_port) +
                     ',' + '1112,2\r\n')

espSend()

while True:
    print('Enter something:')
    msg = input()
    espSend(msg)

print("\n~ bye ~\n");

pyUDP_server.py
"""
Simple Python UDP Server
tested on Raspberry Pi/Python3

ref:
https://wiki.python.org/moin/UdpCommunication
"""
import socket

#have to match my ip
UDP_IP = "192.168.120.147"
UDP_PORT = 8000

sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))

print("UDP IP: %s" % UDP_IP)
print("UDP port: %s" % UDP_PORT)

while True:
    data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
    print("received message: %s" % data)