This post may contain affiliate links. Please read my disclaimer for more info.

Ever wanted to interact with your Home Assistant configuration when you’re not on the network? Want to run Home Assistant services and automations? The solution may be to create a telegram bot for Home Assistant.

A bot allows you to interact with your Home Assistant instance over a messaging platform. You can send custom commands to your bot to run automations, services and query different sensors in your setup. Today’s article goes through the installation and configuration steps to get a Telegram bot working in Home Assistant.

AppDaemon Installation

There are a couple different ways to create a telegram bot for Home Assistant. You can do it using purely YAML based Home Assistant automations and services. You could do it using something graphical like Node-Red. I chose to use AppDaemon for creating my bot. AppDaemon allows you to write Python-based applications that interact with Home Assistant. It extends your Home Assistant instance into the Python environment.

Why did I choose AppDaemon? As automations become more complex you may find yourself pulling your hair out while debugging complicated Home Assistant YAML files. AppDaemon lets you write those complex automations in Python making them easier to maintain and debug. Even if you haven’t written Python before you’ll be able to follow along and customize it for your needs. (Feel free to message me or comment too!)

Docker Image

I recommend using a docker image to get started using AppDaemon. You can use the official docker AppDaemon image, but that only works on x86 based architectures (so not a Raspberry Pi). Because I’m currently running my Home Assistant instance on a Raspberry Pi, I made a new docker image that works on the Raspberry Pi 3 with the latest version of AppDaemon. Check it out here.

I use docker compose to manage my containers for home automation. To add the AppDaemon image make sure you mount a volume for the configuration files and expose port 5050. Below you can see a snippet from my docker-compose.yml file, you can see the full one in my GitHub repository.

--- version: "3" services: homeassistant: image: homeassistant/raspberrypi3-homeassistant:0.76.2 volumes: - /home/pi/home-assistant-config/configuration:/config - /etc/localtime:/etc/localtime:ro network_mode: "host" ports: - "8123:8123" devices: - "/dev/ttyACM0:/dev/zwave:rmw" appdaemon: image: selfhostedhome/rpi-appdaemon:3.0.1 volumes: - /home/pi/home-assistant-config/appdaemon/conf:/conf - /etc/localtime:/etc/localtime:ro network_mode: "host" ports: - "5050:5050" environment: - HA_URL="http://localhost:8123" depends_on: - homeassistant 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 --- version : "3" services : homeassistant : image : homeassistant/raspberrypi3-homeassistant :0.76.2 volumes : - /home/pi/home-assistant-config/configuration :/config - /etc/localtime :/etc/localtime :ro network _ mode : "host" ports : - "8123:8123" devices : - "/dev/ttyACM0:/dev/zwave:rmw" appdaemon : image : selfhostedhome/rpi-appdaemon :3.0.1 volumes : - /home/pi/home-assistant-config/appdaemon/conf :/conf - /etc/localtime :/etc/localtime :ro network _ mode : "host" ports : - "5050:5050" environment : - HA _ URL= "http://localhost:8123" depends _ on : - homeassistant

Remember, only use my selfhostedhome/rpi-appdaemon image if you’re running on a Raspberry Pi. If you’re wondering how to start a docker-compose file on startup, check out my article on Starting Docker Compose using systemd on Debian.

Config Files

So we need to make some configuration files to get AppDaemon playing nicely with Home Assistant and setup our Telegram bot. In the previous section I mentioned creating a conf directory for AppDaemon. Create the following files in this layout within that conf directory:

. ├── appdaemon.yaml ├── apps │ ├── apps.yaml │ └── telegram_bot.py └── dashboards └── Hello.dash 1 2 3 4 5 6 7 . ├── appdaemon . yaml ├── apps │ ├── apps . yaml │ └── telegram_bot . py └── dashboards └── Hello . dash

Let’s go through each of these files. First off is appdaemon.yml.

log: logfile: STDOUT errorfile: STDERR appdaemon: threads: 10 api_port: 5000 plugins: HASS: type: hass ha_url: "http://localhost:8123" hadashboard: dash_url: "http://localhost:5050" 1 2 3 4 5 6 7 8 9 10 11 12 log : logfile : STDOUT errorfile : STDERR appdaemon : threads : 10 api _ port : 5000 plugins : HASS : type : hass ha _ url : "http ://localhost :8123" hadashboard : dash _ url : "http ://localhost :5050"

Change the ha_url if your Home Assistant instance is running on a different host or port. Because AppDaemon is running on the same machine as Home Assistant for me I can just use localhost .

if your Home Assistant instance is running on a different host or port. Because AppDaemon is running on the same machine as Home Assistant for me I can just use . The dash_url will be the address and port that the AppDaemon dashboard will show up on. We won’t be using this for our actual bot, but it is useful to check that AppDaemon is working correctly.

Next make the apps/apps.yaml file.

telegram_bot: module: telegram_bot class: TelegramBotEventListener garage: cover.garage_door 1 2 3 4 telegram _ bot : module : telegram_bot class : TelegramBotEventListener garage : cover.garage_door

The module here needs to match the name of the python file where the app will live. Ours will be in a file called telegram_bot.py so we match it here.

here needs to match the name of the python file where the app will live. Ours will be in a file called so we match it here. The class attribute is the class within the Python module that will be used for the app.

attribute is the class within the Python module that will be used for the app. My bot will interact with my garage, therefore I pass in my Home Assistant cover for my garage. The garage attribute will be an argument for my bot. There is some additional documentation here about this feature, but it basically makes it easy to access your Home Assistant sensors/covers/devices.

You can create the telegram_bot.py file as well but leave it blank for now. We’ll be creating it in the next section.

Finally, create the Hello.dash file. This adds a widget to our dashboard so that we can test that AppDaemon is working correctly.

title: Hello Panel widget_dimensions: [120, 120] widget_margins: [5, 5] columns: 8 label: widget_type: label text: Hello World layout: - label(2x2) 1 2 3 4 5 6 7 8 9 10 11 title : Hello Panel widget _ dimensions : [120 , 120 ] widget _ margins : [5 , 5 ] columns : 8 label : widget _ type : label text : Hello World layout : - label ( 2x2 )

Once all those files are created in your AppDaemon conf directory, restart the container. You should be able to access your AppDaemon dashboard on port 5050 of the machine running it. If you don’t see anything come up, check your docker logs for any errors. Feel free to comment or message me if something isn’t starting up correctly.

Home Assistant

There is nothing really special we need to do in Home Assistant for AppDaemon to work. What you do need to do though is create a Telegram Bot and register it with Home Assistant. Check out the Telegram section of my article on Home Assistant notification platforms to learn more about how to set it up.

Creating the Telegram Bot

Now that AppDaemon is up and running, it’s time to make our bot. For this article, I’m going to be making a bot that can tell us the status of our garage door and open/close it depending on the status. This is really handy if you don’t have your Home Assistant instance exposed on the internet and want to check on your garage door when away from home. If you want to create your own automated garage door check out my article about creating a DIY Smart Garage Door Opener.

The end goal will look something like this:

Initialization and Commands

Telegram considers any message that starts with “/” to be a command. Our telegram bot class checks the command sent and runs the appropriate function. All the following code should be in that telegram_bot.py file located in your apps directory. The below code initializes the class and sets up command handling:

import appdaemon.plugins.hass.hassapi as hass class TelegramBotEventListener(hass.Hass): """Event listener for Telegram bot events.""" def initialize(self): """Listen to Telegram Bot events of interest.""" self.listen_event(self.receive_telegram_command, 'telegram_command') self.listen_event(self.receive_telegram_callback, 'telegram_callback') def garage_command(self, user_id): state = self.get_state(self.args["garage"]) msg = "The garage is currently {}".format(state) if state == "closed": keyboard = [[("Open", "/open_garage"), ("Do Nothing", "/do_nothing")]] else: keyboard = [[("Close", "/close_garage"), ("Do Nothing", "/do_nothing")]] self.call_service( 'telegram_bot/send_message', target=user_id, message=msg, disable_notification=True, inline_keyboard=keyboard) def receive_telegram_command(self, event_id, payload_event, *args): assert event_id == 'telegram_command' user_id = payload_event['user_id'] command = payload_event['command'] if command == "/garage": self.garage_command(user_id) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import appdaemon . plugins . hass . hassapi as hass class TelegramBotEventListener ( hass . Hass ) : """Event listener for Telegram bot events.""" def initialize ( self ) : """Listen to Telegram Bot events of interest.""" self . listen_event ( self . receive_telegram_command , 'telegram_command' ) self . listen_event ( self . receive_telegram_callback , 'telegram_callback' ) def garage_command ( self , user_id ) : state = self . get_state ( self . args [ "garage" ] ) msg = "The garage is currently {}" . format ( state ) if state == "closed" : keyboard = [ [ ( "Open" , "/open_garage" ) , ( "Do Nothing" , "/do_nothing" ) ] ] else : keyboard = [ [ ( "Close" , "/close_garage" ) , ( "Do Nothing" , "/do_nothing" ) ] ] self . call_service ( 'telegram_bot/send_message' , target = user_id , message = msg , disable_notification = True , inline_keyboard = keyboard ) def receive_telegram_command ( self , event_id , payload_event , * args ) : assert event_id == 'telegram_command' user_id = payload_event [ 'user_id' ] command = payload_event [ 'command' ] if command == "/garage" : self . garage_command ( user_id )

Some interesting things to point out:

We need to subclass the hass.Hass class to interface with Home Assistant. After inheriting from that class we define a method called initalize to setup the class.

class to interface with Home Assistant. After inheriting from that class we define a method called to setup the class. Callbacks are added to process receiving a command (basically a message starting with ‘/’) or receiving a callback message.

The receive_telegram_command method determines the command that was sent and calls the appropriate function for the command. In this example, the bot just understands the /garage command.

method determines the command that was sent and calls the appropriate function for the command. In this example, the bot just understands the command. When the /garage command is sent the garage_command method is run. We can read the sate of the garage door using the get_state method and passing in the argument used in the app configuration.

command is sent the method is run. We can read the sate of the garage door using the method and passing in the argument used in the app configuration. When calling the telegram/send_message method sets the keyboard attribute to give the user an interface to call different commands. Based on the status of the garage door different options are shown to the user.

Processing Callbacks

After the bot processes the initial command from the user it displays different follow-up actions. To process those actions we can use the telegram_callback callback.

def receive_telegram_callback(self, event_id, payload_event, *args): assert event_id == 'telegram_callback' data_callback = payload_event['data'] callback_id = payload_event['id'] if data_callback == '/open_garage': self.call_service( 'telegram_bot/answer_callback_query', message='Opening the garage!', callback_query_id=callback_id) self.call_service( 'cover/open_cover', entity_id=self.args["garage"]) elif data_callback == '/close_garage': self.call_service( 'telegram_bot/answer_callback_query', message='Closing the garage!', callback_query_id=callback_id) self.call_service( 'cover/close_cover', entity_id=self.args["garage"]) elif data_callback == '/do_nothing': self.call_service( 'telegram_bot/answer_callback_query', message='OK, you said no!', callback_query_id=callback_id) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def receive_telegram_callback ( self , event_id , payload_event , * args ) : assert event_id == 'telegram_callback' data_callback = payload_event [ 'data' ] callback_id = payload_event [ 'id' ] if data_callback == '/open_garage' : self . call_service ( 'telegram_bot/answer_callback_query' , message = 'Opening the garage!' , callback_query_id = callback_id ) self . call_service ( 'cover/open_cover' , entity_id = self . args [ "garage" ] ) elif data_callback == '/close_garage' : self . call_service ( 'telegram_bot/answer_callback_query' , message = 'Closing the garage!' , callback_query_id = callback_id ) self . call_service ( 'cover/close_cover' , entity_id = self . args [ "garage" ] ) elif data_callback == '/do_nothing' : self . call_service ( 'telegram_bot/answer_callback_query' , message = 'OK, you said no!' , callback_query_id = callback_id )

The data_callback variable contains the option the user chose from the keyboard, we can check that variable to decide what to do.

variable contains the option the user chose from the keyboard, we can check that variable to decide what to do. If the user decides to open or close the garage we use the telegram_bot/answer_callback_query to respond to the user and the cover services to open or close the services.

to respond to the user and the services to open or close the services. The arguments to the call_service method depend on the service being called. When interacting with the cover we pass in the entity_id that the user passed in during app configuration.

method depend on the service being called. When interacting with the cover we pass in the that the user passed in during app configuration. For the do_nothing callback we don’t interact with the garage door.

Summary

You should now have a working telegram bot for Home Assistant! Telegram with AppDaemon and Home Assistant allow you to easily create a very flexible bot to interact with your home. What are some other commands to add to a bot? Using a different bot platform? Let me know in the comments!