A Telegram BOT for this awesome all-in-one board.
Dependencies: BSP_B-L475E-IOT01 mbed es_wifi jsmn
Telegram Bot for DISCO_L475VG_IOT01
This application embeds aTelegram chatbot into the DISCO_L475VG_IOT01 board.
The Bot answers to the users queries about:
- Real time environmental data taken from the on board sensors.
- Environmental data history of the latest 24 hours stored on board.
- Camera images taken from the Arducam-mini-2mp (optional).
This software uses:
- A modified version of the wifi library provided by ST in order to enable the TCP-SSL connection.
- An open source json parser jsmn (https://github.com/zserge/jsmn).
- A web service (http://now.http.org) to initialize the RTC.
- A web service (https://image-charts.com) to publish temperature,humidity and pressure charts.
- A modified version of the Arducam driver (https://os.mbed.com/users/dflet/)
Compilation
Import in your compiler and modify the following defines:
- WIFI_SSID
- WIFI_PASSWORD
- TELEGRAM_BOT_APIKEY
Please follow the Telegram bots documentation (https://core.telegram.org/bots) to better understand how the Telegram API works and how to create your bot.
In order to support the Arducam-Mini-2MP set WITH_ARDUCAM_2640 #define to 1.
Screenshots
Security
The Inventek wifi module creates the ssl connection but does not authenticate the server's certificate ( AT cmd P9=0 ).
For more details http://www.inventeksys.com/IWIN/programming-certificates-tcp-ssltls/
main.cpp
- Committer:
- dvddnr
- Date:
- 2018-02-16
- Revision:
- 10:28cf58359411
- Parent:
- 9:d24842a5a468
File content as of revision 10:28cf58359411:
#include "mbed.h" #include "stm32l475e_iot01_tsensor.h" #include "stm32l475e_iot01_hsensor.h" #include "stm32l475e_iot01_psensor.h" #include "es_wifi.h" #include "es_wifi_io.h" #include "jsmn.h" #include "TCPConnector.h" #include "HTTPHelper.h" #define WIFI_SSID "" #define WIFI_PASSWORD "" #define TELEGRAM_BOT_APIKEY "" #define WITH_ARDUCAM_2640 0 #define CONNECTION_TRIAL_MAX 10 // LED DigitalOut g_alivenessLED(LED1); // secure tcp connector TCPConnector g_tcp_connector; // http parser utils HTTPHelper g_http_parser; // arducam driver #if WITH_ARDUCAM_2640 #include "ArduCAM2640.h" ArduCAM2640 g_arducam; /* jpeg buffer */ #define IMAGE_JPG_SIZE 16*1024 uint8_t g_image_buffer[IMAGE_JPG_SIZE]; #endif // http I/O buffer char g_http_io_buffer[ES_WIFI_DATA_SIZE]; // Telegram json I/O buffer #define TBOT_JSON_BUFFER_SIZE 3 * 1024 char g_json_io_buffer[TBOT_JSON_BUFFER_SIZE]; // telegram REST API const char TELEGRAM_GETUPDATES[] = "GET /bot" TELEGRAM_BOT_APIKEY "/getUpdates?offset=%d&timeout=20&limit=1 HTTP/1.1\r\nHost: api.telegram.org\r\nUser-Agent: curl/7.50.1\r\nAccept: */*\r\n\r\n"; const char TELEGRAM_SENDMESSAGE[] = "GET /bot" TELEGRAM_BOT_APIKEY "/sendMessage HTTP/1.1\r\nHost: api.telegram.org\r\nUser-Agent: curl/7.50.1\r\nAccept: */*\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n"; const char TELEGRAM_SENDPHOTO[] = "POST /bot" TELEGRAM_BOT_APIKEY "/sendPhoto HTTP/1.1\r\nHost: api.telegram.org\r\nUser-Agent: curl/7.47.0\r\nAccept: */*\r\nContent-Length: %d\r\nConnection: close\r\n"; #if WITH_ARDUCAM_2640 const char TELEGRAM_CUSTOM_KEYBOARD[] = "{\"keyboard\": [[\"Temperature\",\"Humidity\",\"Pressure\"],[\"Daily\",\"RTC\",\"Camera\"]],\"one_time_keyboard\": true}"; #else const char TELEGRAM_CUSTOM_KEYBOARD[] = "{\"keyboard\": [[\"Temperature\",\"Humidity\",\"Pressure\"],[\"Daily\",\"RTC\"]],\"one_time_keyboard\": true}"; #endif bool telegram_get_update(int32_t update_id); bool telegram_send_message(char *json_msg); bool telegram_send_photo(uint8_t *image, uint32_t image_size, int32_t chat_id, char *caption); #define TELEGRAM_BOT_INCOMING_CMD_SIZE 80 char g_incoming_msg[TELEGRAM_BOT_INCOMING_CMD_SIZE]; void telegram_bot(); // now.httpbin.org const char NOW_HTTPBIN_ORG[] = "GET / HTTP/1.1\r\nHost: now.httpbin.org\r\nUser-Agent: curl/7.50.1\r\nAccept: */*\r\n\r\n"; bool set_rtc_from_network(void); // JSON parser #define JSON_MAX_TOKENS 128 jsmn_parser g_json_parser; jsmntok_t g_json_tokens[JSON_MAX_TOKENS]; bool jsoneq(const char *json, jsmntok_t *tok, const char *s,jsmntype_t type); // Data history float g_daily_data_history_temp[24][60]; float g_daily_data_history_pres[24][60]; float g_daily_data_history_humi[24][60]; void daily_history_update(int hour, int min,float t, float p, float h); int32_t daily_humidity_history_chart(char *report_buffer, uint32_t buffer_len); int32_t daily_temperature_history_chart(char *report_buffer, uint32_t buffer_len); int32_t daily_pressure_history_chart(char *report_buffer, uint32_t buffer_len); int main() { printf("> DISCO_L475VG_IOT01-Telegram-BOT started\r\n"); #if WITH_ARDUCAM_2640 /* Setup camera */ I2C camI2C(PB_9,PB_8); SPI camSPI(PA_7,PA_6,PA_5); DigitalOut cam_spi_cs(PA_2); if( g_arducam.Setup( OV_RESOLUTION_CIF,92, &cam_spi_cs, &camSPI, &camI2C) == false) { printf("Arducam setup error \r\n"); while(1); } #endif /* Setup env sensors */ BSP_TSENSOR_Init(); BSP_HSENSOR_Init(); BSP_PSENSOR_Init(); /* start chatbot */ telegram_bot(); } void telegram_bot() { int32_t update_id = 0; int32_t chat_id = 0; int json_results; bool well_done = false; time_t timestamp; struct tm *timestamp_tm; float temp,pres,humi; /* wifi connect */ if(g_tcp_connector.wifi_connect(WIFI_SSID,WIFI_PASSWORD,CONNECTION_TRIAL_MAX)) { printf("> Wifi connected\r\n"); } else { NVIC_SystemReset(); } /* set RTC */ set_rtc_from_network(); /* init data history */ for(int i=0; i<24; i++) { for(int ii=0; ii<60; ii++) { g_daily_data_history_temp[i][ii]=0.0F; g_daily_data_history_pres[i][ii]=0.0F; g_daily_data_history_humi[i][ii]=0.0F; } } /* main loop */ while (1) { g_alivenessLED = !g_alivenessLED; /* history update */ timestamp = time(NULL); timestamp_tm = localtime(×tamp); temp = BSP_TSENSOR_ReadTemp(); pres = BSP_PSENSOR_ReadPressure(); humi = BSP_HSENSOR_ReadHumidity(); daily_history_update( timestamp_tm->tm_hour, timestamp_tm->tm_min, temp, pres, humi ); // Get updates -- API method getUpdates printf("> Get updates\r\n"); g_json_io_buffer[0]=0; if (telegram_get_update(update_id + 1) == false) { printf("> ERROR telegram_get_update\r\n> Reset wifi connection\r\n"); g_tcp_connector.wifi_connect(WIFI_SSID,WIFI_PASSWORD,CONNECTION_TRIAL_MAX); continue; } printf("> JSON content: %s\r\n", g_json_io_buffer); // Parsing json response jsmn_init(&g_json_parser); json_results = jsmn_parse(&g_json_parser,g_json_io_buffer,strlen(g_json_io_buffer),g_json_tokens,JSON_MAX_TOKENS); if(json_results < 4) { printf("> ERROR invalid json response\r\n"); continue; } /* check ok */ if( jsoneq(g_json_io_buffer,&g_json_tokens[1],"ok",JSMN_STRING) == false ) continue; if( jsoneq(g_json_io_buffer,&g_json_tokens[2],"true",JSMN_PRIMITIVE) == false ) continue; /* fetch update id */ well_done = false; for(int i=3;i<json_results;i++) { if( jsoneq(g_json_io_buffer,&g_json_tokens[i],"update_id",JSMN_STRING) == true ) { g_json_io_buffer[g_json_tokens[i+1].end]=0; update_id = atoi(g_json_io_buffer+g_json_tokens[i+1].start); well_done = true; } } // update_id not found ? if(well_done == false) continue; /* fetch chat id */ well_done = false; for(int i=3;i<json_results;i++) { if( jsoneq(g_json_io_buffer,&g_json_tokens[i],"id",JSMN_STRING) == true ) { g_json_io_buffer[g_json_tokens[i+1].end]=0; chat_id = atoi(g_json_io_buffer+g_json_tokens[i+1].start); well_done = true; } } // chat_id not found ? if(well_done == false) continue; /*fetch message */ well_done = false; g_incoming_msg[0]=0; for(int i=3;i<json_results;i++) { if( jsoneq(g_json_io_buffer,&g_json_tokens[i],"text",JSMN_STRING) == true ) { int msg_len = g_json_tokens[i+1].end - g_json_tokens[i+1].start; if( msg_len < TELEGRAM_BOT_INCOMING_CMD_SIZE) { memcpy(g_incoming_msg,g_json_io_buffer+g_json_tokens[i+1].start,msg_len); g_incoming_msg[msg_len] = 0; well_done = true; } break; } } printf("> Incoming msg: %s\n\r",g_incoming_msg); // parse incoming message if( strstr(g_incoming_msg,"Temperature") != NULL) { snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"Temperature %.2f degC\",\"reply_markup\":%s}", chat_id,temp,TELEGRAM_CUSTOM_KEYBOARD); } else if( strstr(g_incoming_msg,"Humidity") != NULL) { snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"Humidity %.2f %%\",\"reply_markup\":%s}", chat_id,humi,TELEGRAM_CUSTOM_KEYBOARD); } else if( strstr(g_incoming_msg,"Pressure") != NULL) { snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"Pressure %.2f mBar\",\"reply_markup\":%s}", chat_id,pres,TELEGRAM_CUSTOM_KEYBOARD); } else if( strstr(g_incoming_msg,"RTC") != NULL) { snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"%s UTC\",\"reply_markup\":%s}", chat_id,ctime(×tamp),TELEGRAM_CUSTOM_KEYBOARD); } else if( strstr(g_incoming_msg,"Daily") != NULL ) { int pivot = snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"", chat_id); pivot += daily_temperature_history_chart( g_json_io_buffer + pivot, TBOT_JSON_BUFFER_SIZE - pivot ); snprintf( g_json_io_buffer + pivot, TBOT_JSON_BUFFER_SIZE - pivot, "\"}"); if( telegram_send_message(g_json_io_buffer) == false) { printf("> ERROR telegram_send_message\r\n"); continue; } pivot = snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"", chat_id); pivot += daily_humidity_history_chart( g_json_io_buffer + pivot, TBOT_JSON_BUFFER_SIZE - pivot ); snprintf( g_json_io_buffer + pivot, TBOT_JSON_BUFFER_SIZE - pivot, "\"}"); if( telegram_send_message(g_json_io_buffer) == false) { printf("> ERROR telegram_send_message\r\n"); continue; } pivot = snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"", chat_id); pivot += daily_pressure_history_chart( g_json_io_buffer + pivot, TBOT_JSON_BUFFER_SIZE - pivot ); snprintf( g_json_io_buffer + pivot, TBOT_JSON_BUFFER_SIZE - pivot, "\",\"reply_markup\":%s}",TELEGRAM_CUSTOM_KEYBOARD); } #if WITH_ARDUCAM_2640 else if( strstr(g_incoming_msg,"Camera") != NULL ) { uint32_t image_size; uint32_t idx; snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"Sending photos ...\",\"reply_markup\":%s}", chat_id,TELEGRAM_CUSTOM_KEYBOARD); if( telegram_send_message(g_json_io_buffer) == false) { printf("> ERROR telegram_send_message\r\n"); continue; } for(int i=0; i<3; i++) { image_size = g_arducam.CaptureImage(g_image_buffer,IMAGE_JPG_SIZE,&idx); if( image_size == 0 || telegram_send_photo(g_image_buffer+idx, image_size, chat_id, "Images from space") == false ) { printf("Photo failure %d\r\n",image_size); break; } } snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"Done.\",\"reply_markup\":%s}", chat_id,TELEGRAM_CUSTOM_KEYBOARD); } #endif else { snprintf(g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,"{\"chat_id\":%d,\"text\":\"Available commands\",\"reply_markup\":%s}", chat_id,TELEGRAM_CUSTOM_KEYBOARD); } if( telegram_send_message(g_json_io_buffer) == false) { printf("> ERROR telegram_send_message\r\n"); continue; } jsmn_init(&g_json_parser); json_results = jsmn_parse(&g_json_parser,g_json_io_buffer,strlen(g_json_io_buffer),g_json_tokens,JSON_MAX_TOKENS); if(json_results < 4) { printf("> ERROR invalid json response\r\n"); continue; } /* check ok */ if( jsoneq(g_json_io_buffer,&g_json_tokens[1],"ok",JSMN_STRING) == false ) continue; if( jsoneq(g_json_io_buffer,&g_json_tokens[2],"true",JSMN_PRIMITIVE) == false ) continue; } } /***************************************************************************************** * * * Daily history update * * ******************************************************************************************/ void daily_history_update( int hour, int min, float t, float p, float h ) { g_daily_data_history_temp[hour][min] = t; g_daily_data_history_pres[hour][min] = p; g_daily_data_history_humi[hour][min] = h; } int32_t daily_temperature_history_chart(char *report_buffer, uint32_t buffer_len) { float sum; uint32_t num; int32_t pivot = 0; // temperature chart pivot = snprintf(report_buffer,buffer_len,"Daily temperature chart\n\nhttps://image-charts.com/chart?chxt=x,y&chxr=1,0,%d&cht=lc&chd=t:",50); for(int i=0;i<24;i++) { sum = 0.0F; num = 0; for(int ii=0;ii<60;ii++) { if(g_daily_data_history_temp[i][ii] > 0.0F) { num++; sum += g_daily_data_history_temp[i][ii]; } } pivot += snprintf(report_buffer+pivot,buffer_len-pivot,"%d,",(num>0)?(int32_t)sum/num:0); } pivot--; pivot += snprintf(report_buffer+pivot,buffer_len-pivot,"%s","&chco=76A4FB&chls=2.0&chs=600x300&chg=10,10"); report_buffer[pivot]=0; return pivot; } int32_t daily_humidity_history_chart(char *report_buffer, uint32_t buffer_len) { float sum; uint32_t num; int32_t pivot = 0; // humidity chart pivot += snprintf(report_buffer+pivot,buffer_len-pivot,"Daily humidity chart\n\nhttps://image-charts.com/chart?chxt=x,y&chxr=1,0,%d&cht=lc&chd=t:",100); for(int i=0;i<24;i++) { sum = 0.0F; num = 0; for(int ii=0;ii<60;ii++) { if(g_daily_data_history_humi[i][ii] > 0.0F) { num++; sum += g_daily_data_history_humi[i][ii]; } } pivot += snprintf(report_buffer+pivot,buffer_len-pivot,"%d,",(num>0)?(int32_t)sum/num:0); } pivot--; pivot += snprintf(report_buffer+pivot,buffer_len-pivot,"%s","&chco=76A4FB&chls=2.0&chs=600x300&chg=10,10\n\n"); report_buffer[pivot]=0; return pivot; } int32_t daily_pressure_history_chart(char *report_buffer, uint32_t buffer_len) { float sum; uint32_t num; int32_t pivot = 0; // pressure chart pivot += snprintf(report_buffer+pivot,buffer_len-pivot,"Daily pressure chart\n\nhttps://image-charts.com/chart?chxt=x,y&chxr=1,%d,%d&cht=lc&chd=t:",900,1100); for(int i=0;i<24;i++) { sum = 0.0F; num = 0; for(int ii=0;ii<60;ii++) { if(g_daily_data_history_pres[i][ii] > 0.0F) { num++; sum += g_daily_data_history_pres[i][ii]; } } pivot += snprintf(report_buffer+pivot,buffer_len-pivot,"%d,",(num>0)?(int32_t)sum/num:0); } pivot--; pivot += snprintf(report_buffer+pivot,buffer_len-pivot,"%s","&chco=76A4FB&chls=2.0&chs=600x300&chg=10,10&chds=a"); report_buffer[pivot]=0; return pivot; } /***************************************************************************************** * * * update RTC using now.httpbin.org free service * * * ***************************************************************************************/ #define HTTPBIN_EPOCH_TAG "epoch\":" bool set_rtc_from_network(void) { uint32_t http_content_len=0; bool http_ok = false; /* prepare http get header */ strcpy(g_http_io_buffer,NOW_HTTPBIN_ORG); // CONNECT http://now.httpbin.org if( g_tcp_connector.tcp_connect(0x01,"now.httpbin.org",80,false,3) ) { // send HTML GET if( g_tcp_connector.tcp_write(0x01,g_http_io_buffer,strlen(g_http_io_buffer))) { // read json response if( g_http_parser.HttpParseResponse(&g_tcp_connector,0x01,g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,&http_ok,&http_content_len) ) { if(http_ok) { g_json_io_buffer[http_content_len] = 0; } } } } g_tcp_connector.tcp_close(0x01); // parse and set time if( http_ok ) { char *epoch = strstr(g_json_io_buffer,HTTPBIN_EPOCH_TAG); if(epoch == NULL) return false; epoch += sizeof( HTTPBIN_EPOCH_TAG ); for(int i=0;i<20;i++) { if( epoch[i] == '.') { epoch[i] = 0; printf("> now.httpbin.org epoch: %s\r\n",epoch); set_time( atoi(epoch) ); return true; } } } return false; } /***************************************************************************************** * * * telegram rest api * * ******************************************************************************************/ bool telegram_get_update(int32_t update_id) { uint32_t http_content_len=0; bool http_ok = false; /* prepare http get header */ snprintf(g_http_io_buffer, ES_WIFI_PAYLOAD_SIZE, TELEGRAM_GETUPDATES, update_id); // CONNECT https://api.telegram.org if( g_tcp_connector.tcp_connect(0x01,"api.telegram.org",443,true,3) ) { // send HTML GET getUpdates if( g_tcp_connector.tcp_write(0x01,g_http_io_buffer,strlen(g_http_io_buffer))) { // read json response if( g_http_parser.HttpParseResponse(&g_tcp_connector,0x01,g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,&http_ok,&http_content_len) ) { if(http_ok) { g_json_io_buffer[http_content_len] = 0; } } } g_tcp_connector.tcp_close(0x01); } return http_ok; } bool telegram_send_message(char *json_msg) { uint32_t http_content_len=0,msg_len; bool http_ok = false; msg_len = strlen(json_msg); /* prepare http get header */ snprintf(g_http_io_buffer, ES_WIFI_PAYLOAD_SIZE, TELEGRAM_SENDMESSAGE, msg_len); // CONNECT https://api.telegram.org if( g_tcp_connector.tcp_connect(0x01,"api.telegram.org",443,true,3) ) { // send HTML GET sendMessage if( g_tcp_connector.tcp_write(0x01,g_http_io_buffer,strlen(g_http_io_buffer))) { // send payload if( g_tcp_connector.tcp_write(0x01,json_msg,msg_len) ) { // read json response if( g_http_parser.HttpParseResponse(&g_tcp_connector,1,g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,&http_ok,&http_content_len) ) { if(http_ok) { g_json_io_buffer[http_content_len] = 0; } } } } g_tcp_connector.tcp_close(0x01); } return http_ok; } bool telegram_send_photo(uint8_t *image, uint32_t image_size, int32_t chat_id, char *caption) { char chat_id_str[32]; uint32_t content_len = 0; uint32_t http_content_len=0; bool http_ok = false; content_len = 383; // pre-calculated boundary len content_len += snprintf(chat_id_str,sizeof(chat_id_str),"%d",chat_id); content_len += strlen(caption); content_len += image_size; /* prepare http post header */ snprintf(g_http_io_buffer, ES_WIFI_PAYLOAD_SIZE, TELEGRAM_SENDPHOTO, content_len); // CONNECT https://api.telegram.org if( g_tcp_connector.tcp_connect(0x01,"api.telegram.org",443,true,3) ) { // send HTML POST sendMessage if( g_tcp_connector.tcp_write(0x01,g_http_io_buffer,strlen(g_http_io_buffer))) { strcpy(g_http_io_buffer,"Content-Type: multipart/form-data; boundary=------------------------660d8ea934533f2b\r\n"); if( g_tcp_connector.tcp_write(0x01,g_http_io_buffer,strlen(g_http_io_buffer))) { snprintf(g_http_io_buffer, ES_WIFI_PAYLOAD_SIZE, "\r\n--------------------------660d8ea934533f2b\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\n%s", chat_id_str); if( g_tcp_connector.tcp_write(0x01,g_http_io_buffer,strlen(g_http_io_buffer))) { snprintf(g_http_io_buffer, ES_WIFI_PAYLOAD_SIZE, "\r\n--------------------------660d8ea934533f2b\r\nContent-Disposition: form-data; name=\"caption\"\r\n\r\n%s", caption); if( g_tcp_connector.tcp_write(0x01,g_http_io_buffer,strlen(g_http_io_buffer))) { strcpy(g_http_io_buffer, "\r\n--------------------------660d8ea934533f2b\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"acam2640.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n"); if( g_tcp_connector.tcp_write(0x01,g_http_io_buffer,strlen(g_http_io_buffer))) { if( g_tcp_connector.tcp_write(0x01,(char*)image,image_size)) { strcpy(g_http_io_buffer,"\r\n--------------------------660d8ea934533f2b--\r\n"); if( g_tcp_connector.tcp_write(0x01,g_http_io_buffer,strlen(g_http_io_buffer)) ) { // read json response if( g_http_parser.HttpParseResponse(&g_tcp_connector,0x01,g_json_io_buffer,TBOT_JSON_BUFFER_SIZE,&http_ok,&http_content_len) ) { if(http_ok) { g_json_io_buffer[http_content_len] = 0; } } } } } } } } } g_tcp_connector.tcp_close(0x01); } return http_ok; } /***************************************************************************************** * * * JSON parsing * * ******************************************************************************************/ bool jsoneq(const char *json, jsmntok_t *tok, const char *s,jsmntype_t type) { if (tok->type == type && (int) strlen(s) == tok->end - tok->start && strncmp(json + tok->start, s, tok->end - tok->start) == 0) { return true; } return false; }