Telegrambot на СИ для связи с Arduino
![Telegrambot и Arduino Telegrambot и Arduino](http://istarik.ru/uploads/images/00/00/01/2017/09/05/0dbf3a.png)
Статья рассчитана на людей уже знакомых с
Здравствуйте.
Этого бота я писал для управления своим "
Он написан для Linux и работает через Webhook (то есть выступает в роли сервера получающего уведомления от Telegram автоматически), поэтому понадобится белый ip.
Коротко о его работе: вы пишите боту команду, он переправляет её на сервер, сервер оправляет команду ардуине, ардуина выполняет команду и возвращает статус серверу, и наконец сервер отправляет статус боту.
Теперь обо всём по порядку…
Предполагается что приложение «Telegram» уже установлено.
Перейдите по ссылке @BotFather где будет предложено открыть её в приложении…
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/9abfea.png)
Согласитесь с предложением.
В открывшейся программе отправьте «отцу всех ботов» сообщение /newbot
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/db0fb4.png)
Будет предложено придумать имя боту, например — myardubot.
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/ed8aab.png)
Следом нужно придумать username бота, который должен заканчиваться словом — bot, пусть будет Myardubot.
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/597a23.png)
Если всё прошло успешно, Вас поздравят с созданием нового бота и выдадут токен — 337394654:AAHCz4xEO_Gb0XNOchcvW4EuPoXsMaa32Tc. У Вас он естественно будет другой.
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/389d54.png)
Токен — это пароль-идентификатор Вашего бота, его должны знать только Вы и никому его не показывать! В дальнейшем мы будем использовать его в запросах к нашему серверу.
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/945c5a.jpg)
Теперь в поиске найдите своего бота, кликните по нему и нажмите START…
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/e3be29.png)
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/5329cc.png)
Ну вот, Ваш бот запущен и готов отправлять и принимать сообщения через наш сервер. Однако перед тем как запустить сервер нужно создать самоподписной сертификат (работа будет вестись по защищённому протоколу — https) и установить Webhook.
Откройте терминал и установите необходимые библиотеки:
sudo apt install openssl libssl-dev
Создайте ssl сертификат:
openssl req -newkey rsa:2048 -sha256 -nodes -keyout private.key -x509 -days 365 -out public.pem
В процессе Вам будут заданы вопросы, на которые можно отвечать нажатием Enter кроме пункта — Common Name (e.g. server FQDN or YOUR name) []:, здесь нужно ввести Ваш внешний ip.
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/472f7b.png)
В домашней папке появятся два файла — private.key и public.pem.
Упакуем оба файла в один — cert.pem:
cat private.key public.pem > cert.pem
Файл cert.pem будет использоваться сервером, а public.pem понадобиться для установки Webhooka.
Устанавливаем Webhook:
curl -F "url=https://56.145.151.143:443/337394654:AAHCz4xEO_Gb0XNOchcvW4EuPoXsMaa32Tc" -F "certificate=@public.pem" https://api.telegram.org/bot337394654:AAHCz4xEO_Gb0XNOchcvW4EuPoXsMaa32Tc/setWebhook
Здесь нужно указать Ваш внешний ip и порт (443 или 8443), а после слеша Ваш токен (так рекомендует API), он будет служить признаком правильного запроса к нашему серверу. То есть, Telegram при обращении к нашему серверу будет посылать токен, а сервер проверять его наличие. Если сервер не обнаружит токена, то отбросит соединение.
Не забудьте поменять токен во второй части команды, и обратите внимание на то, что перед токеном есть слово bot.
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/deecb8.png)
Webhook установлен.
Всё что касается Telegram мы сделали, теперь займёмся ардуиной...
Как уже писалось выше, ардуина будет принимать наши команды, выполнять действие и отправлять обратно отчёт о сделанном.
Наглядный код для ардуины
char reciv_buf[16] = {0,};
void setup()
{
Serial.begin(57600);
pinMode(13, OUTPUT);
}
void loop()
{
int s = 0;
memset(reciv_buf, 0, 16);
while(Serial.available())
{
delay(1);
reciv_buf[s] = Serial.read();
if(reciv_buf[s] == '\n' || s > 14)
{
reciv_buf[s] = 0;
if(strstr(reciv_buf, "vkld13") != NULL)
{
digitalWrite(13, HIGH);
Serial.print("OK:vkld13");
Serial.print('\n');
}
if(strstr(reciv_buf, "otkld13") != NULL)
{
digitalWrite(13, LOW);
Serial.print("OK:otkld13");
Serial.print('\n');
}
memset(reciv_buf, 0, 16);
s = 0;
}
s++;
} // END while
} // END LOOP
Команды придумываете сами, вписываете их туда где — «vkld13» и т.д., а потом отправляете их боту.
Так же можно послать боту букву t, на что он ответит словом TEST.
Ну и наконец перейдём к серверу…
Скачайте сервер (telebotstd), положите его в папку вместе с ключами и там же создайте конфигурационный файл TelebotstD.conf со следующим содержимым:
port=443
token=337394654:AAHCz4xEO_Gb0XNOchcvW4EuPoXsMaa32Tc
mkpatch=/dev/ttyUSB0
baudrate=57600
В конце строк не должно быть пробелов.
После знаков «равно» впишите входящий порт сервера (тот что указывали при создании webhooka), свой токен, путь к ардуине и baudrate.
Подключите ардуину и дайте ей все права:
sudo chmod 777 /dev/ttyUSB0
Сделайте сервер исполняемым:
sudo chmod +x ./telebotstd
И запускайте его:
./telebotstd
Вы увидите что сервер прочитал конфиг и ждёт соединения:
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/fe9e61.png)
Теперь отправьте боту букву t и в ответ получите слово TEST:
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/db9e49.png)
Сервер напишет id отправителя сообщения (chat_id:274526150) и полученый текст (Text:t), а после «SendMessage:» будет написан заголовок запроса к Телеграму с токеном бота, id получателя и отправленное сообщение ({«chat_id»:274526150,«text»:«TEST»}).
То есть, Вы со своего телефона отправили боту сообщение с буквой t, бот отправил её Вашему серверу, сервер в ответ на это отправил сообщение с текстом «TEST» обратно боту, а бот передал его Вам на телефон.
Через 30 сек. бездействия, Телеграм разорвёт соединение (Disconnection:0).
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/1bb001.png)
Можно послать какую-нибудь нелепицу…
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/08497a.png)
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/f68c59.png)
Сервер отправит это сообщение (как и любое другое за исключением t) ардуине (to_Ardu:echo 'bla-bla-bla' > /dev/ttyUSB0), но она никак не отреагирует, так как выполняет только записанные в неё команды.
Теперь пошлите vkld13. Загорится D13 и Вам придёт ответ — OK:vkld13
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/1de832.png)
Сервер показал что отправил ардуине — to_Ardu:echo 'vkld13' > /dev/ttyUSB0, и что получил от неё в ответ — SM_from_Ardu: OK:vkld13 отправив это по назначению.
![](http://istarik.ru/uploads/images/00/00/01/2017/09/05/79c3eb.png)
Команда ограничена 15-ю символами.
Ошибки пишутся в файл ErrTelebotstD.log.
По серверу вроде бы сказать больше нечего. Далее можно добавлять в скетч ардуины свои алгоритмы и команды так же, как это сделано в примере.
Исходник сервера
#include <stdio.h>
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <unistd.h>
#include <openssl/err.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <resolv.h>
#include <netdb.h>
#include <time.h>
#include <sys/wait.h>
#include <termios.h>
#include <fcntl.h>
#define BREADSIZE 2048
#define AREADSIZE 128
int fd;
int port = 0;
char token[64] = {0,};
char mkpatch[32] = {0,};
unsigned long int baudrate = 0;
char bRead[AREADSIZE] = {0,};
void error_log(char *my_error)
{
time_t t;
time(&t);
FILE *f;
f = fopen("ErrTelebotstD.log", "a");
if(f == NULL)
{
printf("Error open ErrTelebotstD.log.\n");
exit(0);
}
fprintf(f, "%s", ctime( &t));
fprintf(f, "Error: %s\n\n", my_error);
printf("Error: %s Write to ErrTelebotstD.log.\n", my_error);
fclose(f);
exit(0);
}
void read_conf()
{
FILE *mf;
char str[64] = {0,};
char *restr;
mf = fopen ("TelebotstD.conf","r");
if(mf == NULL) error_log("mf.");
printf ("Open config file.\n");
while(1)
{
restr = fgets(str, sizeof(str), mf);
if(restr == NULL)
{
if(feof(mf) != 0) break;
else error_log("read from config file.");
}
if(strstr(str,"port=") != NULL) { port = atoi(strstr(str, "port=") + 5); printf("Port:%d\n", port); }
char *p;
if((p = strstr(str,"token=")) != NULL)
{
int index = p - str;
int i = 0;
int ot = index + 6;
for(; i <= 62; i++)
{
token[i] = str[ot];
ot++;
if(token[i] == '\n')
{
token[i] = '\0';
printf("Token:%s\n", token);
break;
}
}
}
char *mkp;
if((mkp = strstr(str,"mkpatch=")) != NULL)
{
int index = mkp - str;
int i = 0;
int ot = index + 8;
for(; i <= 14; i++)
{
mkpatch[i] = str[ot];
ot++;
if(mkpatch[i] == '\n')
{
mkpatch[i] = '\0';
printf("Mkpatch:%s\n", mkpatch);
break;
}
}
}
char *sp;
if((sp = strstr(str,"baudrate=")) != NULL)
{
char mkspeed[8] = {0,};
int index = sp - str;
int i = 0;
int ot = index + 9;
for(; i <= 7; i++)
{
mkspeed[i] = str[ot];
ot++;
if(mkspeed[i] == '\n')
{
baudrate = strtoul(mkspeed, NULL, 0);
printf("Baudrate:%lu\n", baudrate);
break;
}
}
}
} // END while
printf ("Close config file.\n");
if(fclose(mf) == EOF) error_log("mf EOF.");
}
void child_kill() { printf("Child_kill.\n"); wait(NULL); }
void SendMessage(char *chat_id, char *send_text)
{
char host[] = "api.telegram.org";
char str[1024] = {0,};
int lenstr = (int)strlen("/sendMessage HTTP/1.1\r\nHost: api.telegram.org\r\nContent-Type: application/json\r\nContent-Length: ");
char json_str[128] = {0,};
snprintf(json_str, 1 + 11 + (int)strlen(chat_id) + 9 + (int)strlen(send_text) + 2, "%s%s%s%s%s", "{\"chat_id\":", chat_id, ",\"text\":\"", send_text, "\"}");
int lenjson = (int)strlen(json_str);
snprintf(str, 1 + 9 + (int)strlen(token) + lenstr + 3 + (int)strlen("\r\nConnection: close\r\n\r\n") + lenjson, "%s%s%s%d%s%s", "POST /bot", token, "/sendMessage HTTP/1.1\r\nHost: api.telegram.org\r\nContent-Type: application/json\r\nContent-Length: ", lenjson, "\r\nConnection: close\r\n\r\n", json_str);
struct hostent *server;
struct sockaddr_in serv_addr;
int sd = 0;
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) error_log("socket in SM.");
server = gethostbyname(host);
if (server == NULL) error_log("host in SM.");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(443);
memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length);
if(connect(sd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) error_log("connect.");
SSL_CTX * sslctx = SSL_CTX_new(TLSv1_2_client_method());
SSL * cSSL = SSL_new(sslctx);
if(SSL_set_fd(cSSL, sd) == 0) error_log("SSL_set_fd in SM.");
if(SSL_connect(cSSL) <= 0) error_log("SSL_connect in SM.");
int vsm = SSL_write(cSSL, str, (int)strlen(str));
if(vsm <= 0)
{
SSL_free(cSSL);
if(close(sd) == -1) error_log("close sd in SM.");
error_log("vsm = SSL_write in SM.");
}
printf("\nSendMessage: %s\n", str);
memset(str, 0, 1024);
int n = SSL_read(cSSL, str, 1022);
if(n <= 0)
{
SSL_free(cSSL);
if(close(sd) == -1) error_log("close client_3.");
printf("Err SSL_read in SM.\n");
}
//printf("\nReport_SM:%d\n%s\n", n, str);
SSL_free(cSSL);
SSL_CTX_free(sslctx);
if(close(sd) == -1) error_log("close sd in SendMessage.");
}
//////////////////////////////////////// ARDUINO /////////////////////////////////////////////////
void ardu_read_func(char *chat_id)
{
int i = 0;
int bytes = 0;
memset(bRead, 0, AREADSIZE * sizeof(char));
if((bytes = read(fd, bRead, AREADSIZE - 1)) == -1) error_log("Read_from_Arduino.");
for(i = 0; i <= bytes; i++)
{
if(bRead[i] == '\n')
{
bRead[i] = 0;
break;
}
}
tcflush(fd, TCIFLUSH);
printf("SM_from_Ardu: %s\n\n", bRead);
SendMessage(chat_id, bRead);
} // END ardu_read_func
/////////////////////////////////////////// open_port /////////////////////////////////////////////////
void open_port()
{
fd = open(mkpatch, O_RDWR | O_NOCTTY);
if(fd == -1) error_log("open /dev/ttyXXX.");
else
{
struct termios options;
tcgetattr(fd, &options);
switch(baudrate)
{
case 4800:
cfsetispeed(&options, B4800);
cfsetospeed(&options, B4800);
break;
case 9600:
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
break;
case 19200:
cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);
break;
case 38400:
cfsetispeed(&options, B38400);
cfsetospeed(&options, B38400);
break;
case 57600:
cfsetispeed(&options, B57600);
cfsetospeed(&options, B57600);
break;
case 115200:
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
break;
default:
error_log("baudrate_port.");
break;
}
options.c_cflag |= (CLOCAL | CREAD);
options.c_iflag = IGNCR;
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 1;
options.c_lflag = ICANON;
options.c_oflag = 0;
options.c_oflag &= ~OPOST;
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &options);
}
}
int main()
{
read_conf();
open_port();
sleep(2);
tcflush(fd, TCIFLUSH);
//////////////////////////////////// SSL //////////////////////////////////////////
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
SSL_library_init();
SSL_CTX * sslctx = SSL_CTX_new(TLSv1_2_server_method());
///////////////////////////// READ certificate ////////////////////////////////////
if(SSL_CTX_use_certificate_file(sslctx, "cert.pem", SSL_FILETYPE_PEM) <= 0) error_log("use_certificate_file.");
if(SSL_CTX_use_PrivateKey_file(sslctx, "cert.pem", SSL_FILETYPE_PEM) <= 0) error_log("use_PrivateKey_file.");
if(!SSL_CTX_check_private_key(sslctx)) error_log("check_private_key.");
/////////////////////////////////// SERVER ////////////////////////////////////////
int sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) error_log("descriptor socket.");
int one = 1;
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET;
s_addr.sin_addr.s_addr = INADDR_ANY;
s_addr.sin_port = htons(port);
if(bind(sd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0) error_log("binding.");
if(listen(sd, 5) == -1)
{
close(sd);
error_log("listen.");
}
char read_buffer[BREADSIZE] = {0,};
int client = 0;
while(1)
{
printf("Wait connection.\n");
memset(read_buffer, 0, BREADSIZE);
client = accept(sd, NULL, NULL);
if(client == -1)
{
printf("Not cl accept.\n");
if(close(client) == -1) error_log("close client_1.");
continue;
}
//printf("OK_1.\n");
SSL *ssl = SSL_new(sslctx);
if(SSL_set_fd(ssl, client) == 0) error_log("SSL_set_fd.");
int acc = SSL_accept(ssl);
if(acc <= 0)
{
SSL_free(ssl);
if(close(client) == -1) error_log("close client_2.");
printf("Not SSL_accept.\n");
continue;
}
//printf("OK2_acc:%d\n", acc);
/////////////////////// FORK ///////////////////////////
pid_t pid;
signal(SIGCHLD, child_kill);
pid = fork();
if(pid != 0)
{
SSL_free(ssl);
if(close(client) == -1) error_log("close client_pid.");
continue;
}
int n = SSL_read(ssl, read_buffer, BREADSIZE - 2); // first SSL_read
if(n <= 0)
{
SSL_free(ssl);
if(close(client) == -1) error_log("close client_3.");
printf("Disconnection:%d\n", n);
exit(0);
}
//printf("First SSL_read:%d %s\n", n, read_buffer);
//printf("OK_3.\n");
if(strstr(read_buffer, token) == NULL)
{
SSL_free(ssl);
if(close(client) == -1) error_log("close client_4.");
printf("Not valid POST.\n");
exit(0);
}
//printf("OK_4.\n");
if(strstr(read_buffer, "Content-Type: application/json") == NULL)
{
SSL_free(ssl);
if(close(client) == -1) error_log("close client_5.");
printf("Not json.\n");
exit(0);
}
//printf("OK_5.\n");
int len = atoi(strstr(read_buffer, "Content-Length: ") + strlen("Content-Length: "));
memset(read_buffer, 0, BREADSIZE);
int m = SSL_read(ssl, read_buffer, len); // second SSL_read
if(m <= 0)
{
SSL_free(ssl);
if(close(client) == -1) error_log("close client_8.");
error_log("m = SSL_read.");
}
//printf("\nSecond SSL_read:%d %s\n", m, read_buffer);
char *p;
//memset(chat_id, 0, 16);
char chat_id[16] = {0,};
if((p = strstr(read_buffer, "chat\":{\"id\":")) != NULL)
{
int index = p - read_buffer;
int i = 0;
int ot = index + 12;
for(; i <= 14; i++)
{
chat_id[i] = read_buffer[ot];
ot++;
if(chat_id[i] == ',')
{
chat_id[i] = 0;
printf("\nchat_id:%s\n", chat_id);
break;
}
}
}
char *q;
char msg_text[64] = {0,};
if((q = strstr(read_buffer, "text\":\"")) != NULL)
{
int index = q - read_buffer;
int i = 0;
int ot = index + 7;
for(; i <= 62; i++)
{
msg_text[i] = read_buffer[ot];
ot++;
if(msg_text[i] == '"')
{
msg_text[i] = 0;
printf("Text:%s\n", msg_text);
break;
}
}
}
int v = SSL_write(ssl, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", 38);
if(v <= 0)
{
SSL_free(ssl);
if(close(client) == -1) error_log("v = SSL_write.");
}
SSL_free(ssl);
if(close(client) == -1) error_log("close client_6.");
if(msg_text[0] == 't' && msg_text[1] == 0)
{
SendMessage(chat_id, "TEST");
}
else
{
char to_Ardu[128] = {0,};
snprintf(to_Ardu, strlen(msg_text) + strlen(mkpatch) + 11, "echo '%s' > %s", msg_text, mkpatch);
printf("\nto_Ardu:%s\n", to_Ardu);
system(to_Ardu);
ardu_read_func(chat_id);
}
exit(0);
} // END while(1)
if(close(sd) == -1) error_log("close sd client_7.");
}
gcc -Wall -Wextra telebotstd.c -o telebotstd -lcrypto -lssl
Makefile для OpenWrt
include $(TOPDIR)/rules.mk
PKG_NAME:=telebot
PKG_VERSION:=1
PKG_RELEASE:=1
PKG_BUILD_DIR:= $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/telebot
SECTION:=utils
CATEGORY:=Utilities
TITLE:=telebot - Telebot utility
DEPENDS:= +libopenssl +lssl
endef
define Package/telebot/description
telebot - Telebot utility
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(TARGET_CC) $(TARGET_CFLAGS) -c -o $(PKG_BUILD_DIR)/telebot.o $(PKG_BUILD_DIR)/telebot.c
$(TARGET_CC) $(TARGET_LDFLAGS) -o $(PKG_BUILD_DIR)/telebot $(PKG_BUILD_DIR)/telebot.o -lcrypto -lssl
endef
define Package/telebot/install
$(INSTALL_DIR) $(1)/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/telebot $(1)/
endef
$(eval $(call BuildPackage,telebot))
make package/telebot/compile V=s
Если будете запускать на роутере, то установите libopenssl (потребуется около 700Кб).
opkg update
opkg install libopenssl
Если что-то не понятно, то спрашивайте. С удовольствием отвечу.
![](http://istarik.ru/uploads/images/thnd.png)
- +32
- stD
14439
Поддержать автора
Комментарии (0)