ESP32 Bluetooth SPP with bidirectional communication (send and receive)

newsettler_AI
Posts: 75
Joined: Wed Apr 05, 2017 12:49 pm

ESP32 Bluetooth SPP with bidirectional communication (send and receive)

Postby newsettler_AI » Fri Apr 06, 2018 10:40 am

Hi,

I need that ESP can send and receive data over SPP while connected to Android smartphone (let say, using any spp terminal I need to receive and send commands).

In IDF examples there are 2 examples for acceptor and initiator. I have checked acceptor and it works fine. Only one thing - it working only in one directional Android -> ESP.

Is it possible to make comunication bidirectional?
I'm looking over code in both acceptor and initiator and I'm not sure is it even possible, as I see at one example ESP is slave and in another master.

There are essential difference in callbacks for processing SPP events:
acceptor:

Code: Select all

static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE;

// . . .

    case ESP_SPP_INIT_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT");
        esp_bt_dev_set_device_name(EXCAMPLE_DEVICE_NAME);
        esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
        esp_spp_start_srv(sec_mask,role_slave, 0, SPP_SERVER_NAME);
        break;
    case ESP_SPP_DISCOVERY_COMP_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT");
       // do nothing!!!
        break;
    case ESP_SPP_OPEN_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT");
       // do nothing!!!
        break;
initiator

Code: Select all

static const esp_spp_role_t role_master = ESP_SPP_ROLE_MASTER;

// . . .

    case ESP_SPP_INIT_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT");
        esp_bt_dev_set_device_name(EXCAMPLE_DEVICE_NAME);
        esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
        esp_bt_gap_start_discovery(inq_mode, inq_len, inq_num_rsps);
        break;
    case ESP_SPP_DISCOVERY_COMP_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT status=%d scn_num=%d",param->disc_comp.status, param->disc_comp.scn_num);
        if (param->disc_comp.status == ESP_SPP_SUCCESS) {
            esp_spp_connect(sec_mask, role_master, param->disc_comp.scn[0], peer_bd_addr);
        }
        break;
    case ESP_SPP_OPEN_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT");
        esp_spp_write(param->srv_open.handle, SPP_DATA_LEN, spp_data);
        gettimeofday(&time_old, NULL);
        break;
And which role should be Android smartphone in case bidirectional communication with ESP?

Please advice, maybe there are some ready robust solutions for bidirectional communication over SPP?

User avatar
loboris
Posts: 494
Joined: Wed Dec 21, 2016 7:40 pm

Re: ESP32 Bluetooth SPP with bidirectional communication (send and receive)

Postby loboris » Fri Apr 06, 2018 1:11 pm

You just have to combine receiving and sending, like in this simple example based on example_spp_acceptor_demo.c

Code: Select all

/*
   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_bt_api.h"
#include "esp_bt_device.h"
#include "esp_spp_api.h"

#include "time.h"
#include "sys/time.h"

#define SPP_TAG "SPP_ACCEPTOR_DEMO"
#define SPP_SERVER_NAME "SPP_SERVER"
#define EXCAMPLE_DEVICE_NAME "ESP_SPP_ACCEPTOR"
#define SPP_SHOW_DATA 0
#define SPP_SHOW_SPEED 1
#define SPP_SHOW_MODE SPP_SHOW_DATA    /*Choose show mode: show data or speed*/

static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB;

static struct timeval time_new, time_old;
static long data_num = 0;

static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_NONE;
static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE;

static void print_speed(void)
{
    float time_old_s = time_old.tv_sec + time_old.tv_usec / 1000000.0;
    float time_new_s = time_new.tv_sec + time_new.tv_usec / 1000000.0;
    float time_interval = time_new_s - time_old_s;
    float speed = data_num * 8 / time_interval / 1000.0;
    ESP_LOGI(SPP_TAG, "speed(%fs ~ %fs): %f kbit/s" , time_old_s, time_new_s, speed);
    data_num = 0;
    time_old.tv_sec = time_new.tv_sec;
    time_old.tv_usec = time_new.tv_usec;
}

static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
{
    char buf[1024];
    char spp_data[256];
    switch (event) {
    case ESP_SPP_INIT_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT");
        esp_bt_dev_set_device_name(EXCAMPLE_DEVICE_NAME);
        esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
        esp_spp_start_srv(sec_mask,role_slave, 0, SPP_SERVER_NAME);
        break;
    case ESP_SPP_DISCOVERY_COMP_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT");
        break;
    case ESP_SPP_OPEN_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT");
        break;
    case ESP_SPP_CLOSE_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT");
        break;
    case ESP_SPP_START_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT");
        break;
    case ESP_SPP_CL_INIT_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT");
        break;
    case ESP_SPP_DATA_IND_EVT:
#if (SPP_SHOW_MODE == SPP_SHOW_DATA)
        ESP_LOGI(SPP_TAG, "ESP_SPP_DATA_IND_EVT len=%d handle=%d",
                 param->data_ind.len, param->data_ind.handle);
        if (param->data_ind.len < 1023) {
            snprintf(buf, (size_t)param->data_ind.len, (char *)param->data_ind.data);
            printf("%s\n", buf);
            sprintf(spp_data, "Receined characters: %d\n", param->data_ind.len);
            esp_spp_write(param->write.handle, strlen(spp_data), (uint8_t *)spp_data);
        }
        else {
            esp_log_buffer_hex("",param->data_ind.data,param->data_ind.len);
        }
#else
        gettimeofday(&time_new, NULL);
        data_num += param->data_ind.len;
        if (time_new.tv_sec - time_old.tv_sec >= 3) {
            print_speed();
        }
#endif
        break;
    case ESP_SPP_CONG_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_CONG_EVT");
        break;
    case ESP_SPP_WRITE_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_WRITE_EVT");
        break;
    case ESP_SPP_SRV_OPEN_EVT:
        ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT");
        gettimeofday(&time_old, NULL);
        break;
    default:
        break;
    }
}



void app_main()
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK( ret );


    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    if (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
        ESP_LOGE(SPP_TAG, "%s initialize controller failed\n", __func__);
        return;
    }

    if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
        ESP_LOGE(SPP_TAG, "%s enable controller failed\n", __func__);
        return;
    }

    if (esp_bluedroid_init() != ESP_OK) {
        ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed\n", __func__);
        return;
    }

    if (esp_bluedroid_enable() != ESP_OK) {
        ESP_LOGE(SPP_TAG, "%s enable bluedroid failed\n", __func__);
        return;
    }

    if (esp_spp_register_callback(esp_spp_cb) != ESP_OK) {
        ESP_LOGE(SPP_TAG, "%s spp register failed\n", __func__);
        return;
    }

    if (esp_spp_init(esp_spp_mode) != ESP_OK) {
        ESP_LOGE(SPP_TAG, "%s spp init failed\n", __func__);
        return;
    }
}
Tested with Bluetooth terminal Android application.

oclyke
Posts: 2
Joined: Thu Sep 27, 2018 6:27 am

Re: ESP32 Bluetooth SPP with bidirectional communication (send and receive)

Postby oclyke » Wed Oct 10, 2018 3:52 am

Thanks loboris, worked great.

PawelJalocha
Posts: 12
Joined: Tue Apr 24, 2018 9:14 pm

Re: ESP32 Bluetooth SPP with bidirectional communication (send and receive)

Postby PawelJalocha » Sat Oct 27, 2018 10:18 pm

Hello, I need the SPP mode too and actually I am not able to make it to work at all.
With some older ESP-IDF I was able to get to a point where the BT was reported as audio device thus could not connect it as serial port
with the newer ESP-IDF it is still reported as audio but is mostly dead thus no callbacks, etc.

Would appreciate help to make it work.

Pawel.

tommeyers
Posts: 58
Joined: Tue Apr 17, 2018 1:51 pm
Location: Santiago, Dominican Republic

Re: ESP32 Bluetooth SPP with bidirectional communication (send and receive)

Postby tommeyers » Sun Oct 28, 2018 2:19 pm

This worked for me:

Code: Select all

#include "BluetoothSerial.h"
//===== Preferences Manager:  (hack so that I can return a preference data type)
//                            see: https://playground.arduino.cc/Code/Struct#Creation 
#include "name.h"

BluetoothSerial SerialBT;
void setup() {
  //add:     PreferenceAdd(preferenceName,preferenceLabel,defaultValue,regexValidation (javascript regex),description);
  preferenceAdd("wifiname",      "WiFi SSID",     "Wireless-N",         "^[A-Z|a-z|-]{5}$",                              "WiFi SSID");
  preferenceAdd("wifipwd",       "WiFi Password", "xyzzy3585",          "^[A-Z|a-z|0-9]{10}$",                           "WiFi Password");
  preferenceAdd("mqttip",        "MQTT IP",       "192.168.1.250",      "^[0-9|.{15}$",                                  "MQTT Broker IP");
  preferenceAdd("nodename",      "MQTT Node",     "Node01",             "^[A-Z|a-z|0-9]{5}$",                            "Node name for MQTT");
  Serial.begin(115200);
  setupBT();
}
String receivedSerialString   = "";
void loop() {
  processBT();
}  
//==  BT interface for preferences/nvs ======================================================
//
//
String receivedSerialBTString = "";
void setupBT() {
  SerialBT.begin("ESP32test"); //Bluetooth device name
  SerialBT.flush();
  Serial.println("The device started, now you can pair it and connect from another bluetooth");
}
void processBT() {
  char receivedBTChar;
  while (SerialBT.available()) {
    receivedBTChar = SerialBT.read();
    receivedSerialBTString = receivedSerialBTString + receivedBTChar;
    if (receivedBTChar == '\n') {
      // ===== hello
      if (receivedSerialBTString.startsWith("hello")) {
          SerialBT.print("hello\n");  
          receivedSerialBTString = "";
          continue;
      }
      // ===== reboot
      if (receivedSerialBTString.startsWith("reboot")) {
          SerialBT.print("rebooting      - reconnect\n");
          //SerialBT.flush();
          //delay(1000);
          esp_restart();  
      }
      // ===== preferences get and update
      if (receivedSerialBTString.startsWith("preferences")) {
          // ===== preferences get
          if(receivedSerialBTString.substring(12).startsWith("get") ) {
            Serial.print("preferences/get");Serial.print(" senting: "); Serial.println(preferenceGetCount());
                            //                                                                                                    1         1         1         1
                            //0         1         2         3         4         5         6         7         8         9         0         1         2         1
                            //01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
                            //                   name           label         value              regex                                     description
                            //preferences gotten wifiname       WiFi SSID     Wireless-N         /.*/                                      WiFi SSID\n");
            for (int i = 0; i < preferenceGetCount(); i++) {
              SerialBT.print("preferences gotten "
                             + (preferenceGetInfo(i).preferenceName   + "                                             ").substring(0,15)
                             + (preferenceGetInfo(i).preferenceLabel  + "                                             ").substring(0,14)
                             + (preferenceGetInfo(i).actualValue      + "                                             ").substring(0,19)
                             + (preferenceGetInfo(i).regexValidation  + "                                             ").substring(0,41)
                             + (preferenceGetInfo(i).description      + "                                             ").substring(0,23)
                             + "\n");   
            }
            receivedSerialBTString = "";
            continue;
          }
          // ===== preferences update
          if(receivedSerialBTString.substring(12).startsWith("update") ) {
              //                                                                                                    1         1         1         1
              //0         1         2         3         4         5         6         7         8         9         0         1         2         1
              //01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
              //                   name           value              
              //preferences update wifiname       Wireless-N         
              receivedSerialBTString = receivedSerialBTString + "                    ";  // trailing spaces so substring(19,33) is in range  
              String nameString = receivedSerialBTString.substring(19,33);
              String valueString = receivedSerialBTString.substring(34);
              nameString.trim();  //because trim() modifies in-place see: https://www.arduino.cc/reference/en/language/variables/data-types/string/functions/trim/
              valueString.trim(); //because trim() modifies in-place see: https://www.arduino.cc/reference/en/language/variables/data-types/string/functions/trim/
              preferenceUpdate(nameString, valueString);
              receivedSerialBTString = "";
              continue;
          }
          // ===== defaults
          if(receivedSerialBTString.substring(15).startsWith("defaults") ) {
            preferenceReset();
            receivedSerialBTString = "";
            continue;
          }
      }   
      Serial.println("Unknown: " + receivedSerialBTString); 
      receivedSerialBTString = "";
      continue;
    }
  }
}
// ===== Preferences Manager" Access to preference Name/Values in NVS 
//       implements: Add (to list and NVS); Reset: (all values in list are set to defaults and NVS too); update: (change list value and NVS value)
//                   getCount and getPreference
//
#include <FS.h>
#include <Preferences.h>
Preferences preferences;
preferenceInfo preferenceList[20];
int preferenceCount = 0;
//============================================================================================
//===== Preferences Manager:  return one of the array 
//
preferenceInfo preferenceGetInfo(int index) {
  return preferenceList[index];
}
//============================================================================================
//===== Preferences Manager:  return the count of preferences in the array 
//
int preferenceGetCount() {
  return preferenceCount;
}
//============================================================================================
//===== Preferences Manager:  Adds a preference to the list and to the NVS "preferences" 
//
void preferenceAdd(String preferenceName,String preferenceLabel,String defaultValue,String regexValidation,String description) { // Add an item and put its value (or default) in table
           // Serial.print(preferenceGetInfo[0].description);
            preferenceList[preferenceCount].preferenceName  = preferenceName;
            preferenceList[preferenceCount].preferenceLabel  = preferenceLabel;
            preferenceList[preferenceCount].defaultValue    = defaultValue;    
            preferenceList[preferenceCount].regexValidation = regexValidation;
            preferenceList[preferenceCount].description     = description;
            preferences.begin("preferences",false);
            preferenceList[preferenceCount].actualValue = preferences.getString(preferenceName.c_str(), defaultValue.c_str());
            preferences.end();
            preferenceCount++;
          }
//============================================================================================
//===== Preference Manager: Sets all preferences in the list to their default (does not clear/change others in NVS but not in list)
//
void preferenceReset() {                                               //clear all and set the list of values to their default
              preferences.begin("preferences",false);
              for (int i = 0; i < preferenceCount; i++) {
                preferences.putString(preferenceList[i].preferenceName.c_str(), preferenceList[i].defaultValue.c_str());
                preferenceList[i].actualValue = preferences.getString(preferenceList[i].preferenceName.c_str(), preferenceList[i].defaultValue.c_str());
              }
              preferences.end(); 
              return;
}
//============================================================================================
//===== Preference Manager: Changes the preference value in NVS and sets the actualValue in the list
//
void preferenceUpdate(String preferenceName,String preferenceValue) {  // Change an items value and update the table 
          preferences.begin("preferences",false);
          for (int i = 0; i < preferenceCount; i++) {
              //Serial.println("compare:" + preferenceList[i].preferenceName + "/" + preferenceName + "/" +  preferenceValue);
              if (preferenceList[i].preferenceName == preferenceName) {
                //Serial.print("Changed:  "); Serial.print(preferenceName.c_str()); Serial.print("/"); Serial.println(preferenceValue.c_str());
                preferenceList[i].actualValue = preferenceValue;
                preferences.putString(preferenceName.c_str(),preferenceValue.c_str()); 
                //Serial.println("Verify:" +  preferences.getString(preferenceList[i].preferenceName.c_str(), "999"));
              }
          }
          preferences.end();
          return;
}
I access it with a my chrome add-in.
IT Professional, Maker
Santiago, Dominican Republic

PawelJalocha
Posts: 12
Joined: Tue Apr 24, 2018 9:14 pm

Re: ESP32 Bluetooth SPP with bidirectional communication (send and receive)

Postby PawelJalocha » Sun Oct 28, 2018 3:32 pm

OK, I am working with ESP-IDF and I actually got to the stage where I can see and pair the BT of my ESP32 in classic BT mode.
But, it refuses to connect, which I suspect is because the ESP32 is seen as audio-card and not a serial port.
Is there a way to resolve this situation ?
Are there any parameters on the ESP32 which affect the way external devices like laptops or tablets see the BT object ?

Pawel.

tommeyers
Posts: 58
Joined: Tue Apr 17, 2018 1:51 pm
Location: Santiago, Dominican Republic

Re: ESP32 Bluetooth SPP with bidirectional communication (send and receive)

Postby tommeyers » Sun Oct 28, 2018 4:48 pm

You might try a BT sniffer to get more info.

I used BT Serial library and my esp32 presented SPP.

I also used a BT Sniffer in the process.
IT Professional, Maker
Santiago, Dominican Republic

PawelJalocha
Posts: 12
Joined: Tue Apr 24, 2018 9:14 pm

Re: ESP32 Bluetooth SPP with bidirectional communication (send and receive)

Postby PawelJalocha » Sun Oct 28, 2018 5:06 pm

Here is what I see with the Linux bluetoothctl:

Class: 0x2c0414
Icon: audio-card
Paired: yes
Trusted: no
Blocked: no
Connected: no
LegacyPairing: no
UUID: Serial Port (00001101-0000-1000-8000-00805f9b34fb)
RSSI: -54

the problem is the "audio-card" - this way laptops and tablets see this BT device.
Connection attempts fail with the message:

Failed to connect: org.bluez.Error.NotAvailable

Who is online

Users browsing this forum: Google [Bot] and 9 guests