From c975ad85366ad9f4f57ca861c380004dd037254d Mon Sep 17 00:00:00 2001 From: Matthijs van der Burgh Date: Tue, 30 May 2023 13:42:33 +0200 Subject: [PATCH 1/6] [WIP] Updater -> Application --- src/telegram_ros/bridge.py | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/telegram_ros/bridge.py b/src/telegram_ros/bridge.py index dbb161b..b6cbfae 100644 --- a/src/telegram_ros/bridge.py +++ b/src/telegram_ros/bridge.py @@ -1,3 +1,5 @@ +import asyncio + import functools import cv2 @@ -10,7 +12,7 @@ from std_msgs.msg import String, Header from telegram import Location, ReplyKeyboardMarkup, Update from telegram.error import TimedOut -from telegram.ext import Updater, CallbackContext, CommandHandler, MessageHandler, filters +from telegram.ext import Application, CallbackContext, CommandHandler, MessageHandler, filters from telegram_ros.msg import Options @@ -94,18 +96,16 @@ def __init__(self, api_token, caption_as_frame_id): # Telegram IO self._telegram_chat_id = None - self._telegram_updater = Updater(api_token) - self._telegram_updater.dispatcher.add_error_handler( + self._telegram_app = Application.builder().token(api_token).build() + self._telegram_app.add_error_handler( lambda _, update, error: rospy.logerr("Update {} caused error {}".format(update, error)) ) - self._telegram_updater.dispatcher.add_handler(CommandHandler("start", self._telegram_start_callback)) - self._telegram_updater.dispatcher.add_handler(CommandHandler("stop", self._telegram_stop_callback)) - self._telegram_updater.dispatcher.add_handler(MessageHandler(filters.TEXT, self._telegram_message_callback)) - self._telegram_updater.dispatcher.add_handler(MessageHandler(filters.PHOTO, self._telegram_photo_callback)) - self._telegram_updater.dispatcher.add_handler( - MessageHandler(filters.LOCATION, self._telegram_location_callback) - ) + self._telegram_app.add_handler(CommandHandler("start", self._telegram_start_callback)) + self._telegram_app.add_handler(CommandHandler("stop", self._telegram_stop_callback)) + self._telegram_app.add_handler(MessageHandler(filters.TEXT, self._telegram_message_callback)) + self._telegram_app.add_handler(MessageHandler(filters.PHOTO, self._telegram_photo_callback)) + self._telegram_app.add_handler(MessageHandler(filters.LOCATION, self._telegram_location_callback)) rospy.core.add_preshutdown_hook(self._shutdown) @@ -114,7 +114,7 @@ def _shutdown(self, reason: str): Sending a message to the current chat id on destruction. """ if self._telegram_chat_id: - self._telegram_updater.bot.send_message( + self._telegram_app.bot.send_message( self._telegram_chat_id, f"Stopping Telegram ROS bridge, ending this chat. Reason of shutdown: {reason}." " Type /start to connect again after starting a new Telegram ROS bridge.", @@ -124,13 +124,13 @@ def spin(self): """ Starts the Telegram update thread and spins until a SIGINT is received """ - self._telegram_updater.start_polling() + self._telegram_app.run_polling() # ToDo: this is blocking rospy.loginfo("Telegram updater started polling, spinning ..") rospy.spin() rospy.loginfo("Shutting down Telegram updater ...") - self._telegram_updater.stop() + self._telegram_app.stop() def _telegram_start_callback(self, update: Update, _: CallbackContext): """ @@ -149,7 +149,7 @@ def _telegram_start_callback(self, update: Update, _: CallbackContext): new_user = "'somebody'" if hasattr(update.message.chat, "first_name") and update.message.chat.first_name: new_user = update.message.chat.first_name - self._telegram_updater.bot.send_message( + self._telegram_app.bot.send_message( self._telegram_chat_id, "Lost ROS bridge connection to this chat_id {} ({} took over)".format( update.message.chat_id, new_user @@ -203,7 +203,7 @@ def _ros_string_callback(self, msg: String): :param msg: String message """ if msg.data: - self._telegram_updater.bot.send_message(self._telegram_chat_id, msg.data) + self._telegram_app.bot.send_message(self._telegram_chat_id, msg.data) else: rospy.logwarn("Ignoring empty string message") @@ -216,7 +216,8 @@ def _telegram_photo_callback(self, update: Update, _: CallbackContext): :param update: Received update that holds the chat_id and message data """ rospy.logdebug("Received image, downloading highest resolution image ...") - byte_array = update.message.photo[-1].get_file().download_as_bytearray() + new_file = asyncio.run(update.message.photo[-1].get_file()) + byte_array = asyncio.run(new_file.download_as_bytearray()) rospy.logdebug("Download complete, publishing ...") img = cv2.imdecode(np.asarray(byte_array, dtype=np.uint8), cv2.IMREAD_COLOR) @@ -238,7 +239,7 @@ def _ros_image_callback(self, msg: Image): :param msg: Image message """ cv2_img = self._cv_bridge.imgmsg_to_cv2(msg, "bgr8") - self._telegram_updater.bot.send_photo( + self._telegram_app.bot.send_photo( self._telegram_chat_id, photo=BytesIO(cv2.imencode(".jpg", cv2_img)[1].tobytes()), caption=msg.header.frame_id, @@ -268,7 +269,7 @@ def _ros_location_callback(self, msg: NavSatFix): :param msg: NavSatFix that the robot wants to share """ - self._telegram_updater.bot.send_location(self._telegram_chat_id, location=Location(msg.longitude, msg.latitude)) + self._telegram_app.bot.send_location(self._telegram_chat_id, location=Location(msg.longitude, msg.latitude)) @ros_callback def _ros_options_callback(self, msg: Options): @@ -283,7 +284,7 @@ def chunks(l, n): # noqa: E741 for i in range(0, len(l), n): yield l[i : i + n] # noqa: E203 - self._telegram_updater.bot.send_message( + self._telegram_app.bot.send_message( self._telegram_chat_id, text=msg.question, reply_markup=ReplyKeyboardMarkup( From c5039b64129446bcef5ed7833eb4af2908a47027 Mon Sep 17 00:00:00 2001 From: PetervDooren Date: Sat, 3 Jun 2023 16:12:54 +0200 Subject: [PATCH 2/6] telegram handlers should be async --- src/telegram_ros/bridge.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/telegram_ros/bridge.py b/src/telegram_ros/bridge.py index b6cbfae..f5e93fe 100644 --- a/src/telegram_ros/bridge.py +++ b/src/telegram_ros/bridge.py @@ -132,7 +132,7 @@ def spin(self): self._telegram_app.stop() - def _telegram_start_callback(self, update: Update, _: CallbackContext): + async def _telegram_start_callback(self, update: Update, _: CallbackContext): """ Called when a Telegram user sends the '/start' event to the bot, using this event, the bridge can be connected to a specific conversation. @@ -169,7 +169,7 @@ def _telegram_start_callback(self, update: Update, _: CallbackContext): ) @telegram_callback - def _telegram_stop_callback(self, update: Update, _: CallbackContext): + async def _telegram_stop_callback(self, update: Update, _: CallbackContext): """ Called when a Telegram user sends the '/stop' event to the bot. Then, the user is disconnected from the bot and will no longer receive messages. @@ -185,7 +185,7 @@ def _telegram_stop_callback(self, update: Update, _: CallbackContext): self._telegram_chat_id = None @telegram_callback - def _telegram_message_callback(self, update: Update, _: CallbackContext): + async def _telegram_message_callback(self, update: Update, _: CallbackContext): """ Called when a new Telegram message has been received. The method will verify whether the incoming message is from the bridges Telegram conversation by comparing the chat_id. @@ -208,7 +208,7 @@ def _ros_string_callback(self, msg: String): rospy.logwarn("Ignoring empty string message") @telegram_callback - def _telegram_photo_callback(self, update: Update, _: CallbackContext): + async def _telegram_photo_callback(self, update: Update, _: CallbackContext): """ Called when a new Telegram photo has been received. The method will verify whether the incoming message is from the bridges Telegram conversation by comparing the chat_id. @@ -246,7 +246,7 @@ def _ros_image_callback(self, msg: Image): ) @telegram_callback - def _telegram_location_callback(self, update: Update, _: CallbackContext): + async def _telegram_location_callback(self, update: Update, _: CallbackContext): """ Called when a new Telegram Location is received. The method will verify whether the incoming Location is from the bridged Telegram conversation by comparing the chat_id. From 0b9fe34cdab702ed8c84c7eb0078827c3a2e9439 Mon Sep 17 00:00:00 2001 From: PetervDooren Date: Sat, 3 Jun 2023 16:13:20 +0200 Subject: [PATCH 3/6] await reply text --- src/telegram_ros/bridge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/telegram_ros/bridge.py b/src/telegram_ros/bridge.py index f5e93fe..c25eb8a 100644 --- a/src/telegram_ros/bridge.py +++ b/src/telegram_ros/bridge.py @@ -159,12 +159,12 @@ async def _telegram_start_callback(self, update: Update, _: CallbackContext): rospy.loginfo("Starting Telegram ROS bridge for new chat id {}".format(update.message.chat_id)) self._telegram_chat_id = update.message.chat_id - update.message.reply_text( + await update.message.reply_text( "Telegram ROS bridge initialized, only replying to chat_id {} (current)".format(self._telegram_chat_id) ) else: rospy.logwarn("Discarding message. User {} not whitelisted".format(update.message.from_user)) - update.message.reply_text( + await update.message.reply_text( "You (user id {}) are not authorized to chat with this bot".format(update.message.from_user.id) ) From b98141b8e173b5eeada4e8d42dd84c25ba0ddd4f Mon Sep 17 00:00:00 2001 From: Matthijs van der Burgh Date: Sun, 4 Jun 2023 12:26:29 +0200 Subject: [PATCH 4/6] All reply_text are now awaited --- src/telegram_ros/bridge.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/telegram_ros/bridge.py b/src/telegram_ros/bridge.py index c25eb8a..5da6de8 100644 --- a/src/telegram_ros/bridge.py +++ b/src/telegram_ros/bridge.py @@ -28,14 +28,14 @@ def telegram_callback(callback_function): """ @functools.wraps(callback_function) - def wrapper(self, update: Update, context: CallbackContext): + async def wrapper(self, update: Update, context: CallbackContext): rospy.logdebug("Incoming update from telegram: %s", update) if self._telegram_chat_id is None: rospy.logwarn("Discarding message. No active chat_id.") - update.message.reply_text("ROS Bridge not initialized. Type /start to set-up ROS bridge") + await update.message.reply_text("ROS Bridge not initialized. Type /start to set-up ROS bridge") elif self._telegram_chat_id != update.message.chat_id: rospy.logwarn("Discarding message. Invalid chat_id") - update.message.reply_text( + await update.message.reply_text( "ROS Bridge initialized to another chat_id. Type /start to connect to this chat_id" ) else: @@ -178,9 +178,9 @@ async def _telegram_stop_callback(self, update: Update, _: CallbackContext): """ rospy.loginfo("Stopping Telegram ROS bridge for chat id {}".format(self._telegram_chat_id)) - update.message.reply_text( - "Disconnecting chat_id {}. So long and thanks for all the fish!" - " Type /start to reconnect".format(self._telegram_chat_id) + await update.message.reply_text( + f"Disconnecting chat_id {self._telegram_chat_id}. So long and thanks for all the fish!" + " Type /start to reconnect" ) self._telegram_chat_id = None From c6e567a0a8b3851d6bdd7ae1dd760e20df536885 Mon Sep 17 00:00:00 2001 From: Matthijs van der Burgh Date: Thu, 29 Jun 2023 09:37:29 +0200 Subject: [PATCH 5/6] split msgs --- .github/workflows/lint.yml | 2 +- .github/workflows/main.yml | 26 +++++++++++++++++-- .gitmodules | 4 +-- telegram_ros/CMakeLists.txt | 22 ++++++++++++++++ README.md => telegram_ros/README.md | 0 docs => telegram_ros/docs | 0 package.xml => telegram_ros/package.xml | 6 +---- rosdoc.yaml => telegram_ros/rosdoc.yaml | 0 .../scripts}/telegram_ros_bridge | 0 setup.py => telegram_ros/setup.py | 0 .../src}/telegram_ros/__init__.py | 0 .../src}/telegram_ros/bridge.py | 2 +- {test => telegram_ros/test}/test_import.py | 0 .../CMakeLists.txt | 17 ++---------- {msg => telegram_ros_msgs/msg}/Options.msg | 0 telegram_ros_msgs/package.xml | 22 ++++++++++++++++ 16 files changed, 75 insertions(+), 26 deletions(-) create mode 100644 telegram_ros/CMakeLists.txt rename README.md => telegram_ros/README.md (100%) rename docs => telegram_ros/docs (100%) rename package.xml => telegram_ros/package.xml (85%) rename rosdoc.yaml => telegram_ros/rosdoc.yaml (100%) rename {scripts => telegram_ros/scripts}/telegram_ros_bridge (100%) rename setup.py => telegram_ros/setup.py (100%) rename {src => telegram_ros/src}/telegram_ros/__init__.py (100%) rename {src => telegram_ros/src}/telegram_ros/bridge.py (99%) rename {test => telegram_ros/test}/test_import.py (100%) rename CMakeLists.txt => telegram_ros_msgs/CMakeLists.txt (57%) rename {msg => telegram_ros_msgs/msg}/Options.msg (100%) create mode 100644 telegram_ros_msgs/package.xml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5f2c44b..fef64e7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,4 +11,4 @@ jobs: submodules: recursive - uses: psf/black@stable with: - options: --check --diff --color -l 120 --exclude docs + options: telegram_ros --check --diff --color -l 120 --exclude docs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f3af87..ddffe0a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,11 +3,33 @@ name: CI on: [push, pull_request] jobs: + matrix: + name: Determine modified packages + runs-on: ubuntu-latest + outputs: + packages: ${{ steps.modified-packages.outputs.packages }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 300 + - name: Commit Range + id: commit-range + uses: tue-robotics/tue-env/ci/commit-range@master + - name: Modified packages + id: modified-packages + uses: tue-robotics/tue-env/ci/modified-packages@master + with: + commit-range: ${{ steps.commit-range.outputs.commit-range }} tue-ci: - name: TUe CI - ${{ github.event_name }} + name: TUe CI - ${{ matrix.package }} runs-on: ubuntu-latest + needs: matrix + strategy: + fail-fast: false + matrix: + package: ${{ fromJson(needs.matrix.outputs.packages) }} steps: - name: TUe CI uses: tue-robotics/tue-env/ci/main@master with: - package: ${{ github.event.repository.name }} + package: ${{ matrix.package }} diff --git a/.gitmodules b/.gitmodules index 9c4ee42..6f5f00f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ -[submodule "docs"] - path = docs +[submodule "telegram_ros/docs"] + path = telegram_ros/docs url = https://github.com/tue-robotics/tue_documentation_python.git branch = master diff --git a/telegram_ros/CMakeLists.txt b/telegram_ros/CMakeLists.txt new file mode 100644 index 0000000..19a729d --- /dev/null +++ b/telegram_ros/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.0.2) +project(telegram_ros) +find_package(catkin REQUIRED) + +find_package(catkin REQUIRED COMPONENTS + sensor_msgs + std_msgs +) + +catkin_python_setup() + +catkin_package( + CATKIN_DEPENDS sensor_msgs std_msgs +) + +############# +## Testing ## +############# + +if (CATKIN_ENABLE_TESTING) + catkin_add_nosetests(test) +endif() diff --git a/README.md b/telegram_ros/README.md similarity index 100% rename from README.md rename to telegram_ros/README.md diff --git a/docs b/telegram_ros/docs similarity index 100% rename from docs rename to telegram_ros/docs diff --git a/package.xml b/telegram_ros/package.xml similarity index 85% rename from package.xml rename to telegram_ros/package.xml index 8d909d0..9e20e4d 100644 --- a/package.xml +++ b/telegram_ros/package.xml @@ -15,13 +15,9 @@ sensor_msgs std_msgs - - message_generation - - message_runtime + telegram_ros_msgs cv_bridge - message_runtime python3-numpy python3-opencv python3-telegram-bot diff --git a/rosdoc.yaml b/telegram_ros/rosdoc.yaml similarity index 100% rename from rosdoc.yaml rename to telegram_ros/rosdoc.yaml diff --git a/scripts/telegram_ros_bridge b/telegram_ros/scripts/telegram_ros_bridge similarity index 100% rename from scripts/telegram_ros_bridge rename to telegram_ros/scripts/telegram_ros_bridge diff --git a/setup.py b/telegram_ros/setup.py similarity index 100% rename from setup.py rename to telegram_ros/setup.py diff --git a/src/telegram_ros/__init__.py b/telegram_ros/src/telegram_ros/__init__.py similarity index 100% rename from src/telegram_ros/__init__.py rename to telegram_ros/src/telegram_ros/__init__.py diff --git a/src/telegram_ros/bridge.py b/telegram_ros/src/telegram_ros/bridge.py similarity index 99% rename from src/telegram_ros/bridge.py rename to telegram_ros/src/telegram_ros/bridge.py index 5da6de8..c7551c5 100644 --- a/src/telegram_ros/bridge.py +++ b/telegram_ros/src/telegram_ros/bridge.py @@ -13,7 +13,7 @@ from telegram import Location, ReplyKeyboardMarkup, Update from telegram.error import TimedOut from telegram.ext import Application, CallbackContext, CommandHandler, MessageHandler, filters -from telegram_ros.msg import Options +from telegram_ros_msgs.msg import Options WHITELIST = "~whitelist" diff --git a/test/test_import.py b/telegram_ros/test/test_import.py similarity index 100% rename from test/test_import.py rename to telegram_ros/test/test_import.py diff --git a/CMakeLists.txt b/telegram_ros_msgs/CMakeLists.txt similarity index 57% rename from CMakeLists.txt rename to telegram_ros_msgs/CMakeLists.txt index ba67bd2..9bc9e6b 100644 --- a/CMakeLists.txt +++ b/telegram_ros_msgs/CMakeLists.txt @@ -1,15 +1,11 @@ cmake_minimum_required(VERSION 3.0.2) -project(telegram_ros) +project(telegram_ros_msgs) find_package(catkin REQUIRED) find_package(catkin REQUIRED COMPONENTS message_generation - sensor_msgs - std_msgs ) -catkin_python_setup() - # Generate messages in the 'msg' folder add_message_files( FILES @@ -19,18 +15,9 @@ add_message_files( # Generate added messages and services with any dependencies listed here generate_messages( DEPENDENCIES - sensor_msgs - std_msgs ) catkin_package( - CATKIN_DEPENDS message_runtime sensor_msgs std_msgs + CATKIN_DEPENDS message_runtime ) -############# -## Testing ## -############# - -if (CATKIN_ENABLE_TESTING) - catkin_add_nosetests(test) -endif() diff --git a/msg/Options.msg b/telegram_ros_msgs/msg/Options.msg similarity index 100% rename from msg/Options.msg rename to telegram_ros_msgs/msg/Options.msg diff --git a/telegram_ros_msgs/package.xml b/telegram_ros_msgs/package.xml new file mode 100644 index 0000000..12844dc --- /dev/null +++ b/telegram_ros_msgs/package.xml @@ -0,0 +1,22 @@ + + + + telegram_ros_msgs + 0.0.0 + The telegram_ros_msgs package + + Rein Appeldoorn + + MIT + + catkin + + message_generation + + message_runtime + + message_runtime + + From 2c192e2fdda6fdb306502856aa6fb4ff9d28b5fe Mon Sep 17 00:00:00 2001 From: Matthijs van der Burgh Date: Thu, 29 Jun 2023 11:03:31 +0200 Subject: [PATCH 6/6] Fix last async issues Only spin needs to be done, which is not easy --- telegram_ros/src/telegram_ros/bridge.py | 36 ++++++++++++++----------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/telegram_ros/src/telegram_ros/bridge.py b/telegram_ros/src/telegram_ros/bridge.py index c7551c5..92d3243 100644 --- a/telegram_ros/src/telegram_ros/bridge.py +++ b/telegram_ros/src/telegram_ros/bridge.py @@ -39,7 +39,7 @@ async def wrapper(self, update: Update, context: CallbackContext): "ROS Bridge initialized to another chat_id. Type /start to connect to this chat_id" ) else: - callback_function(self, update, context) + await callback_function(self, update, context) return wrapper @@ -58,7 +58,7 @@ def wrapper(self, msg): rospy.logerr("ROS Bridge not initialized, dropping message of type %s", msg._type) else: try: - callback_function(self, msg) + asyncio.run(callback_function(self, msg)) except TimedOut as e: rospy.logerr("Telegram timeout: %s", e) @@ -114,10 +114,12 @@ def _shutdown(self, reason: str): Sending a message to the current chat id on destruction. """ if self._telegram_chat_id: - self._telegram_app.bot.send_message( - self._telegram_chat_id, - f"Stopping Telegram ROS bridge, ending this chat. Reason of shutdown: {reason}." - " Type /start to connect again after starting a new Telegram ROS bridge.", + asyncio.run( + self._telegram_app.bot.send_message( + self._telegram_chat_id, + f"Stopping Telegram ROS bridge, ending this chat. Reason of shutdown: {reason}." + " Type /start to connect again after starting a new Telegram ROS bridge.", + ) ) def spin(self): @@ -196,14 +198,14 @@ async def _telegram_message_callback(self, update: Update, _: CallbackContext): self._from_telegram_string_publisher.publish(String(data=text)) @ros_callback - def _ros_string_callback(self, msg: String): + async def _ros_string_callback(self, msg: String): """ Called when a new ROS String message is coming in that should be sent to the Telegram conversation :param msg: String message """ if msg.data: - self._telegram_app.bot.send_message(self._telegram_chat_id, msg.data) + await self._telegram_app.bot.send_message(self._telegram_chat_id, msg.data) else: rospy.logwarn("Ignoring empty string message") @@ -216,8 +218,8 @@ async def _telegram_photo_callback(self, update: Update, _: CallbackContext): :param update: Received update that holds the chat_id and message data """ rospy.logdebug("Received image, downloading highest resolution image ...") - new_file = asyncio.run(update.message.photo[-1].get_file()) - byte_array = asyncio.run(new_file.download_as_bytearray()) + new_file = await update.message.photo[-1].get_file() + byte_array = await new_file.download_as_bytearray() rospy.logdebug("Download complete, publishing ...") img = cv2.imdecode(np.asarray(byte_array, dtype=np.uint8), cv2.IMREAD_COLOR) @@ -232,14 +234,14 @@ async def _telegram_photo_callback(self, update: Update, _: CallbackContext): self._from_telegram_string_publisher.publish(String(data=update.message.caption)) @ros_callback - def _ros_image_callback(self, msg: Image): + async def _ros_image_callback(self, msg: Image): """ Called when a new ROS Image message is coming in that should be sent to the Telegram conversation :param msg: Image message """ cv2_img = self._cv_bridge.imgmsg_to_cv2(msg, "bgr8") - self._telegram_app.bot.send_photo( + await self._telegram_app.bot.send_photo( self._telegram_chat_id, photo=BytesIO(cv2.imencode(".jpg", cv2_img)[1].tobytes()), caption=msg.header.frame_id, @@ -263,16 +265,18 @@ async def _telegram_location_callback(self, update: Update, _: CallbackContext): ) @ros_callback - def _ros_location_callback(self, msg: NavSatFix): + async def _ros_location_callback(self, msg: NavSatFix): """ Called when a new ROS NavSatFix message is coming in that should be sent to the Telegram conversation :param msg: NavSatFix that the robot wants to share """ - self._telegram_app.bot.send_location(self._telegram_chat_id, location=Location(msg.longitude, msg.latitude)) + await self._telegram_app.bot.send_location( + self._telegram_chat_id, location=Location(msg.longitude, msg.latitude) + ) @ros_callback - def _ros_options_callback(self, msg: Options): + async def _ros_options_callback(self, msg: Options): """ Called when a new ROS Options message is coming in that should be sent to the Telegram conversation @@ -284,7 +288,7 @@ def chunks(l, n): # noqa: E741 for i in range(0, len(l), n): yield l[i : i + n] # noqa: E203 - self._telegram_app.bot.send_message( + await self._telegram_app.bot.send_message( self._telegram_chat_id, text=msg.question, reply_markup=ReplyKeyboardMarkup(