Wednesday, May 19, 2021

ESP32/Arduino BLE UART Client connect to Raspberry Pi Pico/CircuitPython + ESP32/nina-fw

It's a BLE UART Client exercise run on ESP2-DevKitC using arduino-esp32 framework. Connect to cpyPico_ESP32_BLE.py in Raspberry Pi Pico/CircuitPython + ESP32/adafruit nina-fw (act as WiFi/BLE Co-Processor).


It search BLE device with specified service UUID, and characteristics for TX and RX. Then sending something every 5 seconds. In the Pico/CircuitPython +ESP32/nina-fw server side, receive and echo back every bytes. This client then print the received byte on Terminal.

ESP32_BLE_client.ino
/*
Modify from Arduino Examples for ESP32 Dev Module:
>  ESP32 BLE Arduino > BLE_client
 */

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

#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" // RX Characteristic
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // TX Characteristic
static BLEUUID serviceUUID(SERVICE_UUID);
static BLEUUID charUUID_RX(CHARACTERISTIC_UUID_RX);   
static BLEUUID charUUID_TX(CHARACTERISTIC_UUID_TX);  

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pTXCharacteristic;
static BLERemoteCharacteristic* pRXCharacteristic;
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);
    */
    Serial.print((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");

    // 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.
    // charUUID_TX
    pTXCharacteristic = pRemoteService->getCharacteristic(charUUID_TX);
    if (pTXCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID_TX.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found charUUID_TX characteristic");

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

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

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

    String helloValue = "Hello Remote Server";
    pRXCharacteristic->writeValue(helloValue.c_str(), helloValue.length());

    

    // =================================================================================
    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(serviceUUID)) {

      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 + "\"");

    String newValue = " " + String(millis()/1000);
    // Set the characteristic's value to be the array of bytes that is actually a string.
    pRXCharacteristic->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(5000); // Delay a second between loops.
} // End of loop


Thursday, April 29, 2021

ESP32-C3/Arduino (ESP32-C3-DevKitM-1) read Network Time from pool.ntp.org

It's a example from arduino-esp32, ESP32 >Time > SimpleTime, to read Network Time from pool.ntp.org.

Tested on ESP32-C3 (ESP32-C3-DevKitM-1).


Currently, to program ESP32-C3 using Arduino, Install arduino-esp32 2.0.0-alpha1 on Arduino IDE.


Wednesday, April 28, 2021

ESP32-S2/Arduino (ESP32-S2-Saola-1) web server control onboard RGB LED (NeoPixel)

With arduino-esp32 2.0.0-alpha1 installed, we can program ESP32-S2 using Arduino IDE.

This exercise run on ESP32-S2-Saola-1 (ESP32-S2), act as access point, setup web server, and turn ON/OFF onboard RGB LED (NeoPixel) according to user click.

Scroll down to have another exercise changing onboard LED color with HTML ColorPicker.

The onboard RGB LED of ESP32-S2-Saola-1 is connected to GPIO18.

Adafruit NeoPixel library is needed, can be install in Arduino IDE's Library Manager.



ESP32_WebRGB.ino

/*
 A simple web server that lets you blink the onboard RGB LED via the web.

 Run on ESP32-S2-Saola-1 (ESP32-S2),
 ast as access point, setup web server,
 and turn ON/OFF onboard RGB LED (NexPixel) accordingly.

 The onboard RGB LED of ESP32-S2-Saola-1 is connected to GPIO18.

 http://yourAddress/H turns the LED on
 http://yourAddress/L turns it off

reference examples in Arduino:
Examples > WiFi > WiFiAccessPoint
Examples > WiFi > SimpleWiFiServer
Examples > Adafruit NeoPixel > simple
 
 */
#include <Adafruit_NeoPixel.h>
#include <WiFi.h>
#include <Esp.h>

//Onboard RGB LED (NeoPixel)
#define PIXELPIN  18
#define NUMPIXELS 1

const char* ssid     = "esp32s2";
const char* password = "password";

const char* html="<html>"
      "<head>"
      "<meta charset=\"utf-8\">"
      "<title>ESP32-S2 Web control NeoPixel</title>"
      "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">"
      "</head>"
      "<body>"
      "Click <a href=\"/H\">here</a> to turn the RGB LED (NeoPixel) ON.<br>"
      "Click <a href=\"/L\">here</a> to turn the RGB LED (NeoPixel) OFF.<br>"
      "</body>"
      "</html>";

Adafruit_NeoPixel pixel(NUMPIXELS, PIXELPIN, NEO_GRB + NEO_KHZ800);
WiFiServer server(80);

void setup()
{
    Serial.begin(115200);
    pixel.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
    pixel.clear(); // Set pixel colors to 'off'
    pixel.show();

    delay(1);
    Serial.printf("\n\n---Start---\n");
    Serial.print("Chip Model: ");
    Serial.print(ESP.getChipModel());
    Serial.print("\nChip Revision: ");
    Serial.print(ESP.getChipRevision());
    Serial.println();
  
    Serial.println();
    Serial.println("Configuring access point...");

    WiFi.softAP(ssid, password);

    IPAddress myIP = WiFi.softAPIP();
    Serial.print("AP IP address: ");
    Serial.println(myIP);
    server.begin();
    
    Serial.println("Server started");

}

int value = 0;

void loop(){
 WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("New Client.");          // print a message out the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            client.print(html);
            // break out of the while loop:
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /H" or "GET /L":
        if (currentLine.endsWith("GET /H")) {
          Serial.println("\n--- ON ---");
          pixel.setPixelColor(0, pixel.Color(100, 100, 0));
        }
        if (currentLine.endsWith("GET /L")) {
          Serial.println("\n--- OFF ---");
          pixel.setPixelColor(0, pixel.Color(0, 0, 0));
        }

        pixel.show();
      }
    }
    // close the connection:
    client.stop();
    Serial.println("Client Disconnected.");
  }
}




ESP32-S2/Arduino (ESP32-S2-Saola-1) web control onboard NeoPixel, with HTML ColorPicker

This another exercise run on ESP32-S2-Saola-1 (ESP32-S2), act as station, connect to mobile hotspot, setup web server. User can open the web page using browsers on mobile, change LED color using HTML ColorPicker.



ESPAsyncWebServer and the AsyncTCP libraries are need.

ESP32_WebRGB_ColorPicker.ino
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_NeoPixel.h>

//Onboard RGB LED (NeoPixel)
#define PIXELPIN  18
#define NUMPIXELS 1
Adafruit_NeoPixel pixel(NUMPIXELS, PIXELPIN, NEO_GRB + NEO_KHZ800);

AsyncWebServer server(80);

// ssid/password match your WiFi Network
const char* ssid = "ssid";
const char* password = "password";

const char* Param_COLOR ="color";

// HTML
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head>
  <title>ESP32-S2 exercise</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  </head><body>
  <p>Select Color</p>
  <form action="/get">
    <input name="color" type="color">
    <input type="submit" value="Update RGB">
  </form><br>

</body></html>)rawliteral";

void notFound(AsyncWebServerRequest *request) {
  request->send(404, "text/plain", "Not found");
}

void setup() {
  Serial.begin(115200);
  Serial.println("---Start---");
  Serial.println("Chip Model: ");
  Serial.println(ESP.getChipModel());
  Serial.println("\nChip Revision: ");
  Serial.println(ESP.getChipRevision());
  Serial.println();
  
  pixel.begin();    // INITIALIZE NeoPixel
  pixel.clear();    // Set pixel colors to 'off'
  pixel.show();

  //Act as STA, connect to WiFi network
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi Failed!");
    return;
  }
  Serial.println();
  Serial.print("Visit IP Address: ");
  Serial.println(WiFi.localIP());

  // Send HTML
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html);
  });

  // Send a GET request to <ESP_IP>/get?color=#rrggbb
  server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String color_value;

    //check if parameter "color" found
    if (request->hasParam(Param_COLOR)) {
      Serial.println("param_COLOR received");
      color_value = request->getParam(Param_COLOR)->value();
      Serial.println(color_value);

      //Convert intMessage in #rrggbb form to r,g,b
      color_value.remove(0, 1);
      int intColor = hstol(color_value);
      int r = (intColor & 0xff0000)>>16;
      int g = (intColor & 0x00ff00)>>8;
      int b = (intColor & 0x0000ff);
      Serial.println(r);
      Serial.println(g);
      Serial.println(b);

      //set NeoPixel color
      pixel.setPixelColor(0, pixel.Color(r, g, b));
      pixel.show();
    }
    
    else {
      Serial.println("No color found");
    }
    
    
    //re-send html
    request->send_P(200, "text/html", index_html);
    
  });
  server.onNotFound(notFound);
  server.begin();
}

//hex-string to long
long hstol(String recv){
  char c[recv.length() + 1];
  recv.toCharArray(c, recv.length() + 1);
  return strtol(c, NULL, 16); 
}

void loop() {
  
}