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
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.
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