esp32 BLE client application - connect to device name - bluetooth-lowenergy

I've hacked apart the ESP32 BLE arduino sketches to do what I want. The server side is easy. Please see code below:
if (con == 0){
digitalWrite(LED, LOW);
}
if (con == 1){
digitalWrite(LED, HIGH);
delay(1000);
digitalWrite(LED, LOW);
delay(1000);
}
if (deviceConnected) {
pCharacteristic->setValue((uint8_t*)&value, 4);
pCharacteristic->notify();
value++;
delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
con = 1;
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
oldDeviceConnected = deviceConnected;
con = 0;
}
This works exactly how I want. It simply sits idle doing nothing, when a device connects to the BLE server then it will flash an LED.
No problems there, even though I suspect my code isn't 'that pretty.
What i'm having trouble doing however is creating an ESP32 client to connect to the BLE device.
The client has the name set as
BLEDevice::init("BOX_A1");
The example code seems to want UID for both the service and characteristic. Is there any way to just connect to the short advertised name? No data is being shared, it's just simply acting as a beacon to identify a box when connected to.
Thanks
Andrew

You can't connect to a device using the advertised name, not directly at least.
If you want to use the advertised name you have to scan for all BLE devices around you and select the one matching your name. The BLE scan example shows you how this is done. The code scans for a scanTime of 5 seconds, waits 2 seconds and starts scanning again:
void loop() {
// put your main code here, to run repeatedly:
BLEScanResults foundDevices = pBLEScan->start(scanTime, false);
Serial.print("Devices found: ");
Serial.println(foundDevices.getCount());
Serial.println("Scan done!");
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
delay(2000);
}
The example just prints the amount of found devices, you want to search through them and look for the correct name. The BLEScan returns an object of type BLEScanResults. You can access the found devices using getDevice with an index. Something like this might work to print the names of all found devices:
BLEAdvertisedDevice device;
for (int i = 0; i < foundDevices.getCount(); ++i) {
device = foundDevices.getDevice(i);
Serial.println(device.getName().c_str());
}
Now you can compare the names and work with the correct device.

To my understanding,
You want the client to connect to the server with given advertised name.
After connection is success, server turns on led.
You do have notification service running on server, but your client isn't interested.
Below is the client code which only connects to server with name "BOX_A1".
First Set our callback function that checks if device name matches when scanner discovers the devices:
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks
{
void onResult(BLEAdvertisedDevice advertisedDevice)
{
if (advertisedDevice.getName() == "BOX_A1")
{
advertisedDevice.getScan()->stop(); //Scan can be stopped, we found what we are looking for
foundDevice = new BLEAdvertisedDevice(advertisedDevice);
deviceFound = true;
Serial.print("Device found: ");
Serial.println(advertisedDevice.toString().c_str());
}
}
};
Use the BLEAdvertisedDevice object i.e. foundDevice object to connect to this server.
BLEClient* connectToServer(BLEAdvertisedDevice* device) {
BLEClient* pClient = BLEDevice::createClient();
if (pClient->connect(device)){ // Connect to the remote BLE Server.
Serial.println(" - Connected to server");
return pClient;
}
else{
Serial.println("Failed to Connect to device");
return NULL;
}
}
Use following line to call this connect function, it return the client object which can be used to disconnect from server.
if(deviceFound==true){
BLEClient* myDevice = connectToServer(device);
if(myDevice!=NULL){
Serial.print("Connected to the BLE Server: ");
Serial.println(device->getName().c_str());//print name of server
//DO SOME STUFF
//disconnect the device
myDevice->disconnect();
Serial.println("Device disconnected.");
}
}
This was the client side.
At server side to set the connection status flag use the following:
//Setup callbacks onConnect and onDisconnect
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
Serial.println("Client Connected");
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
Serial.println("Client Disconnected");
deviceConnected = false;
}
};

Related

Can I sniff a BLE communication between my multimeter and my smartphone?

I am currently trying to manipulate a multimeter (Zoyi ZT-5BQ) using an Arduino board, but I don't know the protocol that uses my multimeter to change the reading mode (in example, from ohmeter to termometer).
I have tried to pair my AT-09 module to the manufacturer's app (Bluetooth DMM) and see what does it sends when I try to change the reading mode from the smartphone, but AT-09 is not detected by the phone, and I guess it's due to the MAC address of my module.
Is there any way I can sniff between the communication of my smarthphone and multimeter?
Thanks in advance!
The manufacturers' app can't find the AT-09 most likely because the app searches for devices that advertise a specific service that the module does not offer.
Start your research by installing a generic BLE scanner app such as nRF Connect. Connect to your multimeter and look at the services and characteristics it discovers. Try reading and/or writing from/to them, sometimes this is enough to figure out the easier protocolls.
nRF Connect also offers to debug the connection if nRF Connetc is open in the background and you connect to your device using the manufacturers' app. This can already give some insights to the messages sent and received.
The last resort would be to use a real BLE sniffer. There are multiple options, I personally have great experience using the one from Nordic Semiconductor. You would need a bit of hardware and can use Wireshark with an extension to see everything. The cheapest option for the hardware would be the nRF52840-Dongle.
Finally, I found a solution thanks to Michael Kotzjan.
I searched about sniffing and native ways to do it from my phone, and I found the "enable Bluetooth HCI Snoop solution". It was not trivial, at least not for a MIUI based smartphone because I had to enable that option in developer's options and find a way to see those logs, in my case I had to reboot my Redmi Note 8 Pro and then I went to "Xiaomi Services and Feedback" app, where I enabled the logs of the phone's bluetooth, then I started a communication between manufacturer's app (Bluetooth DMM) and my multimeter (Zoyi ZT-5BQ), after that I had to find the folder where logs are stored, in my case: debuglogger->connyslog->bthci->CsLog_2022...
I downloaded the folder to my PC and finally using Wireshark I was able to see the commands that I sent from the destination (Redmi Note...) to my source (Shenzen__88...).
Commands shown in Wireshark
This way I was able to note the commands down, and I made this list:
List of commands
Using NRF Connect app, I was able to test the commands, updating the 0xFFF4 characteristic of the multimeter through BLE technology.
Finally, I made this code in Arduino to connect my ESP32 to the multimeter and test the commands, and I can read and write the 0xFFF4 characteristic under 0xFFF0 service.
/**
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"
//String serverUUID = "FC:58:FA:88:56:59";
// The remote service we wish to connect to.
static BLEUUID serviceUUID("fff0");
// The characteristic of the remote service we are interested in.
static BLEUUID charUUID("fff4");
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;
//BLEAddress address(serverUUID.c_str());
static void notifyCallback(
BLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.println((char*)pData);
}
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient* pclient) {
}
void onDisconnect(BLEClient* pclient) {
connected = false;
Serial.println("onDisconnect");
}
};
bool connectToServer() {
Serial.print("Forming a connection to ");
Serial.println(myDevice->getAddress().toString().c_str());
BLEClient* pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the remove BLE Server.
pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
Serial.println(" - Connected to server");
pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)
// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our service");
// Obtain a reference to the characteristic in the service of the remote BLE server.
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our characteristic");
// Read the value of the characteristic.
if (pRemoteCharacteristic->canRead()) {
std::string value = pRemoteCharacteristic->readValue();
Serial.print("The characteristic value was: ");
Serial.println(value.c_str());
}
if (pRemoteCharacteristic->canNotify())
pRemoteCharacteristic->registerForNotify(notifyCallback);
connected = true;
return true;
}
/**
Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
/**
Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(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 nothing 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());
byte Command[2][10] = {
{0xea, 0xec, 0x70, 0xe3, 0xa2, 0xc1, 0x32, 0x71, 0x64, 0x9b}, // Celsius
{0xea, 0xec, 0x70, 0xe2, 0xa2, 0xc1, 0x32, 0x71, 0x64, 0x98} // Fahr
};
Serial.println("Celsius");
pRemoteCharacteristic->writeValue(Command[0], sizeof(colors[0]));
delay(2000);
Serial.println("Fahr");
pRemoteCharacteristic->writeValue(Command[1], sizeof(colors[1]));
delay(2000);
} 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

ESP32 WPS reconnect on power-on

I am trying to develop an IoT device that should provide some functionality using a HTTP/REST API. I decided to use the ESP32 chip (on "ESP32 dev board").
Now I want to implement an easy-to-use WLAN configuration. I don't want to store credentials in my source code like many other samples do; so I decided to use WPS.
I tried to implement a basic web server using the sources here:
https://randomnerdtutorials.com/esp32-web-server-arduino-ide/ - and then I added the WPS functionality from the Wifi/WPS samples shipped with the EPS32 extensions for Arduino IDE.
Now the WPS already works, i.e. when the dev-board gets powered it is in WPS connection mode and waits for the router to accept the WPS connection. It successfully gets the SSID and connects to the WLAN.
But when I power-off the ESP32, and power-on again, I have to do the WPS reconnection procedure again. I'd expect a reconnection, that stores the credentials and is able to connect to the same WLAN again when the ESP32 device is powered-on at any time later. I guess I have to store some credentials and use them to re-establish the connection - but where do I get the credentials, and how do I reconnect?
I did search the web for "ESP32 WLAN WPS reconnect" and similar terms, but did find only reconnect strategies for non-wps (SSID + password) connections. I did also check the WiFi library documentation and the esp_wps library documentation, but didn't find anything suitable.
That's the WLAN WPS connection source:
#include <WiFi.h>
#include "esp_wps.h"
#define ESP_WPS_MODE WPS_TYPE_PBC
esp_wps_config_t config = WPS_CONFIG_INIT_DEFAULT(ESP_WPS_MODE);
String wpspin2string(uint8_t a[]){
//...
}
void WiFiEvent(WiFiEvent_t event, system_event_info_t info){
switch(event){
case SYSTEM_EVENT_STA_START:
Serial.println("Station Mode Started");
break;
case SYSTEM_EVENT_STA_GOT_IP:
Serial.println("Connected to :" + String(WiFi.SSID()));
Serial.print("Got IP: ");
Serial.println(WiFi.localIP());
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("Disconnected from station, attempting reconnection");
WiFi.reconnect();
break;
case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:
Serial.println("WPS Successfull, stopping WPS and connecting to: " + String(WiFi.SSID()));
esp_wifi_wps_disable();
delay(10);
WiFi.begin();
break;
case SYSTEM_EVENT_STA_WPS_ER_FAILED:
Serial.println("WPS Failed, retrying");
esp_wifi_wps_disable();
esp_wifi_wps_enable(&config);
esp_wifi_wps_start(0);
break;
case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:
Serial.println("WPS Timedout, retrying");
esp_wifi_wps_disable();
esp_wifi_wps_enable(&config);
esp_wifi_wps_start(0);
break;
case SYSTEM_EVENT_STA_WPS_ER_PIN:
Serial.println("WPS_PIN = " + wpspin2string(info.sta_er_pin.pin_code));
break;
default:
break;
}
}
// some GPIO stuff, removed for SO question
void setup() {
// initialize some GPIO for status etc. - removed for SO
//Initialize serial and wait for port to open:
Serial.begin(115200);
while(!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// We start by connecting to a WiFi network
WiFi.onEvent(WiFiEvent);
WiFi.mode(WIFI_MODE_STA);
Serial.println("Starting WPS");
esp_wifi_wps_enable(&config);
esp_wifi_wps_start(0);
// attempt to connect to Wifi network:
while(WiFi.status() != WL_CONNECTED) {
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
delay(700);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop() {
//irrelevant for SO question
}
The ESP, both 32 and 8266, remember the last AP it was connected to. So simple call WiFi.begin(); without any credentials will make it connect to that last AP. Then in your while(WiFi.status() != WL_CONNECTED) loop you could make it timeout and then call the esp_wifi_wps_start(0); if it does not connect.
you can find the answer here https://www.esp32.com/viewtopic.php?f=19&t=27004
the SSID and password are stored in the config and given to esp_wifi_set_config, after WPS has finished, you can just use the getter function again by calling:
wifi_config_t config;
esp_err_t err = esp_wifi_get_config(WIFI_IF_STA, &config);
if (err == ESP_OK) {
printf("SSID: %s, PW: %s\n", (char*) config.sta.ssid, (char*) config.sta.password);
} else {
printf("Couldn't get config: %d\n", (int) err);
}
and you can then find the SSID and password in that struct again.

Configure WiFi on ESP8266 uing WIFI_AP_STA mode

I am trying to program my NodeMCU (Lolin v3) board in such a way that I can use it to configure the WiFi settings without having to hard code the credentials. I know there is a WiFiManager Library, but I don't intend to use that since I need to do my own implementation, not use the UI that the library provides. The credentials provided by the user are stored in a file using SPIFFS, used to check whether to start the board in AP_STA mode or STA mode only.
Below is the logic I use:
void connectWiFi(String ssid, String password, boolean staOnly = false) {
boolean state = true;
int i = 0;
if(staOnly)
WiFi.mode(WIFI_STA);
WiFi.begin(ssid.c_str(), password.c_str());
while (WiFi.status() != WL_CONNECTED) {
delay(500);
if (i > 10) {
state = false;
break;
}
i++;
}
return state;
}
void join() {
String ssid = setupServer.arg("ssid");
String password = setupServer.arg("password");
result = connectWifi(ssid, password);
if(result) {
Serial.println("Connected");
// **THIS IS THE PROBLEMATIC PART**
setupServer.send(200, "text/plain", WiFi.localIP().toString());
// save credentials to a file
Serial.println("Conf saved");
delay(2000);
ESP.restart();
} else
setupServer.send(200, "text/plain", "fail");
}
void setup() {
Serial.begin(115200);
WiFi.disconnect(true);
boolean fileExists = SPIFFS.exists(WIFI_CONF_FILE);
if(!fileExists) {
WiFi.mode(WIFI_AP_STA);
WiFi.softAP("AP", "password");
IPAddress myIP = WiFi.softAPIP();
setupServer = ESP8266WebServer(myIP, 8888);
setupServer.on("/join", join);
setupServer.begin();
} else {
// read file contents for ssid and password
connectWifi(ssid, password, true);
// do some work here
}
}
void loop() {
setupServer.handleClient();
}
So now when I do a fresh boot, the board enters AP_STA mode and starts with SSID AP. I connect to it and open http://192.169.4.1/join?ssid=mywifi&password=12345678 in the browser. Somehow the connection gets terminated and I get "Destination Unreachable" in my browser. But the serial monitor prints 'Connected' and 'Conf saved'.
I want to know why it isn't returning the success response to the browser. I need the localIP after it has connected to the WiFi. It returns the failed response correctly in case it fails. How can I ensure it will always return the IP address assigned to it back to the client that connected to it before restarting?
Any help is appreciated.
Thanks!
Looks like it is bound to happen as radio module is shared between two modes.
Found the explaination for this issue here: https://github.com/esp8266/Arduino/issues/3282
This is related to the fact that STA will switch to the channel of the AP it is trying to connect to, and SoftAP will have to switch to the same channel. So the client (PC or smartphone connected to the SoftAP) will have to reconnect to the SoftAP on its new channel. In most cases this causes TCP connections to be reset.

How can I make the WifiClient.connect work in Arduino?

I would like to know if WifiClient.connect does not allow the locally running server to be connected (e.g. http://localhost:8080). I discovered that WifiClient.connect works when the url is anything public (e.g. www.google.com). Please help if there may be something I can programmatically change in order to connect the localhost server. The code below is a portion of setup().
status = WiFi.begin(ssid, pass);
// if you're not connected, stop here:
if ( status != WL_CONNECTED) {
Serial.println("Couldn't get a wifi connection");
while(true);
}
// if you are connected, print out info about the connection:
else {
Serial.println("Connected to network");
}
// configure the uri to be called
char apiURL[] = "127.0.0.1";
uint16_t port = 8080;
if (client.connect(apiURL, port)){
Serial.println("connected to " + String(apiURL));
// it always prints this statement
}else{
Serial.println("not calling " + String(apiURL));
}
One more (and maybe silly) thing I would like to ask: would Bluetooth be a better option than Wifi when I want my Arduino to send data to server (e.g. making POST request)?

Trying to loop a http request using the Arduino Adafruit CC3000 Webclient

I am trying to retrieve data from a webpage to my Arduino on a looping 5 second interval. I'm building off the Adafruit CC3000 'Webclient' example.
The complete code can be seen here.
Everything works as expected, the Arduino connects to the network and then makes a single request to the specified website.
Now I'm trying to add the loop, so that the Arduino gets the refreshed data. I don't want to disconnect and then have to reconnect to the wifi network so I tried to loop the following code.
void myLoop(uint32_t ip)
{
// START LOOPING
int count = 0;
while(count == 0) {
Adafruit_CC3000_Client www = cc3000.connectTCP(ip, 80);
if (www.connected()) {
www.fastrprint(F("GET "));
www.fastrprint(WEBPAGE);
www.fastrprint(F(" HTTP/1.1\r\n"));
www.fastrprint(F("Host: ")); www.fastrprint(WEBSITE); www.fastrprint(F("\r\n"));
www.fastrprint(F("\r\n"));
www.println();
} else {
Serial.println(F("Connection failed"));
return;
}
/* Read data until either the connection is closed, or the idle timeout is reached. */
//unsigned long lastRead = millis();
while (www.connected()) {
while (www.available()) {
char c = www.read();
content = c;
Serial.print(c);
//lastRead = millis();
}
}
www.close();
Serial.println(F("\n\nDisconnecting"));
cc3000.disconnect();
delay(5000);
}
}
The loop runs perfectly once, but after the first iteration continually outputs "Connection Failed".
It appears that I cannot run the connectTCP function more than once but I cannot understand why
Adafruit_CC3000_Client www = cc3000.connectTCP(ip, 80);
I've also tried removing this from the loop and removing the www.close(); and cc3000.disconnect(); but it still fails to leave the connection open.
Any assistance would be greatly appreciated.
I'm also new using CC3000 and had a same problem with that code before.
you cannot connecting again because you put cc3000.disconnect(); inside the loop()
so just remove it, but keep the www.close(); in the loop().
I had the same issue, the cc3000 connecting only once or 3-4 http requests (Get) before crashing.
Now i have it working like this:
on the loop():
Adafruit_CC3000_Client www = cc3000.connectTCP(ip, WEBPORT);
delay(300);
if (www.connected()) {
//your http request get
//with a connection close:
www.fastrprint(F("GET "));
//inser your stuff
www.fastrprint(F("Connection: close\r\n"));
www.fastrprint(F("\r\n"));
www.println();
} else {
Serial.println(F("Connection failed"));
return;
}
//reading response
while(www.connected()){
while (www.available()) {
char c = www.read();
Serial.print(c);
}
}
www.stop();
delay(100);
www.close();
delay(200);
and stop&close the client at the end.
Hope this helps someone.. took mi so long to have this easy stuff working!

Resources