Ovládání Raspberry Pi z Telegramu

Znáte Telegram Messenger? Je to celkem pěkný komunikátor, ale hlavně má úžasné API, které umožňuje jednoduše vytvořit robota, který bude reagovat na vaše rozkazy 😀.

Dlouho jsem přemýšlel o tom, jak ovládat Raspberry, které mám doma. SSH mi přijde moc „nízkoúrovňové“ a navíc bych potřeboval veřejnou IP adresu (pokud nejsem doma). Chtěl jsem něco pohodlnějšího. A pak jsem objevil video, kde někdo pomocí Telegramu a Raspberry Pi rozsvěcel a zhasínal žárovky 😀.

V tu chvíli bylo jasné, že to musím prozkoumat! Jednoduché… programovatelné… co si povolím, to to bude umět.

Co je to Telegram Messenger?

Telegram je cloudová služba, která slouží k posílání zpráv mezi uživateli. Kromě textových zpráv je možné posílat i fotky, videa a jiné soubory. Je podobná jako WhatsApp nebo třeba ICQ (pamatujete si na ještě na něj 😁).

K identifikaci uživatelů používá stejně jako WhatsApp telefonní číslo. Klientské aplikace jsou vytvořeny pro mobilní telefony s operačním systémem Android, iPhone a WindowsPhone. Také pro všechny počítače s operačními systémy Windows, macOS i Linux.

Telegram Bot

Obrázky se nestihly načíst.
Prosím opakujte akci zachvilku.
Telegram BotFather

Kromě běžných uživatelů, jako já nebo třeba vy, se na Telegramu vyskytují i speciální uživatelé tzv. boti. Každý uživatel si může svého bota vytvořit. Jakmile máte bota vytvořeného, dostanete k němu token pro API, přes které ho můžete ovládat.

Co můžete přes Telegram API dělat?

Můžete přijímat zprávy poslané botovi různými uživateli a ve svém programu na ně reagovat. Můžete samozřejmě posílat i zprávy zpět. Jen si dávejte pozor na „spamování“.

Jak vytvořit Telegram Bota?

Je to jednoduché. Nejdříve napíšete zprávu uživateli @BotFather. Je to kmotr všech robotů a provede vás s jejich vytvářením.

Příkazy k ovládání BotFathera:
/newbot - create a new bot
/mybots - edit your bots [beta]
Edit Bots
/setname - change a bot's name
/setdescription - change bot description
/setabouttext - change bot about info
/setuserpic - change bot profile photo
/setcommands - change the list of commands
/deletebot - delete a bot
Bot Settings
/token - generate authorization token
/revoke - revoke bot access token
/setinline - toggle inline mode (https://core.telegram.org/bots/inline)
/setinlinegeo - toggle inline location requests (https://core.telegram.org/bots/inline#location-based-results)
/setinlinefeedback - change inline feedback (https://core.telegram.org/bots/inline#collecting-feedback) settings
/setjoingroups - can your bot be added to groups?
/setprivacy - toggle privacy mode (https://core.telegram.org/bots#privacy-mode) in groups

Takže jdeme na to… Nejdříve pošleme BotFatherovi zprávu

/newbot


Zeptá se na jméno. Nějaké si vymyslete 😉 a pošlete mu ho. Pak se zeptá na uživatelské jméno (username). Zde je podmínkou, že bude končit na ‚bot‘ (např. TetrisBot nebo tetris_bot, tak se krásně pozná, že se bavíte s robotem). Opět si nějaké vymyslete a pošlete ho BotFatherovi.

A je to… BotFather vám pogratuluje a nezapomene poslat API token.

Obrázky se nestihly načíst.
Prosím opakujte akci zachvilku.
Kompletní přepis vytvoření nového bota


Program pro ovládání Raspberry Pi

Robota máme vytvořeného, teď ještě zbývá vytvořit druhou část. Tou je jednoduchý prográmek v jazyce Python, který bude reagovat na zprávy, které našemu robotovi někdo pošle.

Využijeme knihovnu Telepot abychom nemuseli znovu „vynalézat kolo“ a přicházet na to, jak to celé má fungovat knihovnu nainstalujeme příkazem

pip3 install telepot
pip3 install telepot --upgrade


Pip3 je instalátor python knihoven pro Python3 a pokud ho náhodou nemáte, tak se instaluje příkazem

sudo apt-get install python3-pip


Když máme knihovnu telepot nainstalovánu, můžeme se pustit do prvního programování.
Abychom ji vyzkoušeli tak si nejprve napíšeme takový malý Hello World.

import telepot
bot = telepot.Bot('API TOKEN')
bot.getMe()
response = bot.getUpdates()
print(response)


Program uložíme do souboru hello_world.py a spustíme příkazem

python3 hello_world.py


Vypíše se nám následující výpis

{
  'first_name': 'Your Bot',
  'username': 'YourBot',
  'id': 123456789
}


A pokud jsem již botovi poslali nějakou zprávu (třeba jen /start) bude výpis obsahovat i tuto zprávu.

[
  {
    'message': {
      'chat': {
        'first_name': 'Karel',
        'id': 1234567890,
        'type': 'private'
      },
      'date': 1465283242,
      'from': {
        'first_name': 'Karel',
        'id': 1234567890
      },
      'message_id': 10772,
      'text': 'Hello'
    },
    'update_id': 100000000
  }
]


Pokud všechno funguje a zprávy se vypsaly, tak máme vyhráno a můžeme se pustit do vytváření „dálkového ovládání“.

import subprocess
import sys
import time
import telepot
from telepot.loop import MessageLoop

# add --on-boot param if script is started during raspberry pi booting
# script is added to /etc/rc.local
onboot = False
for arg in sys.argv:
  if arg == '--on-boot':
    onboot = True

if onboot:
  # sleeping and waiting... everything must be ok
  time.sleep(60)

userId = 1234567890 # chat.id from message, only we can control our Raspberry Pi
exited = False
picturePath = '/path/to/photo.jpg'

bot = telepot.Bot('API TOKEN')

print(bot.getMe())

bot.sendMessage(userId, 'Hi... I\'m back...')

def handleMsg(msg):
  global userId
  global exited

  print(msg)

  chatId = msg['chat']['id']

  if (not userId) or (chatId == userId):
    output = ''
    if 'text' in msg:
      command = msg['text']
      if command == '/help':
        output = output + '/help - prints this help\n'
        output = output + '/photo - sends actual photo from camera\n'
        output = output + '/uptime - shows uptime from RPi\n'
        output = output + '/df - shows free disk space\n'
        output = output + '/die - stops bot\n'
        output = output + '/reboot - reboot RPi\n'
      elif command == '/photo':
        camera = None
        try:
          camera = picamera.PiCamera()
        except:
          output = 'Camera not connected!'
        if camera is not None:
          try:
            time.sleep(5)
            camera.resolution = '1080p'
            camera.hflip = True
            camera.vflip = True
            camera.capture(picturePath)
            bot.sendPhoto(chatId, open(picturePath, 'rb'))
          finally:
            camera.close()
      elif command == '/uptime':
        output = subprocess.check_output('uptime')
      elif command == '/df':
        output = subprocess.check_output(['df', '-h'])
      elif command == '/die':
        bot.sendMessage(chatId, 'Bye bye... 😞')
        exited = True
      elif command == '/reboot':
        bot.sendMessage(chatId, 'Bye bye... I\'ll be back soon...')
        subprocess.Popen(['sudo','/sbin/reboot'])
      else:
        bot.sendMessage(chatId, 'Received message: ' + msg['text'])
    else:
      bot.sendMessage(chatId, 'Unknown message 😞')

    if output != '':
      bot.sendMessage(chatId, output)
  else :
    bot.sendMessage(chatId, 'I can\'t talk to you... Sorry')
  
bot.message_loop(handleMsg)


# Keep program running
try:
  while 1:
    if exited:
      sys.exit(0)
    time.sleep(10)
except KeyboardInterrupt:
  bot.sendMessage(userId, 'I\'m going offline')
  sys.exit(0)


V programu je nutné pro správnou funkčnost vyplnit userId a doplnit API TOKEN. Program si uložíme do souboru telegram.py. Spuští se příkazem

python3 telegram.py


Prográmek se napojí na bota a bude čekat na zprávu, kterou mu někdo pošle.

Pokud jste vyplnili userId, tak bude zprávy příjmat jen z konverzace s vámi. Všem ostatním bude odpovídat „I can't talk to you... Sorry“.

Příkazy, které něco dělají:
/help – vypíše nápovědu (seznam dostupných příkazů)
/uptime – vypíše informace z příkazu uptime (jak dlouho už Raspberry Pi běží, kolik je přihlášených uživatelů a jaký je load)
/df – vypíše jak jsou obsazené jednotlivé disky
/die – vypne program, bot přestane reagovat na zprávy
/reboot – rebootuje Raspberry Pi

Samozřejmě si každý může své příkazy doplnit dle potřeby 😉. Jak je vidět na ukázce, není to nic těžkého.

Spuštění robota po startu Raspberry Pi
Jednoduše jeho spuštění doplníme na konec souboru /etc/rc.local před závěrečný exit 0.

sudo vim /etc/rc.local


doplnit řádku

sudo python3 /home/pi/telegram.py --on-boot


Závěr

Hurááá... konečně mám jednoduché ovládání pro svoje Raspberry Pi. Můžu ho dle potřeby celkem okamžitě rebootovat a ani nemusím mít doma veřejnou IP adresu, kterou bych potřeboval pro SSH.

Samozřejmě je spousta dalších věcí, které může robot dělat.

Kdyby vás napadl nějaký další zajímavý příkaz, napište mi do komentářů 😉.

Zdroje:
https://core.telegram.org/bots
https://core.telegram.org/bots/api
https://telepot.readthedocs.io/en/latest/

Aktualizace 15. 5. 2019 - Ukončení rozvoje knihovny telepot

Dneska jsem si všimnul, že autor knihovny telepot ukončil její další rozvoj (zdroj).
Dokud to funguje, tak mi to celkem nevadí. A až to fungovat přestane, tak telepot bude nutné nahradit za nějakou jinou knihovnu. Např. python-telegram-bot 😉.

Pro jiné jazyky jsou knihony dostupné také https://core.telegram.org/bots/samples.

Aktualizace 30. 8. 2019 - Doplnění možnosti poslat fotku z kamery a GitHub repozitář

Do scriptu jsem doplnil možnost si nechat poslat aktuální fotku z kamery.

Také jsem si všimnul, že tady nemám odkaz na GitHub repozitář, ze kterého si můžete celý program jednoduše stáhnout 😉. Takže tady je odkaz https://github.com/Ch4rlieB/raspberry-pi-telegram.

Problémy a jejich řešení

ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:720)

Traceback (most recent call last):
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py", line 603, in urlopen
    chunked=chunked)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py", line 344, in _make_request
    self._validate_conn(conn)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py", line 843, in _validate_conn
    conn.connect()
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connection.py", line 350, in connect
    ssl_context=context)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/util/ssl_.py", line 355, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
  File "/usr/lib/python3.5/ssl.py", line 385, in wrap_socket
    _context=self)
    File "/usr/lib/python3.5/ssl.py", line 760, in __init__
    self.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 996, in do_handshake
    self._sslobj.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 641, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:720)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "controller.py", line 33, in <module>
    print(bot.getMe())
  File "/home/pi/.local/lib/python3.5/site-packages/telepot/__init__.py", line 503, in getMe
    return self._api_request('getMe')
  File "/home/pi/.local/lib/python3.5/site-packages/telepot/__init__.py", line 491, in _api_request
    return api.request((self._token, method, params, files), **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/telepot/api.py", line 154, in request
    r = fn(*args, **kwargs) # `fn` must be thread-safe
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/request.py", line 150, in request_encode_body
    return self.urlopen(method, url, **extra_kw)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/poolmanager.py", line 326, in urlopen
    response = conn.urlopen(method, u.request_uri, **kw)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py", line 670, in urlopen
    **response_kw)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py", line 670, in urlopen
    **response_kw)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py", line 670, in urlopen
    **response_kw)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py", line 641, in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/util/retry.py", line 399, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='api.telegram.org', port=443): Max retries exceeded with url: /bot123457897:abcdefghijklmnopqrst/getMe (Caused by SSLError(SSLError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:720)'),))

Řešení je popsáno v Issue #463 (https://github.com/nickoala/telepot/issues/463)

pip3 install urllib3==1.24.1

Telegram, Python

- (30. 8. 2019)

Líbil se vám článek?

Nasdílejte ho svým přátelům na sociálních sítích.

Pinterest

Názory, připomínky a jiné komentáře

Telegram v češtině? Už se chystá...
t.me/setlanguage/cs-beta

Keď Vám začnú vyvolávať rôzni "obchodníci" so super ponukami, aspoň budete vedieť, odkiaľ získali telefónne číslo...

Nemyslím si, že by v tomhle byl problém. Spíš bych to viděl na WhatsApp. Ale každopádně ti obvolávači vždycky ví víc než jen telefonní číslo ;-)

ja mam takto vymyslene posielanie fotky z kamery pripojenej k pc

Vito: Díky za nápad. Poslat si aktuální fotku z kamery není problém. Asi to doplním.

cauko, myslis ze mi to bude fungovat aj na pi 4b? nemam to este ako vyskusat kedze som ho prave len objednal :)

Patrik: Ano bude. A pokud ne, tak to určitě nebude tím, že je to RPI4 ale spíš tím, že knihovna, kterou sem použil se již nevyvíjí a tak nemusí být kompatibilní s nejnovějším Pythonem respektive dalšími knihovnami.

To by ale mělo být možné vyřešit, stejně jako problém s urllib3 ;-)

Dobrý den,
nedávno jsem použil váš příspěvek Ovládání Raspberry Pi pomocí Telegramu. Musím se přiznat, že nejsem zdatný linuxák a rovněž ani programátor.
Tento způsob komunikace s RPi se mi hodně líbí, je fakt, že něco podobného jsem hledal. Vše funguje tak jak má. Trochu jsem to poupravil. Vložil jsem do skriptu pár řáků, které reagují na změnu textového souboru. Když do tohoto souboru napíši v podstatě cokoliv, telegram to odešle. Využívám to jako takovou notifikaci od ostatních programů. Vše funguje . Až na malý problém a tím je občasný výpadek připojení do sítě. Pokud se bude tento skript snažit odeslat cokoliv při výpadku, skončí to chybou a ukončením skriptu.
Nevěděl byste co s tím?
Předem děkuji za jakoukoliv odpověď

Vložit nový komentář

Jsem programátor, horolezec a tak trochu FlexiBee fanatik :-).
Na CharlieBlogu sepisuji své nápady a poznatky už od roku 2006.

Powered by CharlieBlog Engine v2.3 - Login