Issue with null key/value's when accessing a map/vector from a callback generated from ESP32 bluetooth library

0

I am trying to fire a function every time an ESP32 node receives a certain char. I can receive the data and parse the char out of a packet structure I have made. I register all my actions in a map of type <char,callback_function>, where

typedef void (*callback_function)(void);

The idea being that I link all the chars to their respective functions. I can then parse a char out and find the pointer to the function I want to fire easily. The only downside is I have to keep a reference to a global object to access this map from within the bluetooth_callback. I've been testing this for almost 2 days now, and I've written some example code without the espressif APIs. On its own, my code seems to work fine and it runs on the ESP32 aswell. But if I let the bluetooth_callback get called after registering it with the espressif API (ie. let a real bluetooth event to fire the callback), the behavior becomes undefined. The size of the map is still correct with the number of pairs I have added to it, but all of the values and keys are null or undefined, and if I try to access them I get a Guru Meditation Error. I have been trying to solve this problem a lot, and the behaviour is very strange to me. If I call the callback manually then everything seems to work, but if I let it get called by a bluetooth event, I get undefined behavior and crashes. I'm not sure if I am doing something wrong c++ wise or if I this is an issue specific to the esp32. I could be making a dumb mistake. I have attached my Arduino script, the header and cpp files of the library I was working on, and a sample application that should show what I am trying to do. Thank you for your help.

Hardware:

Board: ESP32 Dev Module (Sparkfun ESP32 thing)

Core Installation version: 1.0.3

IDE name: Arduino IDE

Flash Frequency: 40Mhz

PSRAM enabled: no

Upload Speed: 115200

Computer OS: Windows 10

Backtrace

Guru Meditation Error: Core  0 panic'ed (LoadProhibited). Exception was unhandled.
Core 0 register dump:
PC      : 0x400014e8  PS      : 0x00060430  A0      : 0x800d2410  A1      : 0x3ffcf1a0  
A2      : 0x000000fe  A3      : 0x000000fc  A4      : 0x000000ff  A5      : 0x0000ff00  
A6      : 0x00ff0000  A7      : 0xff000000  A8      : 0x800d2228  A9      : 0x3ffcf170  
A10     : 0x3ffbed90  A11     : 0x3f40148b  A12     : 0x3f40148b  A13     : 0x0000ff00  
A14     : 0x00ff0000  A15     : 0xff000000  SAR     : 0x00000010  EXCCAUSE: 0x0000001c  
EXCVADDR: 0x000000fc  LBEG    : 0x400014fd  LEND    : 0x4000150d  LCOUNT  : 0xfffffffb  

Backtrace: 0x400014e8:0x3ffcf1a0 0x400d240d:0x3ffcf1b0 0x400d242d:0x3ffcf1d0 0x400d14a2:0x3ffcf1f0 0x400d1501:0x3ffcf210 0x400df881:0x3ffcf230 0x400d8362:0x3ffcf280 0x4008d83d:0x3ffcf2b0

Decoded:

PC: 0x400014e8 EXCVADDR: 0x000000fc

Decoding stack results
0x400d240d: Print::write(char const*) at C:\Users\Jake Booth\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\cores\esp32/Print.h line 66
0x400d242d: Print::print(char const*) at C:\Users\Jake Booth\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\cores\esp32\Print.cpp line 89
0x400d14a2: NodeESP::act_message(char*) at C:\Users\Jake Booth\Documents\Arduino\libraries\NodeESP\NodeESP.cpp line 336
0x400d1501: NodeESP::bluetooth_callback(esp_spp_cb_event_t, esp_spp_cb_param_t*) at C:\Users\Jake Booth\Documents\Arduino\libraries\NodeESP\NodeESP.cpp line 176
0x400df881: btc_spp_cb_handler at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/bt/bluedroid/btc/profile/std/spp/btc_spp.c line 152
0x400d8362: btc_task at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/bt/bluedroid/btc/core/btc_task.c line 110
0x4008d83d: vPortTaskWrapper at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/freertos/port.c line 143

I've highlighted line 336 below to show you easily which one it is.

Arduino Sketch

#include "NodeESP.h"

#define LED_PIN 5

#define TOGGLE_LED_CHAR 'l'
#define TURN_ON_LED_CHAR 'p'

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  
  pinMode(LED_PIN,OUTPUT);

  char l = TOGGLE_LED_CHAR;
  char p = TURN_ON_LED_CHAR;

  esp_spp_cb_param_t ex_param;
  ex_param.data_ind.data = (uint8_t*)l;
  ex_param.data_ind.len = 1;
  //ex_param_ptr = *ex_param;

  NodeESP node_esp;
  node_esp.add_func(&p, toggle_LED);
  node_esp.add_func(&l, turn_on_LED);
  node_esp.begin("TEST_1");
//Just calling it manually works, but when the espressif API calls it I get undefined behaviour.
//  NodeESP::bluetooth_callback(ESP_SPP_DATA_IND_EVT,&ex_param);
}

void toggle_LED()
{
  Serial.println("toggle_LED()");
}

void turn_on_LED()
{
  Serial.println("turn_on_LED()");
}

void loop() {
  // put your main code here, to run repeatedly:
  while(1)
  {
    vTaskDelay(100 / portTICK_PERIOD_MS);
  }
}

NodeESP.h

/*
  NodeESP.cpp - Library for automatically connecting to and using esp32 microcontrollers
  Created by Jacob Booth 11/9/2020
*/

#ifndef NodeESP_h
#define NodeESP_h

#include "Arduino.h"

#include <ESPmDNS.h>
#include <WiFi.h>
#include <map>

#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_spp_api.h"
#include "esp_gap_bt_api.h"

//---Define buffer constants--------------------------
#define RCV_BUF_SIZE 128
#define TX_BUF_SIZE 256

//---Define communication constants-------------------
#define RFCOMM_CHANNEL 1
#define RFCOMM_MAX_FRAME_SIZE 0xffff

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! please run 'make menuconfig' to enable it
#endif

class NodeESP
{
  public:
    //Constructor
    NodeESP();
    
    //Begin
    bool begin(const char *_name);

    //Typedef
    typedef void (*callback_function)(void);

    //Variables
    uint32_t bt_handle;
    const char *_name;
    std::map<char*, callback_function> funcs;
    
    char rx_buf[RCV_BUF_SIZE];
    char tx_buf[RCV_BUF_SIZE];
    WiFiServer server;
    WiFiClient client;
    
    //Generic
    void add_func(char* command, callback_function func);
    void act_message(char* trigger);

    //Bluetooth
    void bluetooth_write(char *buf, int len);
    bool bluetooth_spp_init();
    bool bluetooth_init(const char *deviceName);
    
    static char parse_data(uint8_t *rcvd, uint16_t len);
    static void bluetooth_callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param);

    String toString(const IPAddress & address);
  private:
};
#endif

NodeESP.cpp

/*
  NodeESP.cpp - Library for automatically connecting to and using esp32 microcontrollers
  Created by Jacob Booth 11/9/2020
*/

#include "Arduino.h"

#include <ESPmDNS.h>
#include <WiFi.h>
#include <map>

#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_spp_api.h"
#include "esp_gap_bt_api.h"

#include "NodeESP.h"

using namespace std;

//---State Machine Variables---
//State enum that helps the state machine track its way through 
//connecting to wifi.
enum state_t {
  UNDEFINED,
  DISCONNECTED,
  WIFI_CON,
  SERVER_CON
};

enum comm_t {
  s,
  NONE,
  WIFI,
  BLUETOOTH,
  q
};

//The current communication protocol we are using
comm_t cur_comm = s;

//The current state
state_t cur_state = UNDEFINED;


char tx_buf[TX_BUF_SIZE];
char rx_buf[RCV_BUF_SIZE];

//---Wifi
WiFiServer server(0);
WiFiClient client;

//---Bluetooth
uint32_t bt_handle;

void* example_obj;

//---Main Init--------------------------------------------------------
NodeESP::NodeESP()
{
  this->_name = "default";
  example_obj = (void*)this;
}

bool NodeESP::begin(const char *_name)
{
  this->_name = _name;
  
  //For testing set the current communication method to bluetooth
  cur_comm = BLUETOOTH;
    
  switch (cur_comm)
  {
    case UNDEFINED:
      return false;
      break;
      
    case NONE:
      return false;
      break;
      
    case WIFI:
      return true;
      break;
      
    case BLUETOOTH:
      if (!this->bluetooth_init(_name))
      {
        return false;
      }
      break;
  }
  return true;
}

//---Bluetooth Functions--------------------------------
bool NodeESP::bluetooth_init(const char *deviceName)
{
  //Start Bluetooth
  if (!btStart()) 
  {
    return false;
  }
  
  //Start BlueDroid (bluetooth stack)
  if (esp_bluedroid_init()!= ESP_OK) 
  {
    return false;
  }
  
  //Enable BlueDroid
  if (esp_bluedroid_enable()!= ESP_OK) 
  {
    return false;
  }
  
  //Set device name
  esp_bt_dev_set_device_name(deviceName);
  
  //Set sannable and discoverable
  esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
  
  //Init spp server
  if (!this->bluetooth_spp_init())
  {
    return false;
  }
  //Set send bytes to the correct callback
  //send_bytes = write_bluetooth;
  return true;
}

bool NodeESP::bluetooth_spp_init()
{
  if (esp_spp_register_callback(&bluetooth_callback) != ESP_OK)
  {
    return false;
  }
  
  if (esp_spp_init(ESP_SPP_MODE_CB) != ESP_OK)
  {
    return false;
  }

  if (esp_spp_start_srv(ESP_SPP_SEC_NONE, ESP_SPP_ROLE_SLAVE, 1, "") != ESP_OK)
  {
    return false;
  }
  return true;
}

void NodeESP::bluetooth_write(char *buf, int len)
{
  esp_spp_write(bt_handle, len, (uint8_t*)buf);
}

void NodeESP::bluetooth_callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
{
  switch (event)
  {
    case ESP_SPP_SRV_OPEN_EVT:
    {
      NodeESP* self = (NodeESP*) example_obj;
      self->bt_handle = param->srv_open.handle;
      break;
    }  
    case ESP_SPP_CL_INIT_EVT:
      esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_NONE);
      break;
    case ESP_SPP_OPEN_EVT:
      break;
    case ESP_SPP_DATA_IND_EVT:
    {
      NodeESP* self = (NodeESP*) example_obj;
      //char trig = self->parse_data(param->data_ind.data, param->data_ind.len);
      self->act_message((char*)param->data_ind.data);
      break;
    }
    case ESP_SPP_CLOSE_EVT:
      esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
    case ESP_SPP_WRITE_EVT:
      break;
    case ESP_SPP_CONG_EVT:
      break;
  }
  return;
}

//---Parsing functions-------------------------------
//TODO: Add circular buffer
char NodeESP::parse_data(uint8_t *rcvd, uint16_t len)
{
  
  char body_buf[RCV_BUF_SIZE];
  uint8_t len_buf[2];
  uint8_t rcvd_bytes = 0;
  uint8_t recive_stage = 0;

  uint16_t calc_len = 0;
  
  //Loop through each char recieved
  for (uint16_t i = 0; i < len; i++)
  {
    uint8_t cur_char = rcvd[i];
    rcvd_bytes++;
 
    //If we haven't recieved the length or body
    //And we recieve a start delim
    if (recive_stage == 0 && cur_char == 0x7E)
    {
      //We are ready to read the length of the message (next 2 bytes)
      recive_stage = 1;
      rcvd_bytes = 0;
    } 
    else if (recive_stage == 1)//If we are recieving the length
    {      
      //Next two bytes get stored in the length buffer
      len_buf[rcvd_bytes - 1] = cur_char;
            
      //If we have recieved the 2 length bytes
      if (rcvd_bytes == 2)
      {
        //Decode the length
        calc_len = ((uint16_t)len_buf[0]) << 8 | (uint16_t)len_buf[1];
                
        //Ready to recieve the body
        recive_stage = 2;
        rcvd_bytes = 0;
      }
    } 
    else if (recive_stage == 2)//If we are recieving the body
    {
      //Store the recieved bytes
      body_buf[rcvd_bytes - 1] = cur_char;

      if (rcvd_bytes == calc_len)
      {
        //Reset
        rcvd_bytes = 0;
        recive_stage = 0;

        return(cur_char);
      }
    }
  }
  return '/';
}

void NodeESP::add_func(char* command, callback_function func)
{
  Serial.println("Added new callback...");
  std::pair<char*, callback_function>* new_pair = new std::pair<char*, callback_function>(command,func);
  this->funcs.insert(*new_pair);
}

void NodeESP::act_message(char* trigger)
{  
  Serial.println("[act message]");
  Serial.print("funcs.size() = ");
  Serial.print(this->funcs.size()); //Always returns the correct size all of the time
  Serial.println("");
  
  for  (std::map<char*, callback_function>::iterator it = this->funcs.begin();
        it != this->funcs.end(); 
        ++it)
  {
    Serial.print("funcs[i].first = ");
    Serial.print(it->first); //Line 336, accessing the first element of the iterator
    Serial.println("");       //This works properly when I don't call the function through the callback.
    if (trigger == it->first)
    {
        (it->second)();
    }
  }
}


String NodeESP::toString(const IPAddress & address)
{
  return String(address[0]) + "." + address[1] + "." + address[2] + "." + address[3];
}

Example Program

Here is an example program that shows what I want to happen and that it works correctly.

// Example program
#include <iostream>
#include <string>
#include <map>
//Global object to access the instance from within static function

using namespace std;


class example
{
  public:

    example();
    void begin();
    typedef void (*callback_function)(void);
    std::map<char, callback_function> funcs;

    void add_func(char command, callback_function func);
    void do_something(char trigger);
    
    static void example_callback(char cb);
};

void* example_obj;

example::example()
{
  example_obj = (void*)this;
}

void example::begin()
{
  //Set up everything to do with external library, peripherals, ect...

  //Register the callback with the library
  //register_callback(&example_callback);
}

//Static func that does something
void example::example_callback(char cb)
{
  //Have to do this because the args need to match the library's requirements
  example* self = (example*) example_obj;

  //Do some processing...
  self->do_something(cb);
}

//Add a function pointer and its command char to the vector
void example::add_func(char command, callback_function func)
{
    std::pair<char, callback_function>* new_pair = new std::pair<char, callback_function>(command,func);
    this->funcs.insert(*new_pair);
}

//This function is called from the callback
void example::do_something(char trigger)
{
  cout << "funcs.size() = "<< (this->funcs.size())<<"\n"; //Always returns the correct size all of the time

  for  (std::map<char, callback_function>::iterator it = this->funcs.begin();
        it != this->funcs.end(); 
        ++it)
  {
    cout << "funcs[i].first = "<< (it->first)<<"\n";
    if (trigger == it->first)
    {
        (it->second)();
    }
  }
}

void callback()
{
    cout << "Callback Called!";
}

int main()
{
    cout << "Starting program...\n";
    
    cout << "Creating example object...\n";
    example ex;
    
    cout << "Adding some callbacks...\n";
    ex.add_func('x',callback);
    ex.add_func('y',callback);
    ex.add_func('z',callback);
    
    cout << "Firing callback...\n";
    example::example_callback('z');
}
c++
arduino
bluetooth
microcontroller
esp32
asked on Stack Overflow Nov 11, 2020 by jeb

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0