shurillu / CTBot

A simple (and easy to use) Arduino Telegram BOT Library for ESP8266/ESP32
MIT License
147 stars 34 forks source link

CTBot in its own thread #82

Open grosdax opened 3 years ago

grosdax commented 3 years ago

Thank you for all the good work on this library.

Just wanted to share a wrapper i wrote, it allows to have the CTBot playing in its own thread without disturbing the main thread. It does not wrap all the features, but only the one i am currently using, but it would be very straightforward to write the missing functions.

The thread is looping forever and stacking the received messages in a std::queue. The messages are removed from queue when getNewMessage is called from the main thread

The usage is very simple, after including CTBot.h, include ThreadedCTBot.h then replace your CTBot instance by a ThreadedCTBot. The thread is created in the setTelegramToken function, then all wrapped functions are simply locked by a mutex. Note that this should be only working on an ESP32.

Some additionnal work would be needed in order to make it bullet proof, but the concept does work.

#include <queue>

class LockGuard
{
public:
    LockGuard()
    {
        if(!semaphore)
            vSemaphoreCreateBinary(semaphore);

        if(semaphore)
            xSemaphoreTake(semaphore,portMAX_DELAY);
    }

    virtual ~LockGuard()
    {
        if(semaphore)
            xSemaphoreGive(semaphore);
    }

private:
    static SemaphoreHandle_t semaphore;
};
SemaphoreHandle_t LockGuard::semaphore = nullptr;

class ThreadedCTBot
{
public:
    ThreadedCTBot() {}  
    static ThreadedCTBot* getInstance() { return instance; }

    bool wifiConnect(const String& ssid, const String& password = "")
    {
        LockGuard lock();
        return bot.wifiConnect(ssid, password);
    }

    bool testConnection(void)
    {
        LockGuard lock();
        return bot.testConnection();
    }

    void setTelegramToken(const String& token)
    {
        instance = this;
        if(nullptr == BotTaskHandle)
            xTaskCreatePinnedToCore(ThreadedCTBot::BotTask, "Telegram_Bot_task", 10 * 1024, nullptr, 1, &BotTaskHandle, 1);

        LockGuard lock();
        bot.setTelegramToken(token);
    }

    bool hasMessages()
    {
        LockGuard lock();
        return !messages.empty();
    }

    TBMessage nextMessage()
    {
        LockGuard lock();
        TBMessage message = std::move(messages.front());
        messages.pop();
        return message;
    }

    CTBotMessageType getNewMessage(TBMessage &message, bool blocking = false)
    {
        if(blocking)
        {
            while(!hasMessages())
            {
                delay(1);
            }
        }

        if(!hasMessages())
            return CTBotMessageNoData;

        LockGuard lock();
        message = nextMessage();
        return message.messageType;
    }

    bool getMe(TBUser &user)
    {
        LockGuard lock();
        return bot.getMe(user);
    }
    int32_t sendMessage(int64_t id, const String& message, const String& keyboard = "")
    {
        LockGuard lock();
        return bot.sendMessage(id, message, keyboard);
    }
    int32_t sendMessage(int64_t id, const String& message, CTBotInlineKeyboard &keyboard)
    {
        LockGuard lock();
        return bot.sendMessage(id, message, keyboard);
    }
    int32_t sendMessage(int64_t id, const String& message, CTBotReplyKeyboard  &keyboard)
    {
        LockGuard lock();
        return bot.sendMessage(id, message, keyboard);
    }

private:    
    // Task callback
    static void BotTask(void* pvParameters)
    {
        ThreadedCTBot* ThreadedCTBot = ThreadedCTBot::getInstance();
        if(ThreadedCTBot)
            ThreadedCTBot->loop();
    }

    void loop()
    {
        while(true)
        {
            TBMessage message;
            // no lock here
            CTBotMessageType messagetype = bot.getNewMessage(message, true);
            if(messagetype != CTBotMessageNoData)
            {
                LockGuard lock();
                messages.push(std::move(message));
            }           
        }
    }

private:
    CTBot bot;
    std::queue<TBMessage> messages;
    static ThreadedCTBot* instance; 
    static TaskHandle_t BotTaskHandle;
};

ThreadedCTBot* ThreadedCTBot::instance = nullptr;
TaskHandle_t ThreadedCTBot::BotTaskHandle = nullptr;

and as an example, the echoBot implementation:

/*
 Name:          echoBot.ino
 Created:       12/21/2017
 Author:        Stefano Ledda <shurillu@tiscalinet.it>
 Description: a simple example that check for incoming messages
              and reply the sender with the received message
*/
#define CTBOT_DEBUG_MODE CTBOT_DEBUG_ALL

#include "CTBot.h"
#include "ThreadedCTBot.h"
ThreadedCTBot myBot;

String ssid  = "mySSID"    ; // REPLACE mySSID WITH YOUR WIFI SSID
String pass  = "myPassword"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY
String token = "myToken"   ; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN

void setup() {
    // initialize the Serial
    Serial.begin(115200);
    Serial.println("Starting TelegramBot...");

    // connect the ESP8266 to the desired access point
    myBot.wifiConnect(ssid, pass);

    // set the telegram bot token
    myBot.setTelegramToken(token);

    // check if all things are ok
    if (myBot.testConnection())
        Serial.println("\ntestConnection OK");
    else
        Serial.println("\ntestConnection NOK");
}

void loop() {
    // a variable to store telegram message data
    TBMessage msg;

    // if there is an incoming message... 
    if (myBot.getNewMessage(msg))
        // ...forward it to the sender
        myBot.sendMessage(msg.sender.id, msg.text);

  /* or could be replaced by:
  if (myBot.hasMessages())
  {
    TBMessage msg = myBot.nextMessage();
    myBot.sendMessage(msg.sender.id, msg.text);
  }
  */

    // wait 500 milliseconds
    delay(500);
}
shurillu commented 3 years ago

Hello grosdax, thank you for using the library and for your suggestions, they are very useful! The only problem that I notice is the incompatibility with the ESP8266 paltform, as you pointed out. So I'm wondering if make a "wrapper, threaded" class only for the ESP32... Thanks a lot! Cheers,

Stefano