Daniel
Daniel

Reputation: 1481

ESP32 Unable to send Telegram message in interrupt method

I'm working on my first ESP32 project using a DFRobot Firebeetle 2 (ESP32-WROOM-32E) and the Arduino IDE (configured as per DFRobot's instructions). I'm trying creating a Telegram bot which will send a message when the temperature exceeds a certain value. This part is working in the loop() method, but now I'm trying to add a 'status button' which triggers an interrupt to send a status message. I plan to use the ESP32's sleep function, hence using an interrupt rather than checking the button in loop() - I am aware that loop() doesn't execute when using sleep. The problem is sending a message in the interrupt method is causing an error, but I don't know how to resolve it.

If I reduce minStatusMsgInterval to 250 and call SendStatusMessage(); at the end of setup() I receive the message, so the function works, just not when it's called from an interrupt. The error mentions wdt timeout so I'm thinking bot.sendMessage() is taking too long? But if that is the case, I can't make it any quicker, so is there another way to achieve this? or have I misinterpreted the error completely?

#include <WiFi.h>
#include "WiFiClientSecure.h"
#include <ESP32Time.h>
#include <DFRobot_DHT11.h>
#include <UniversalTelegramBot.h>

//WiFi
#define WLAN_SSID  "ESP32Test"
#define WLAN_PASS "*********"

//WiFiFlientSecure for SSL/TLS support
WiFiClientSecure client;

//Time
ESP32Time rtc(0);  // offset in seconds; 0 = GMT, 3600 = GMT+1

//Telegram
#define TELEGRAM_TOKEN "*********:*************************"
#define CHAT_ID "**********"

UniversalTelegramBot bot(TELEGRAM_TOKEN, client);

//Temperature
DFRobot_DHT11 DHT;
#define DHT11_PIN D11

//Buttons
#define ONBOARD_BUTTON_PIN 27 //ESP32 pin 27
#define STATUS_BUTTON_PIN D10 //ESP32 pin 12
int StatusButtonLastInterrupt = 0;

//Alerts
int alertValue = 27;
bool alertSent = false;
int minStatusMsgInterval = 5000;

void IRAM_ATTR SendStatusMessage() {  
  
  if (millis() - StatusButtonLastInterrupt < minStatusMsgInterval) return; //Prevent executing multiple times per button press or more frequent than required

  String message = "Temperature Monitor Status\n\n";
  message += "Internal temperature: " + String(DHT.temperature) + "C\n";
  message += "Internal humidity: " + String(DHT.humidity) + "%\n";
  //message += "System time: " + rtc.getDateTime(); // Causes ESP to crash and reset. Error: abort() was called at PC 0x40084cf7 on core 1
  
  //TODO: Check WiFi is connected first - will need to connect when woken from sleep

  bot.sendMessage(CHAT_ID, message, ""); // Causes ESP to crash and reset. Error: Guru Meditation Error: Core  1 panic'ed (Interrupt wdt timeout on CPU1).

  StatusButtonLastInterrupt = millis();
}

void SyncNTP() {
  Serial.print("Retrieving time: ");
  configTime(0, 3600, "time.google.com"); // get UTC time via NTP
  time_t now = time(nullptr);
  while (now < 24 * 3600)
  {
    Serial.print(".");
    delay(100);
    now = time(nullptr);
  }
  rtc.setTime(now); //Set the time on the realtime clock
  Serial.println(rtc.getDateTime());
}

void setup() {
  pinMode(ONBOARD_BUTTON_PIN, INPUT_PULLUP);
  pinMode(STATUS_BUTTON_PIN, INPUT);

  Serial.begin(115200);

  //Interrupts
  attachInterrupt(STATUS_BUTTON_PIN, SendStatusMessage, FALLING);

  // Connect to WiFi access point.
  Serial.println(); Serial.println();
  Serial.print("Connecting to ");
  Serial.println(WLAN_SSID);

  WiFi.mode(WIFI_MODE_STA);
  WiFi.begin(WLAN_SSID, WLAN_PASS);

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

  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Add root certificate for api.telegram.org

  SyncNTP();
}

void loop() {

  DHT.read(DHT11_PIN);
  Serial.print("temp:");
  Serial.print(DHT.temperature);
  Serial.print(" humi:");
  Serial.println(DHT.humidity);

  if (DHT.temperature >= alertValue)
  {
    if (!alertSent)
    {
      bot.sendMessage(CHAT_ID, "TEMPERATURE ALERT: Internal temperature is: " + String(DHT.temperature), "");
      alertSent = true;
    }
    
  } else {
    if (alertSent)
    {
      bot.sendMessage(CHAT_ID, "Alert Cleared: Internal temperature is: " + String(DHT.temperature), "");
      alertSent = false; //Reset the flag when temperature drops below alertValue
    }
  }

  delay(1000);
}

Serial Monitor Output:

Connecting to ESP32Test
WiFi Status: 6
WiFi Status: 6
WiFi Status: 6
WiFi Status: 6
WiFi Status: 3

WiFi connected
IP address: 192.168.1.2
Retrieving time: ....................Sun, Feb 19 2023 22:23:38
temp:25 humi:57
temp:24 humi:53
temp:24 humi:60
temp:24 humi:60
temp:24 humi:60
temp:24 humi:60
Guru Meditation Error: Core  1 panic'ed (Interrupt wdt timeout on CPU1). 

Core  1 register dump:
PC      : 0x40090362  PS      : 0x00060f34  A0      : 0x8008f47e  A1      : 0x3ffbeb00  
A2      : 0x3ffde700  A3      : 0x3ffbd994  A4      : 0x00000004  A5      : 0xb33fffff  
A6      : 0x00000001  A7      : 0x00000001  A8      : 0x3ffbd994  A9      : 0x3ffbd994  
A10     : 0x00000019  A11     : 0x00000019  A12     : 0x3ffc3afc  A13     : 0xb33fffff  
A14     : 0x00000001  A15     : 0x00000001  SAR     : 0x0000000f  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x400896b0  LEND    : 0x400896c6  LCOUNT  : 0xffffffff  
Core  1 was running in ISR context:
EPC1    : 0x400e8e33  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x40090362


Backtrace:0x4009035f:0x3ffbeb000x4008f47b:0x3ffbeb20 0x4008de7c:0x3ffbeb40 0x40106ab7:0x3ffbeb80 0x400f408f:0x3ffbeba0 0x401071c5:0x3ffbebd0 0x40107464:0x3ffbebf0 0x400f379d:0x3ffbec40 0x400d49d2:0x3ffbec60 0x400d472d:0x3ffbeed0 0x400d483e:0x3ffbef00 0x400d6c31:0x3ffbef20 0x400d7ece:0x3ffbef80 0x400d8406:0x3ffbf000 0x4008139e:0x3ffbf070 0x400d93f5:0x3ffbf0d0 0x40084a8d:0x3ffbf0f0 0x40173c7f:0x3ffc78f0 0x400e82e9:0x3ffc7910 0x4008e417:0x3ffc7930 


Core  0 register dump:
PC      : 0x40090522  PS      : 0x00060634  A0      : 0x8008fc1b  A1      : 0x3ffb3290  
A2      : 0x3ffbf108  A3      : 0x3ffb32ac  A4      : 0x00060623  A5      : 0xb33fffff  
A6      : 0x0000cdcd  A7      : 0x0000abab  A8      : 0x0000abab  A9      : 0x0000abab  
A10     : 0x3ffde770  A11     : 0x000000d0  A12     : 0x3ffc6948  A13     : 0xb33fffff  
A14     : 0x00000001  A15     : 0x00000001  SAR     : 0x00000011  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x400896b0  LEND    : 0x400896c6  LCOUNT  : 0xffffffff  



Backtrace:0x4009051f:0x3ffb32900x4008fc18:0x3ffb32d0 0x4008dd9c:0x3ffb32f0 0x401069c0:0x3ffb3330 0x40106d0e:0x3ffb3350 0x400f56bb:0x3ffb3370 0x400f5739:0x3ffb3390 0x400f812c:0x3ffb33b0 0x400f8211:0x3ffb33e0 0x40108557:0x3ffb3400 0x400f3f19:0x3ffb3420 




ELF file SHA256: 0000000000000000

Rebooting...
ets Jul 29 2019 12:21:46

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:1412
load:0x40078000,len:13400
load:0x40080400,len:3672
entry 0x400805f8


Connecting to ESP32Test

Upvotes: 0

Views: 540

Answers (1)

romkey
romkey

Reputation: 7089

In general, interrupt handlers need to do as little as possible and return as quickly as possible. Doing too much or calling unsafe functions (which almost all functions will be) leads to programs crashing exactly the way you saw.

Interrupts, as the name indicates, interrupt whatever code is running and execute the interrupt's handler. The handler has no idea what state the code that was interrupted is in. It could easily be in an inconsistent state while it's performing a computation; calling it while it's in an inconsistent state is likely to corrupt memory or cause it to crash or behave erratically. Unless software is specifically designed to be called from an interrupt handler you must assume it's not safe to do so.

As you noticed, the ESP32 requires an interrupt handler to be marked with the IRAM_ATTR attribute - this tells the ESP32 to always keep this code in "Instruction RAM" so that it's always available to execute. The ESP32 isn't able to pull code in from flash storage on demand to service an interrupt. That means that not only does the interrupt handler have to be in IRAM, all functions it calls must be as well. IRAM is a scarce resource, so you want to use as little of it as possible - calling Telegram from an interrupt handler means that not only does all of the Telegram code need to be in IRAM but the entire TCP/IP network stack needs to be. Which also means you'd have to modify every single function in the TelegramBot and network stack to be declared IRAM_ATTR.

Obviously that's not going to work.

Unless you really know what you're doing, the safest way to code an interrupt handler is to set a flag variable indicating there's work to be done, store any data that needs to be saved for the work in other variables and return.

In your case it would look something like this:

volatile boolean should_send_telegram_message = false;

void IRAM_ATTR SendStatusMessage() {
  if (millis() - StatusButtonLastInterrupt < minStatusMsgInterval)
    return; //Prevent executing multiple times per button press or more frequent than required

  should_send_telegram_message = true;
}

void loop() {
  if(should_send_telegram_message) {
    should_send_telegram_message = false;

    String message = "Temperature Monitor Status\n\n";
    message += "Internal temperature: " + String(DHT.temperature) + "C\n";
    message += "Internal humidity: " + String(DHT.humidity) + "%\n";
    //message += "System time: " + rtc.getDateTime(); // Causes ESP to crash and reset. Error: abort() was called at PC 0x40084cf7 on core 1
  
    //TODO: Check WiFi is connected first - will need to connect when woken from sleep

    bot.sendMessage(CHAT_ID, message, ""); // Causes ESP to crash and reset. Error: Guru Meditation Error: Core  1 panic'ed (Interrupt wdt timeout on CPU1).

    StatusButtonLastInterrupt = millis();
  }

  // do other loop() stuff
}

The volatile attribute on should_send_telegram_message tells the compiler that this variable may unexpectedly have its value changed (as the interrupt handler may do) so that the compiler will avoid doing certain optimizations that depend on the variable not unexpectedly changing.

Upvotes: 1

Related Questions