witnessmenow / Universal-Arduino-Telegram-Bot

Use Telegram on your Arduino (ESP8266 or Wifi-101 boards)
MIT License
1.12k stars 307 forks source link

ESP8266 Crash with inline Keyboards and or send commands #218

Open klementuel opened 3 years ago

klementuel commented 3 years ago

Hi there, First of all, thank you for your work and the Telegram bot. However, since my project got bigger and I use some inline keyboards, my ESP8266 crashes all the time, it always ends between exception 28 and 9 ... Since this is my first large project in C ++ for university, I don't rule out the Fault lies with me. So maybe someone can help me?

In this project I use a web server where I can make all settings, an MQTT service to communicate with my OPENHAB and Telegram so that I receive notifications.

If I don't use inline keyboards, the code works super fast and without errors for days. As soon as I use this, everything crashes ...

My h. Telegram file looks like this:

#ifndef TELEGRAM_H
#define TELEGRAM_H
#include <LittleFS.h>
#include <pgmspace.h>
#include <ESP8266WiFi.h>
#include <UniversalTelegramBot.h> //  UniversalTelegramBot-Library von Brian
#include <ArduinoJson.h>
#include <GlobalSettings.h>
#include "TelegramKeyboards.h"

#define CF(p) ((const __FlashStringHelper *)p)

//#include <map>

class Telegram 
{  
    private:
        //std::map<const char*, const char*> chatIds;
        //const char* adminChatId = "463187859";
        //const char* botToken = "906998821:AAEeMOT3lPQQOWdHvFugHEOZ1wtv_uKyLy0";
        int tryToConnect = 0;
        bool isBot = false;
        WiFiClientSecure teleclient;
        std::unique_ptr<UniversalTelegramBot> bot;
        //UniversalTelegramBot bot = {wmconfig.telegram_BotToken, teleclient};
        X509List *cert = new X509List(TELEGRAM_CERTIFICATE_ROOT);
        //const unsigned long BOT_MTBS = 1000; // mean time between scan messages
        unsigned long bot_lasttime = 0; // last time messages' scan has been done
        const unsigned long BOT_MTBS = 2000; // mean time between scan messages

        unsigned long bot_lastConnection = 0; // when was the last try to connect
        const unsigned long BOT_CT_TIMEOUT = 5000; // time Out between try to Connect

        void SetBotCommands();
        void StartKeyBoardMsg(String chatId);
        void handleNewMessages(int numNewMessages);
        void handleAdminMsg(telegramMessage msg);
        void setAboChangeAndAnswere(String &chatId, telegram_user &user, String &command);

    public:
        Telegram();

        void Connect();

        void TelegramLoop();

        bool IsChatId(const char* chatId);

        void AddChatId(const char* chatId, const char* name);

        void ChangeChatIDSettings(const char* chatId, bool wm1, bool wm2, bool leak);

        void EntitledChatID(const char* chatId, bool isEntitled);

        bool IsEntitled(const char* chatID);

        void DeletedChatID(const char* chatId);

};

#endif

My cpp file like this:

#include "Telegram.h"
#include "Debug.h"

Telegram::Telegram()
{
  teleclient.setTrustAnchors(cert);
  configTime(0, 0, "pool.ntp.org"); // get UTC time via NTP
}

void Telegram::handleNewMessages(int numNewMessages)
{
  Serial.print("handleNewMessages ");
  Serial.println(numNewMessages);

  for (int i = 0; i < numNewMessages; i++)
  {
    String chat_id = bot->messages[i].chat_id;
    String text = bot->messages[i].text;
    String from_name = bot->messages[i].from_name;
    if (from_name == "") from_name = "Guest";

    // Inline buttons with callbacks when pressed will raise a callback_query message
    if (bot->messages[i].type == "callback_query")
    {
      DEBUG_PRINTIN("Call back button pressed by: ");
      DEBUG_PRINT(bot->messages[i].from_id);
      DEBUG_PRINTIN("Data on the button: ");
      DEBUG_PRINT(bot->messages[i].text);
      chat_id = bot->messages[i].from_id;
    }

    //Check User at Send Settings to a pointer on Error break
    if(!IsChatId(chat_id.c_str())) AddChatId(chat_id.c_str(), from_name.c_str());
    telegram_user *pt_user;
    auto it = wmconfig.telegram_users.find(chat_id.c_str());
    if(it != wmconfig.telegram_users.end()) pt_user = &it->second;
    else break;

    //Check if user has permission
    if(pt_user->isEntitled)
    {
      if (text == "/start") {
        StartKeyBoardMsg(chat_id);
      }
      else if (text == "/wm1") {
        bot->sendMessageWithInlineKeyboard(chat_id, "Melde Abo Waschmaschine 1", "", CF(keyboardJsonWM1));
      }
      else if (text == "/wm2") {
        bot->sendMessageWithInlineKeyboard(chat_id, "Melde Abo Waschmaschine 2?", "", CF(keyboardJsonWM2));
      }

      else if (text == "/leak") bot->sendMessageWithInlineKeyboard(chat_id, "Melde Abo Wasserschaden?", "", CF(keyboardJsonLEAK));

      else if (text == "/all") bot->sendMessageWithInlineKeyboard(chat_id, "Alles Abonieren?", "", CF(keyboardJsonALL));

      else if (text == "/off") {
      bot->sendMessage(chat_id, "Benachrichtigunge Deaktiviert");
      }
      else if (text == "/status") {
      bot->sendMessage(chat_id, "Mir geht es super und dir?");
      }
      else if (text == "/help") {
      bot->sendMessage(chat_id, "Keine Ahnung wie das geht, fragt doch Klemens ;-)");
      }
      else if (text == "/addid") {
      bot->sendMessage(chat_id, "Welche Chat ID möchtest du hinzufügen?");
      }
      else if (text.startsWith("/Abo")) setAboChangeAndAnswere(chat_id, *pt_user, text);
      else
      {
        if(chat_id == wmconfig.telegram_MasterChatId) handleAdminMsg(bot->messages[i]);
        else if(text.charAt(0) == '/') bot->sendMessage(chat_id, "Diesen Befehl kenne ich nicht");
        else bot->sendMessage(chat_id, "ECHO:  " + text);
      }  
  }
  //Handle User without permission
  else
  {
    if((text == "/askAdmin"))
    {
      String msg = "Der Nutzer " + pt_user->name + ", mit der Chat ID " + chat_id + ". Möchte eine Freigabe. Erlauben?";
      bot->sendMessageWithInlineKeyboard(wmconfig.telegram_MasterChatId, msg, "", CF(keyboardJsonAdminRequest));
    }
    else if((text == "/del"))
    {
      DeletedChatID(chat_id.c_str());
    }
    else
    {
      bot->sendMessageWithInlineKeyboard(chat_id, "Du hast noch keine Berechtigungen, soll ich den Admin Fragen?", "", CF(keyboardJsonUserQuest));
    }
  }

  }
}

//handle speziel User Setting if msg from Admin
void Telegram::handleAdminMsg(telegramMessage msg)
{
  if(msg.text.startsWith("/AddbyAdmin "))
  {
    DEBUG_PRINTIN("Telegram Admin: MSG: ");
    DEBUG_PRINT(msg.text);
    String chat_id = msg.text.substring(12);
    EntitledChatID(chat_id.c_str(), true);
    bot->sendMessage(chat_id, "Admin Freigabe erhalten");
    bot->sendMessage(wmconfig.telegram_MasterChatId, "Admin Freigabe gesendet");
    chat_id = "xxx" + chat_id + "xxx";
    safe = true;
    DEBUG_PRINTIN("Telegram Admin chat-id entitled: ");
    DEBUG_PRINT(chat_id);
  }
  else if(msg.text.startsWith("/BlockbyAdmin "))
  {
    String chat_id = msg.text.substring(14);
    DeletedChatID(chat_id.c_str());
    bot->sendMessage(chat_id, "Admin hat dich entfernt");
    bot->sendMessage(wmconfig.telegram_MasterChatId, "Nutzer entfernt");
    safe = true;
    DEBUG_PRINTIN("Telegram Admin chat-id deleted: ");
    DEBUG_PRINT(chat_id);
  }
}

void Telegram::setAboChangeAndAnswere(String &chatId, telegram_user &user, String &command)
{
  if(command == "/AboWM1AN")
  {
    if(user.aboWm1 == true) bot->sendMessage(chatId, "ABO WM1 Bereits Aboniert - es wurde nichts geändert");
    else 
    {
      bot->sendMessage(chatId, "ABO WM1 wurde Aboniert");
      user.aboWm1 = true;
    }
  }
  else if(command == "/AboWM1AUS")
  {
    if(user.aboWm1 == false) bot->sendMessage(chatId, "ABO WM1 ist nicht Aboniert - es wurde nichts geändert");
    else 
    {
      bot->sendMessage(chatId, "Das ABO von WM1 wurde deaktiviert");
      user.aboWm1 = false;
    }
  }
  else if(command == "/AboWM2AN")
  {
    if(user.aboWm2 == true) bot->sendMessage(chatId, "ABO WM2 Bereits Aboniert - es wurde nichts geändert");
    else 
    {
      bot->sendMessage(chatId, "ABO WM2 wurde Aboniert");
      user.aboWm2 = true;
    }
  }
  else if(command == "/AboWM1AUS")
  {
    if(user.aboWm2 == false) bot->sendMessage(chatId, "ABO WM2 ist nicht Aboniert - es wurde nichts geändert");
    else 
    {
      bot->sendMessage(chatId, "Das ABO von WM2 wurde deaktiviert");
      user.aboWm2 = false;
    }
  }
  else if(command == "/AboLEAKAN")
  {
    if(user.aboLeak == true) bot->sendMessage(chatId, "ABO Wasserschaden Bereits Aboniert - es wurde nichts geändert");
    else 
    {
      bot->sendMessage(chatId, "ABO Wasserschaden wurde Aboniert");
      user.aboLeak = true;
    }
  }
  else if(command == "/AboLEAKAUS")
  {
    if(user.aboLeak == false) bot->sendMessage(chatId, "ABO Wasserschaden ist nicht Aboniert - es wurde nichts geändert");
    else 
    {
      bot->sendMessage(chatId, "Das ABO von Wasserschaden wurde deaktiviert");
      user.aboLeak = false;
    }
  }
  else if(command == "/AboALLAN")
  { 
    bot->sendMessage(chatId, "Es wurde alles Aboniert");
    user.aboLeak = true;
    user.aboWm2 = true;
    user.aboWm1 = true;
  }
  else if(command == "/AboALLAUS")
  {
      bot->sendMessage(chatId, "Es wurden alle Abos beendet");
      user.aboLeak = false;
      user.aboWm2 = false;
    user.aboWm1 = false;
  }
  safe = true;
}

void Telegram::Connect()
{
  String test = "";
  if(wmconfig.telegram_isTelegram && (bot == 0) && (tryToConnect <= 3))
  {
    bot.reset(new UniversalTelegramBot(wmconfig.telegram_BotToken, teleclient));
    test  = "First Config Telegram... Send Test Telegram: " ;
    bool isSend = bot->sendMessage(wmconfig.telegram_MasterChatId, "WM Online");
    if(isSend)
    {
      SetBotCommands();
      wmconfig.telegram_newSetup = false;
      test += "Hat Funktioniert";
      isBot = true;
      tryToConnect = 0;
    }
    else
    {
      test += "Das war wohl nix Check Telegram Setup";
      tryToConnect++;
      isBot = false;
    }
  }
  else if (wmconfig.telegram_isTelegram && wmconfig.telegram_newSetup && (tryToConnect <= 3))
  {
    test  = "Change Telegram Setup... Send Test Telegram: " ;
    bot->updateToken(wmconfig.telegram_BotToken);
    bool isSend = bot->sendMessage(wmconfig.telegram_MasterChatId, "WM Online");
    if(isSend)
    {
      SetBotCommands();
      wmconfig.telegram_newSetup = false;
      test += "Hat Funktioniert";
      isBot = true;
      tryToConnect = 0;
    }
    else
    {
      test += "Das war wohl nix Check Telegram Setup";
      tryToConnect++;
    }
  }
  else
  {
   if(tryToConnect >= 3)
   {
      test = " Telegram zuviele Fehlversuche, Ändere das Setup Telegram wird deaktiviert ";
      wmconfig.telegram_isTelegram = false;
      tryToConnect = 0;

   } 
      test = "Kein Telegram Setup gefunden: Telegram ist Deaktiviert";
      isBot = false;
  }
   DEBUG_PRINT(test);

}

void Telegram::TelegramLoop()
{
  if(isBot && !wmconfig.telegram_newSetup)
  {
  if (millis() - bot_lasttime > BOT_MTBS)
  {
    int numNewMessages = bot->getUpdates(bot->last_message_received + 1);
    while (numNewMessages)
    {
      DEBUG_PRINT("got response");
      handleNewMessages(numNewMessages);
      numNewMessages = bot->getUpdates(bot->last_message_received + 1);
    }

    bot_lasttime = millis();
  }
  }
  else 
  {
    if (millis() -bot_lastConnection > BOT_CT_TIMEOUT)
    {
      Connect();
      bot_lastConnection = millis();
    }
  }
}

bool Telegram::IsChatId(const char* chatId)
{
  if (wmconfig.telegram_users.count(chatId) != 0) return true;
  else return false;
}

bool Telegram::IsEntitled(const char* chatId)
{
auto it = wmconfig.telegram_users.find(chatId);
 if(it != wmconfig.telegram_users.end())
 {
   return it->second.isEntitled;
 }
  else return false;
}

void Telegram::AddChatId(const char* chatId, const char* name)
{
  telegram_user temp;
  if(wmconfig.telegram_users.count(chatId) == 0)
  {
    temp.name = name;
    temp.aboWm1 = false;
    temp.aboWm2 = false;
    if(strcmp(chatId, wmconfig.telegram_MasterChatId.c_str()) == 0)
    {
      temp.isEntitled = true;
      safe = true;
      DEBUG_PRINT("Telegram AddAdmin");
    } 
    else temp.isEntitled = false;
    temp.aboLeak = false;
    wmconfig.telegram_users.insert(std::make_pair(chatId, temp));
    DEBUG_PRINTIN("Telegram: New User Detected - ChatID :");
    DEBUG_PRINT(chatId);
  }
  else DEBUG_PRINT("Telegram: User Allready in List PLS Check Code");

}

void Telegram::ChangeChatIDSettings(const char* chatId, bool wm1, bool wm2, bool leak)
{
 auto it = wmconfig.telegram_users.find(chatId);
 if(it != wmconfig.telegram_users.end())
 {
   it->second.aboWm1 = wm1;
   it->second.aboWm2 = wm2;
   it->second.aboLeak = leak;
   DEBUG_PRINT("Telegram: New Settings for User");
 }
 else DEBUG_PRINT("Telegram: User Not Find not posible to set new Settings");

}

void Telegram::EntitledChatID(const char* chatId, bool isEntitled)
{
 auto it = wmconfig.telegram_users.find(chatId);
 if(it != wmconfig.telegram_users.end())
 {
   it->second.isEntitled = isEntitled;
   DEBUG_PRINT("Telegram: User Settings entitled set");
 }
 else DEBUG_PRINT("Telegram: User Not found not posible to set entitled");
}

void Telegram::DeletedChatID(const char* chatId)
{
  wmconfig.telegram_users.erase(chatId);
}

void Telegram::SetBotCommands()
{
  DEBUG_PRINT((bot->setMyCommands(CF(commands))? "Commands klappt" : "Commands klappt nicht"));
}

void Telegram::StartKeyBoardMsg(String chatID)
{
  DEBUG_PRINT((bot->sendMessageWithInlineKeyboard(chatID, "Melde UI: Wähle aus....", "", CF(keyboardJsonStart)))? "Keyboard klappt" : "Keyboard klappt nicht");
}

and my settings for the inline keyboards, where I finally came up with this PROGMEM solution, but this did not lead to success:

const char keyboardJsonStart[] PROGMEM = R"=====(
                            [
                            [{ "text" : "Bedienungsanleitung", "callback_data" : "/help" }],
                            [{ "text" : "Status", "callback_data" : "/status" }],
                            [{ "text" : "Abo Maschine 1 AN/AUS", "callback_data" : "/wm1" }],
                            [{ "text" : "Abo Maschine 2 AN/AUS", "callback_data" : "/wm2" }],
                            [{ "text" : "Abo WasserSchaden AN/AUS", "callback_data" : "/leak" }],
                            [{ "text" : "Abo Alle Geräte AN/AUS", "callback_data" : "/all" }]
                            ]
                            )=====";

const char keyboardJsonWM1[] PROGMEM = R"=====(
                            [
                            [{ "text" : "ABO AN", "callback_data" : "/AboWM1AN" }],
                            [{ "text" : "ABO AUS", "callback_data" : "/AboWM1AUS" }]
                            ]
                            )=====";

const char keyboardJsonWM2[] PROGMEM = R"=====(
                            [
                            [{ "text" : "ABO AN", "callback_data" : "/AboWM2AN" }],
                            [{ "text" : "ABO AUS", "callback_data" : "/AboWM2AUS" }]
                            ]
                            )=====";

const char keyboardJsonLEAK[] PROGMEM = R"=====(
                            [
                            [{ "text" : "ABO AN", "callback_data" : "/AboLEAKAN" }],
                            [{ "text" : "ABO AUS", "callback_data" : "/AboLEAKAUS" }]
                            ]
                            )=====";

const char keyboardJsonALL[] PROGMEM = R"=====(
                            [
                            [{ "text" : "ABO AN", "callback_data" : "/AboALLAN" }],
                            [{ "text" : "ABO AUS", "callback_data" : "/AboALLAUS" }]
                            ]
                            )=====";

const char keyboardJsonAdminRequest[] PROGMEM = R"=====(
                            [
                            [{ "text" : "Ja", "callback_data" : "/AddbyAdmin "+ chat_id + "" }],
                            [{ "text" : "Nein", "callback_data" : "/BlockbyAdmin "+ chat_id + "" }]
                            ]
                            )=====";   

const char keyboardJsonUserQuest[] PROGMEM = R"=====(
                            [
                            [{ "text" : "Ja bitte", "callback_data" : "/askAdmin" }],
                            [{ "text" : "Nein danke", "callback_data" : "/del" }]
                            ]
                            )=====";       

const char commands[] PROGMEM = R"=====(
                            [
                            {"command":"help",  "description":"Bedienungsanleitung"},
                            {"command":"start", "description":"Hier kannst du mich Starten"},
                            {"command":"wm1", "description":"Mitteilungen für Maschine 1 An und Ausschalten"},
                            {"command":"wm2", "description":"Mitteilungen für Maschine 2 An und Ausschalten"},
                            {"command":"leak", "description":"Mitteilungen für Wasserschaden An und Ausschalten"},
                            {"command":"all", "description":"Alle Mitteilungen An und Ausschalten"},
                            {"command":"status","description":"Den Aktuellen Status der Maschinen abfragen"}
                            ]
                            )====="; 

I thank you in advance and look forward to tips to fix the error ...

klementuel commented 3 years ago

I think it was the combination with my DEBUG_Print Stuff without I think it works so I build my Debug Section like this: but any ideas how to make the telegram Code a little bit more memory safe?

Debug.h:

/**
 * @brief Comment out if you are not in the code development phase
 * 
 */
#define DEBUG

#ifdef DEBUG
    //Debug Print with Text like #define DEBUG_PRINT_TXT("Test")
    #define DEBUG_PRINT_TXT(x)  Serial.println (F(x))
    #define DEBUG_PRINTIN_TXT(x)  Serial.print (F(x))
    #define DEBUG_WAIT() Serial.print (F("."))

    //DEBUG Print for Dynamik Staff
    #define DEBUG_PRINT(x)  Serial.println (x)
    #define DEBUG_PRINTIN(x)  Serial.print (x)

#else
    #define DEBUG_PRINT_TXT(x)  
    #define DEBUG_PRINTIN_TXT(x) 
    #define DEBUG_PRINT(x)
    #define DEBUG_PRINTIN(x)
    #define DEBUG_WAIT()
#endif
klementuel commented 3 years ago

Ok this was not a Solution, it give me just more time before the ESP Crash....

klementuel commented 3 years ago

Hello Every one, I think I found the Problem, I think there is a problem with with the Wifi Secure Client... after long nights I found out that every things works a little bit better when I start and stop the client manually, but not at 100% If anybody has an better idea, pls I need help ;-))

for the rest Merry Christmas :-)