Using PubSubClient from within a custom class

2

I'm building a library that I can reuse for multiple devices to save me setting up the same functions in my code over and over again.

I'm having issues when trying to publish a message, and I'm hoping you can help!

The code is targeting an ESP32 with BLE and WiFi enabled (it's a port of Timo's Heart Rate Monitor, and the message payload is created using ArduinoJSON 6.x (the original used ArduinoJSON 5.x, but that's not the problem here as the code fails with the same error when trying to print plain text)

The code is managed via Platformio.org, not the Arduino IDE, and my platformio.ini file looks like this:

[env:esp32dev]
platform = espressif32
board = ttgo-t-beam
framework = arduino
lib_extra_dirs = 
    ../common_libraries
build_flags = !../bin/build_flags.sh
board_build.partitions = min_spiffs.csv
lib_deps = 
    nkolban/ESP32 BLE Arduino@^1.0.1
    bblanchon/ArduinoJson@^6.17.2

The build_flags.sh script is just to set environment variables for the WiFi and MQTT client as you'll see in a bit.

The code in the library is as follows, and the issue that I'm seeing is that whilst the "registration message" from within the class setup is always sent, the data from the heart rate monitor is sent sporadically and always causes a Guru Meditation.

Frustratingly, this only fails on an MQTT publish from outside the class, subscription messages come through regardless of where the client is initialised.

The Code

My "library"

/*
 * mymqtt32.h
 *
 */

#ifndef mymqtt32_h
#define mymqtt32_h

#include "Arduino.h"

#include <WiFi.h>
#include <PubSubClient.h>


class mymqtt32 {
    public:
        mymqtt32(
                char* device_name,
                char* device_type,
                char* wifi_ssid,
                char* wifi_pw,
                char* mqtt_server_ip,
                int mqtt_server_port
                );
        void wifi_setup();
        PubSubClient mqtt_setup(String client_name);
    private:
        char* _device_name;
        char* _device_type;
        char* _wifi_ssid;
        char* _wifi_pw;
        char* _mqtt_server_ip;
        int _mqtt_server_port;
        String _getMAC();
        WiFiClient _espClient;
        PubSubClient _mqtt_client;
};

#endif
#include "mymqtt32.h"

#include <ArduinoJson.h>

mymqtt32::MYMQTT(
        char* device_name,
        char* device_type,
        char* wifi_ssid, 
        char* wifi_pw,
        char* mqtt_server_ip,
        int mqtt_server_port
) {
    _device_name = device_name;
    _device_type = device_type;
    _wifi_ssid = wifi_ssid;
    _wifi_pw = wifi_pw;
    _mqtt_server_ip = mqtt_server_ip;
    _mqtt_server_port = mqtt_server_port;
}

void mymqtt32::wifi_setup() {
    delay(10);
    // We start by connecting to a WiFi network
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(_wifi_ssid);
    WiFi.mode(WIFI_STA);
    WiFi.begin(_wifi_ssid, _wifi_pw);

    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }

    randomSeed(micros());

    Serial.print("My mac address is: ");
    Serial.println(_getMAC());
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
}

PubSubClient mymqtt32::mqtt_setup(String client_name) {
    _mqtt_client.setClient(_espClient);
    _mqtt_client.setServer(_mqtt_server_ip,
                           _mqtt_server_port);
    String _client_name = client_name;
    String _dev_type_str = _device_type;
    while (!_mqtt_client.connected()) {
        Serial.print("Attempting MQTT connection...");
        // Attempt to connect
        if (
            _mqtt_client.connect(_client_name.c_str())
            ) {
            Serial.println("connected");
            const size_t capacity = JSON_OBJECT_SIZE(3);
            DynamicJsonDocument systemDescription(capacity);

            systemDescription["device_name"] = _client_name;
            systemDescription["device_type"] = _dev_type_str;
            systemDescription["device_mac"]  = _getMAC();

            char bodyString[500];
            serializeJson(systemDescription, bodyString, sizeof(bodyString));
            serializeJson(systemDescription, Serial);
            Serial.println();
            Serial.println(bodyString);
            String pub_topic = "/register";
            char pub_topic_name[50];
            pub_topic.toCharArray(pub_topic_name, 50);
            Serial.print("Publishing to: ");
            Serial.println(pub_topic_name);
            // Once connected, publish an announcement...
            _mqtt_client.publish(pub_topic_name, bodyString);
            // Now subscribe to the controller topic
            String sub_topic = "devices/control/";
            sub_topic.concat(_dev_type_str);
            sub_topic.concat("/");
            sub_topic.concat(_getMAC());
            char sub_topic_name[50];
            sub_topic.toCharArray(sub_topic_name, 50);
            Serial.print("Subscribing to: ");
            Serial.println(sub_topic_name);
            _mqtt_client.subscribe(sub_topic_name);
        } else {
            Serial.print("Trying to connect to ");
            Serial.print(_mqtt_server_ip);
            Serial.print(" on port ");
            Serial.println(_mqtt_server_port);
            Serial.print("failed, rc=");
            Serial.print(_mqtt_client.state());
            Serial.println(" try again in 5 seconds");
            // Wait 5 seconds before retrying
            delay(5000);
        }
    }
    return _mqtt_client;
}

String mymqtt32::_getMAC()
{
  String macaddr = String(WiFi.macAddress());
  macaddr.replace(":", "");
  macaddr.toLowerCase();
  return String( macaddr );
}

How I call the code

#include <MYMQTT32.h>
#define ST(A) #A
#define STR(A) ST(A)

// Setup the WiFi and MQTT Client based on the build flags
MYMQTT mymqtt(STR(DEVICE_NAME), device_type, STR(WIFI_SSID), STR(WIFI_PASS), STR(MQTT_SERVER), 1883);
PubSubClient mqctrl;

...
void setup() {
    // put your setup code here, to run once:
    Serial.begin(9600);
    hbat.wifi_setup();
    mqctrl = hbat.mqtt_setup(device_type);
    mqctrl.setCallback(control);

...

}

void loop() {
...

  mqctrl.loop()
}

The function that is meant to publish but causes the guru meditation

//--------------------------------------------------------------------------------------------
  // Send HRM stats to MQTT
  //--------------------------------------------------------------------------------------------
  void sendHRMData() {
    Serial.println("Message received from chest strap, sending to MQTT");
    DynamicJsonDocument  dataJson(20);
  
    dataJson[F("HRM")]             = hrm.HRM;
    char buffer[20];
    serializeJson(dataJson, buffer);
~   Serial.print("Buffer Content: ");
~   Serial.println(buffer);
     mqctrl.publish("/devices/status/heartrate", buffer); // <- JSON BUFFER CAUSES GURU MEDITIATION
~   // mqctrl.publish("/devices/status/heartrate", "test message"); <- STATIC MESSAGE CAUSES THE SAME ISSUE
  }

The "meditation"

The Meditation that is returned is as follows:

Guru Meditation Error: Core  0 panic'ed (InstrFetchProhibited). Exception was unhandled.
Core 0 register dump:
PC      : 0x2f736563  PS      : 0x00060031  A0      : 0x8001a060  A1      : 0x3ffbe290  
A2      : 0x00000000  A3      : 0x00000000  A4      : 0x00000000  A5      : 0x00000001  
A6      : 0x00000000  A7      : 0x3ffb8360  A8      : 0x8008ebac  A9      : 0x3ffbc88c  
A10     : 0x00000002  A11     : 0x3ffbc230  A12     : 0x800922e0  A13     : 0x3ffbc230  
A14     : 0x00000008  A15     : 0x00000000  SAR     : 0x0000001d  EXCCAUSE: 0x00000014  
EXCVADDR: 0x2f736560  LBEG    : 0x00000000  LEND    : 0x00000000  LCOUNT  : 0x00000000  
Core 0 was running in ISR context:
EPC1    : 0x2f736563  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x00000000

Backtrace: 0x2f736563:0x3ffbe290 0x4001a05d:0x3ffbe2b0 0x4008cd3e:0x3ffbe2e0 0x4008a591:0x3ffbe320 0x4008df8d:0x3ffbe340 0x4008e97f:0x3ffbe360 0x40086f41:0x3ffbe380 0x401e7d03:0x3ffbc210 0x400df6ce:0x3ffbc230 0x400922dd:0x3ffbc250 0x40090af9:0x3ffbc270

This decodes to

PC: 0x2f736563
EXCVADDR: 0x2f736560

Decoding stack results
0x4008df8d: xRingbufferCreate at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp_ringbuf/ringbuf.c line 704
0x4008e97f: xQueueGenericSendFromISR at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/queue.c line 1207
0x40086f41: spi_flash_mmap_pages at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spi_flash/flash_mmap.c line 201
0x400df6ce: esp_fill_random at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp32/hw_random.c line 61
0x400922dd: verify_allocated_region at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/heap/multi_heap_poisoning.c line 109
0x40090af9: vTaskPlaceOnEventListRestricted at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/include/freertos/portable.h line 208

and according to the documents suggests that I'm calling the publish command incorrectly:

InstrFetchProhibited

This CPU exception indicates that CPU could not load an instruction because the the address of the instruction did not belong to a valid region in instruction RAM or ROM.

Usually this means an attempt to call a function pointer, which does not point to valid code. PC (Program Counter) register can be used as an indicator: it will be zero or will contain garbage value (not 0x4xxxxxxx).

(from the ESP32 Fatal Errors page)

I'm sure this is an obvious issue and it's just my misunderstanding of how to pass the PubSubClient around, but I'm completely lost on how to do it!

c++
arduino
mqtt
asked on Stack Overflow Dec 20, 2020 by Esra Non

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0