CTBot icon indicating copy to clipboard operation
CTBot copied to clipboard

CTBot in its own thread

Open grosdax opened this issue 3 years ago • 1 comments

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 <[email protected]>
 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);
}

grosdax avatar Apr 03 '21 18:04 grosdax

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

shurillu avatar Apr 06 '21 06:04 shurillu