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.
/*
* 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 );
}
#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()
}
//--------------------------------------------------------------------------------------------
// 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 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!
User contributions licensed under CC BY-SA 3.0