Control OpenHAB2 with Telegram and simple python script

I used to use Telegram action mainly to receive some alerts or notifications from OpenHAB, but as I have to establish a SSH tunnel to connect home and check system status I found it useful to be able to trigger some rules directly from Telegram. This is what I have:

TelegramMenu.py (which I run at startup with “python TelegramMenu.py”). It runs in the back

# Based on https://github.com/eternnoir/pyTelegramBotAPI 
# and the work of my good friend Tomás (TPG)
# Other useful link: https://www.mindk.com/blog/how-to-develop-a-chat-bot/
import telebot
import os
from subprocess import call

bot = telebot.TeleBot("xxxxxxx:yyyyyyyyyyyyy") #<<<<<Insert Telegram API Key here
logFilename = open('/var/log/openhab2/telegrammenu.log','a')

# Handles /menu command
# It is required to register the command with BotFather issuing a /setcommands
# Icons codification can be found at https://apps.timwhitlock.info/emoji/tables/unicode
@bot.message_handler(commands=['menu'])
def send_menu(message):
    if message.chat.type == "private":
        keyboard = telebot.types.InlineKeyboardMarkup()
        keyboard.row(
                    telebot.types.InlineKeyboardButton('\xF0\x9F\x92\xA5 Motion', callback_data='MOTIONSTATUS'),
                    telebot.types.InlineKeyboardButton('\xF0\x9F\x9A\xA8 Arm/Disarm', callback_data='ARMALARM')
                    )
        keyboard.row(
                    telebot.types.InlineKeyboardButton('\xF0\x9F\x92\xB9 Temps', callback_data='TEMPS'),
                    telebot.types.InlineKeyboardButton('\xE2\x9C\xB4 Status', callback_data='STATUS')
                    )
        bot.send_message(<chatID>,"Main Menu...",reply_markup=keyboard)

#callback del keyboard de markup
@bot.callback_query_handler(func=lambda call: True)
def iq_callback(query):
    # Sends Telegram received command through MQTT to OH2
    os.system("/usr/bin/mosquitto_pub -t TelegramMenuCommand -m " + query.data) 

def main_loop():
    while 1:
        try:
            print >>logFilename, "Polling..." #python 2
            #print("Polling...", file=logFilename) #python 3
            bot.polling(none_stop=True, timeout=60)
            while 1:
                time.sleep(3)
        except Exception as er:
            print >>logFilename, "Unexpected exception: "+str(er) #python 2
            #print("Unexpected exception: "+str(er), file=logFilename) #python 3


if __name__ == '__main__':
    try:
        main_loop()
    except KeyboardInterrupt:
        print >> sys.stderr, '\nExiting by user request.\n'
        sys.exit(0)

Now there is an Item that will receive the TelegramCommand:

String TelegramMenuCommandString { mqtt="<[broker:TelegramMenuCommand:state:default]" }

And finally a rule that execute the commands received:

rule "Telegram Menu Actions" 
when
    Item TelegramMenuCommandString received update
then
    //Emoticons from https://www.charbase.com
    //logInfo("TelegramMenu", "TelegramMenuCommandString received command " + receivedCommand)

    if (TelegramMenuCommandString.state == NULL) {
        return
    } else if (TelegramMenuCommandString.state == 'ARMALARM') {
        if (HomeAlarmEnabled.state == ON) {
            HomeAlarmEnabled.sendCommand(OFF)
            HomeAlarmFired.sendCommand(OFF)
        } else {
            HomeAlarmEnabled.sendCommand(ON)
        }
    //} else if (TelegramMenuCommandString.state == 'MOTIONSTATUS') { //Served from HomeAlarm.rules
    } else if (TelegramMenuCommandString.state == 'TEMPS') {
        .... Code preparing status response here ....
        sendTelegram("TelegramBot", TemperaturesStr)
    } else if (TelegramMenuCommandString.state == 'STATUS') {
        .... Code preparing status response here ....
        sendTelegram("TelegramBot",  StatusStr)
    }     

    TelegramMenuCommandString.postUpdate(NULL)
end

That is all I need. If I issue a /menu command to the Telegram chat with my bot the python script answers with a buttons keyboard, containing 2 rows of 2 buttons (can contain more lines or rows at will) and whenever I press one of the buttons an action gets executed within my OpenHAB installation. Simple and very useful for me

01
26

12 Likes

nice, i’m planning to do something similar, although over slack, based on habot. it’s going to be a pretty awesome addition to OpenHAB:

5 Likes

WOW this is pretty impressive!!

Interesting approach @Dixon. Really like it.
When looking at the Telegram part, I see that your chatID is hardcoded. Means that only one “person” can talk to the bot. Would be cool, if reply_to() can be used to that multiple persons can talk to OH via Telgram.
Else I was testing with a groupChat ID, but seems wired to send a message to a bot and receive reply in the group. I’m not sure if a bot can grab commands from a chat group.

Actually I can’t. I was concern of anybody else interacting with my OpenHAB and that’s why chatid is hardcoded, but a good improvement would be a “allowed chat or group ids list”, as I have a private chat with my bot for more freaky alerts and tests and a group chat with the bot and my wife where we receive nofitifations on when the main door is open an presence changes and would like to be able to use the telegram menu from here too. I have it in the “pending” list
For group chat ID is a negative number

Great idea, having issues with the TeleBot install.

AttributeError: 'TeleBot' object has no attribute 'message_handler'

Found this, but my python knowledge lacks

Did you see this?

pip3 uninstall telebot

then

pip3 uninstall PyTelegramBotAPI

and then

pip3 install PyTelegramBotAPI==2.2.3

and it works now!

Depending on python version you may need to use pip instead of pip3.

Yes I have, thank you.
Tried a few variations with no success as yet.

script initally working , removed conflicting telegrambot api , used only pip

Working through what can be passed back to the script with a couple issues

the telegramremote.rules in vs code flags up a lot of errors

initially with a load of == NULL

That’s why first thing I do is

    if (TelegramMenuCommandString.state == NULL) {
        return

Yes, I have directly copyed your whole script, and added the necessary Items for a test run, VS code flags up

        var DecimalType AvgTemp == NULL
        var DecimalType MaxTemp == NULL
        var DecimalType MinTemp == NULL

 "Type mismatch: type void is not applicable at this location"

I guessed ?incorrectly it was a NULL issue with openhab 2.3.0 ?

There is a difference between an Item state and a var, where the former will be NULL until the Item is initialized and has a valid value, the latter will be null.

Please be aware of the difference between = and ==

var DecimalType AvgTemp == NULL is wrong, as the first part stands for “declare a var of Type DecimalType with the name AvgTemp” and the last part stands for “compare with NULL”
Further, you can’t define a decimal type var and assign NULL to it, as NULL is a special state which is not an element of DecimalType. You could assign null, though, but you have to use =

var DecimalType AvgTemp = null would be correct.

1 Like

Makes sense, but var DecimalType AvgTemp = NULL is working for me :thinking:

A friend of mine has built a commercial chatbot tech for banks and other sectors. Maybe I will mention this to him next time I meet him. He could’ve further pointers or I could get him convinced to contribute whatever he can. :wink:

Thanks. Script works great. The only thing I can’t figure out is how to run it at startup.

I added

/usr/bin/python /etc/openhab2/scripts/TelegramMenu.py &

to my rc.local but Telegram did not respond to the /menu command.
Next, I used ExecuteCommand in an OpenHab rule to trigger the script:

var String startmenu = executeCommandLine("sudo /usr/bin/python /etc/openhab2/scripts/TelegramMenu.py &")

Didn’t work either. Do you have any hint what might be wrong?

you can start the script as a service like mosquitto

Thanks for your hint. Tried it by creating a file “TelegramMenu.service” with

[Unit]
Description=Start TelegramMenu
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/python /etc/openhab2/scripts/TelegramMenu.py
Restart=on-abort

[Install]
WantedBy=multi-user.target

and received the following error after starting the service:

[16:45:13] openhabian@openhabnew:/etc/systemd/system$ sudo systemctl status telegramMenu.service
● telegramMenu.service - Start TelegramMenu
   Loaded: loaded (/etc/systemd/system/telegramMenu.service; enabled; vendor preset: enabled)
   Active: failed (Result: exit-code) since Sun 2018-08-26 16:45:13 CEST; 6s ago
  Process: 19196 ExecStart=/usr/bin/python /etc/openhab2/scripts/TelegramMenu.py (code=exited, status=1/FAILURE)
 Main PID: 19196 (code=exited, status=1/FAILURE)

Aug 26 16:45:13 openhabnew systemd[1]: Started Start TelegramMenu.
Aug 26 16:45:13 openhabnew python[19196]: Traceback (most recent call last):
Aug 26 16:45:13 openhabnew python[19196]:   File "/etc/openhab2/scripts/TelegramMenu.py", line 4, in <module>
Aug 26 16:45:13 openhabnew python[19196]:     import telebot
Aug 26 16:45:13 openhabnew python[19196]: ImportError: No module named telebot
Aug 26 16:45:13 openhabnew systemd[1]: telegramMenu.service: Main process exited, code=exited, status=1/FAILURE
Aug 26 16:45:13 openhabnew systemd[1]: telegramMenu.service: Unit entered failed state.
Aug 26 16:45:13 openhabnew systemd[1]: telegramMenu.service: Failed with result 'exit-code'.

The script works when I start it from the command line. What is wrong?

I’m guessing permissions/user/group, I will try this method on my setup. I guess you have added your own take on what the bot returns as information ?

VS code flags up many errors with the .rules, but it does work as the author states

gTemperature.members.forEach[ item | 
            if (item.state != NULL &&
                item.state != UNDEF &&
               // item.name != "Temperature" &&
               // item.name != "RFTemp" &&
                item.name != "FF_Bathroom_Extractor_Temperature") 
                {
                TempCount += 1
                if (AvgTemp = null) {AvgTemp = item.state} else {AvgTemp += item.state}
                if (MaxTemp = null) {MaxTemp = item.state; MaxTempSensorName = item.label} else if (MaxTemp < item.state) {MaxTemp = item.state; MaxTempSensorName = item.label}
                if (MinTemp = null) {MinTemp = item.state; MinTempSensorName = item.label} else if (MinTemp > item.state) {MinTemp = item.state; MinTempSensorName = item.label}
            }
        ]
Cannot refer to the non-final variable TempCount inside a lambda expression

Type mismatch: cannot convert from BigDecimal to Number

Type mismatch: cannot convert from DecimalType to boolean

Type mismatch: cannot convert from State to DecimalType

Type mismatch: cannot convert from State to Number

is VS code wrong or is its prefered method something different

I tried to launch it like mosquito too with the same issue than you, It didn’t work. So I have to launch it manually after reboot. This is one of the pending things to improve, but I haven’t had time to debug it