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:

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

/media/uploads/dvddnr/screenshot_20180130-073732.png /media/uploads/dvddnr/screenshot_20180130-073703.png /media/uploads/dvddnr/arducam.jpeg /media/uploads/dvddnr/screenshot_20180216-102601.png

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-01-26
Revision:
5:f204a47aa813
Parent:
4:9926288a8703
Child:
6:94fc61e1cf40

File content as of revision 5:f204a47aa813:

#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"



#define WIFI_SSID ""
#define WIFI_PASSWORD ""
#define TELEGRAM_BOT_APIKEY ""

#define WIFI_WRITE_TIMEOUT 10000
#define WIFI_READ_TIMEOUT 10000
#define CONNECTION_TRIAL_MAX 10


// LED 
DigitalOut g_alivenessLED(LED1);

// wifi interfaces
ES_WIFIObject_t g_es_wifi_ctx;
bool wifi_connect(void);
bool open_tcp_connection(uint8_t socket, char *domain_name, uint16_t remote_port,bool secure);
bool close_tcp_connection(uint32_t socket);

// http I/O buffer
char g_http_io_buffer[ES_WIFI_DATA_SIZE];

// Telegram json I/O buffer
#define TBOT_JSON_BUFFER_SIZE 5 * 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=5&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_CUSTOM_KEYBOARD[] = "{\"keyboard\": [[\"Temperature\"],[\"Humidity\"],[\"Pressure\"]],\"one_time_keyboard\": true}";
bool telegram_get_update(int32_t update_id);
bool telegram_send_message();
#define TELEGRAM_BOT_INCOMING_CMD_SIZE 80
char g_incoming_msg[TELEGRAM_BOT_INCOMING_CMD_SIZE];
void telegram_bot(void);

// 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);

// HTTP util
bool http_request(char *http_server_domain,bool has_json_payload,bool secure);
bool http_parse_response(char *http_chunk, uint16_t http_chunk_len, bool *status_code_ok, uint16_t *content_len);

// 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);

int main()
{

    printf("> DISCO_L475VG_IOT01-Telegram-BOT started\r\n");

           
    /* Setup env sensors */
    BSP_TSENSOR_Init();
    BSP_HSENSOR_Init();
    BSP_PSENSOR_Init();
   

    /* start chatbot */
    telegram_bot();
}

void telegram_bot(void)
{
    int32_t update_id = 0;
    int32_t chat_id = 0;
    int json_results;
    bool well_done = false;

    /* wifi connect */
    if(wifi_connect())
    {
        printf("> Wifi connected\r\n");
    }
    else
    {
        NVIC_SystemReset();
    }


    /* set RTC */
    set_rtc_from_network();


    /* main loop */
    while (1)
    {
        g_alivenessLED = !g_alivenessLED;
        
        // 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");
            ES_WIFI_ResetModule(&g_es_wifi_ctx);
            wifi_connect();
            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,BSP_TSENSOR_ReadTemp(),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,BSP_HSENSOR_ReadHumidity(),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,BSP_PSENSOR_ReadPressure(),TELEGRAM_CUSTOM_KEYBOARD);
        }
        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() == 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;

    }
}



/*****************************************************************************************
 * 
 * 
 * update RTC using now.httpbin.org free service
 * 
 * 
 * ***************************************************************************************/

#define HTTPBIN_EPOCH_TAG "epoch\":"
bool set_rtc_from_network(void)
{
    /* prepare http get header */
    strcpy(g_http_io_buffer,NOW_HTTPBIN_ORG);
    if( http_request("now.httpbin.org",false,false) )
    {
        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)
{
    /* prepare http get header */
    snprintf(g_http_io_buffer, ES_WIFI_PAYLOAD_SIZE, TELEGRAM_GETUPDATES, update_id);
    return http_request("api.telegram.org",false,true);
}



bool telegram_send_message()
{
    /* prepare http get header */
    snprintf(g_http_io_buffer, ES_WIFI_PAYLOAD_SIZE, TELEGRAM_SENDMESSAGE, strlen(g_json_io_buffer));
    return http_request("api.telegram.org",true,true);
}

/**************************************************************************************************
*
*
* HTTP request transaction
*
*
***************************************************************************************************/
bool http_request(char *http_server_domain,bool has_json_payload,bool secure)
{
    uint16_t io_s, tx_s;
    ES_WIFI_Status_t io_status;
    bool http_ok;
    int32_t http_content_len;
    char *http_content_pivot;
    uint16_t content_chunk_size;
    bool ret_val = false;
    static uint8_t socket=0;

    /* open socket */
    socket++;
    if(socket==4) socket=0;
    io_status = ES_WIFI_STATUS_ERROR;
    for (int i = 0; i < CONNECTION_TRIAL_MAX; i++)
    {
        printf("> Open TCP connection ...\r\n");
        if (open_tcp_connection(socket, http_server_domain,(secure)?443:80,secure))
        {
            printf("> TCP Connection opened successfully.\r\n");
            io_status = ES_WIFI_STATUS_OK;
            break;
        }
    }

    if (io_status != ES_WIFI_STATUS_OK)
    {
        printf("> socket open error.\r\n");
        return false;
    }


    /* send http get header */
    tx_s = strlen(g_http_io_buffer);
    // printf(g_http_io_buffer);
    io_s = 0;
    if (ES_WIFI_SendData(&g_es_wifi_ctx, socket, (uint8_t *)g_http_io_buffer, tx_s, &io_s, WIFI_WRITE_TIMEOUT) != ES_WIFI_STATUS_OK)
    {
        printf("> ERROR : CANNOT send data\r\n");
        goto happy_end;
    }
    if (io_s != tx_s)
    {
        printf("> ERROR Send %d of %d.\r\n", io_s, tx_s);
        goto happy_end;
    }

    /* send json payload */
    if(has_json_payload)
    {
        tx_s = strlen(g_json_io_buffer);
        io_s = 0;
        if (ES_WIFI_SendData(&g_es_wifi_ctx, socket, (uint8_t *)g_json_io_buffer, tx_s, &io_s, WIFI_WRITE_TIMEOUT) != ES_WIFI_STATUS_OK)
        {
            printf("> ERROR : CANNOT send data\r\n");
            goto happy_end;
        }
        if (io_s != tx_s)
        {
            printf("> ERROR Send %d of %d.\r\n", io_s, tx_s);
            goto happy_end;
        }
    }

    /* from now on the http io buffer is reused */
    g_json_io_buffer[0]=0;
    g_http_io_buffer[0]=0;

    /* fetch response */
    io_status = ES_WIFI_ReceiveData(&g_es_wifi_ctx, socket, (uint8_t *)g_http_io_buffer, ES_WIFI_PAYLOAD_SIZE, &io_s, WIFI_READ_TIMEOUT);
    if (io_status != ES_WIFI_STATUS_OK)
    {
        printf("> ERROR : socket receive data\r\n");
        goto happy_end;
    }

    /* parse http response for the status code and content len */
    http_ok = false;
    http_content_len = 0;
    g_http_io_buffer[io_s] = 0;
    if (io_s == 0 || http_parse_response((char *)g_http_io_buffer, io_s, &http_ok, (uint16_t *)&http_content_len) == false)
    {
        printf("< Invalid response\r\n");
        for (int i = 0; i < io_s; i++)
            printf("%c", g_http_io_buffer[i]);
        printf("> Invalid response\r\n");
        goto happy_end;
    }
    printf("> HTTP OK = %d Content len = %d\r\n", http_ok, http_content_len);

    /* fetch json response */
    http_content_pivot = strstr((char *)g_http_io_buffer, "\r\n\r\n");
    if (http_content_pivot != NULL)
    {
        http_content_pivot += 4;
        content_chunk_size = strlen(http_content_pivot);
        if (content_chunk_size < TBOT_JSON_BUFFER_SIZE)
            strcpy(g_json_io_buffer, http_content_pivot);
        http_content_len -= content_chunk_size;
    }
    else
    {
        printf("< Invalid response\r\n");
        for (int i = 0; i < io_s; i++)
            printf("%c", g_http_io_buffer[i]);
        printf("> Invalid response\r\n");
        goto happy_end;
    }

    /* continue to fetch json chunck */
    while (http_content_len > 0)
    {
        if (ES_WIFI_ReceiveData2(&g_es_wifi_ctx, (uint8_t *)g_http_io_buffer, ES_WIFI_PAYLOAD_SIZE, &io_s) != ES_WIFI_STATUS_OK)
        {
            printf("> ERROR : socket receive data\r\n");
            goto happy_end;
        }
        g_http_io_buffer[io_s] = 0;
        http_content_len -= io_s;
        if (http_content_len < 0 || io_s == 0)
        {
            printf("> ERROR : http content len overflow\r\n");
            goto happy_end;
        }
        strcat(g_json_io_buffer, g_http_io_buffer);
    }
    ret_val = http_ok;

happy_end:
    printf("> Close TCP connection...\r\n");
    close_tcp_connection(socket);
    printf("> done.\r\n");
    return ret_val;
}




/*****************************************************************************************
*
*
*   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;
}

/*****************************************************************************************
*
*
*   HTTP parsing
*
*
******************************************************************************************/

const char HTTP_200OK[] = "200 OK\r\n";
const char HTTP_CL[] = "Content-Length: ";
bool http_parse_response(char *http_chunk, uint16_t http_chunk_len, bool *status_code_ok, uint16_t *content_len)
{
    char *line_pivot, *key_pivot;

    line_pivot = strstr(http_chunk, "\r\n");
    if (line_pivot == NULL)
        return false;

    key_pivot = strstr(http_chunk, HTTP_200OK);
    
    if (key_pivot != NULL)
        *status_code_ok = true;
    else
        *status_code_ok = false;

    while (1)
    {
        line_pivot = strstr(line_pivot + 2, "\r\n");
        if (line_pivot == NULL)
            break;
        key_pivot = strstr(http_chunk, HTTP_CL);
        if (key_pivot == NULL)
            continue;
        if (key_pivot < line_pivot)
        {
            key_pivot += strlen(HTTP_CL);
            *content_len = atoi((char const *)key_pivot);
            return true;
        }
    }

    return false;
}

/*****************************************************************************************
*
*
*   WIFI
*
*
******************************************************************************************/

bool wifi_connect(void)
{
    /* HW setup */
    if (ES_WIFI_RegisterBusIO(&g_es_wifi_ctx,
                              SPI_WIFI_Init,
                              SPI_WIFI_DeInit,
                              SPI_WIFI_Delay,
                              SPI_WIFI_SendData,
                              SPI_WIFI_ReceiveData) != ES_WIFI_STATUS_OK)
        return false;

    if (ES_WIFI_Init(&g_es_wifi_ctx) != ES_WIFI_STATUS_OK)
        return false;
    ES_WIFI_ResetToFactoryDefault(&g_es_wifi_ctx);

    /* JOIN AP */
    for (int i = 0; i < CONNECTION_TRIAL_MAX; i++)
    {
        if (ES_WIFI_Connect(&g_es_wifi_ctx, WIFI_SSID, WIFI_PASSWORD, ES_WIFI_SEC_WPA_WPA2) == ES_WIFI_STATUS_OK)
        {
            if (ES_WIFI_GetNetworkSettings(&g_es_wifi_ctx) == ES_WIFI_STATUS_OK)
                return true;
            else
                return false;
        }
        wait_ms(1000);
    }

    return false;
}


bool open_tcp_connection(uint8_t socket, char *domain_name, uint16_t remote_port,bool secure)
{
    ES_WIFI_Conn_t conn;

    conn.Number = socket;
    conn.RemotePort = remote_port;
    conn.LocalPort = 0;
    conn.Type = (secure)?ES_WIFI_TCP_SSL_CONNECTION:ES_WIFI_TCP_CONNECTION;
    strncpy((char *)conn.RemoteHost, domain_name, sizeof(conn.RemoteHost));
    return (ES_WIFI_ConnectToRemoteHost(&g_es_wifi_ctx, &conn) == ES_WIFI_STATUS_OK) ? true : false;
}

bool close_tcp_connection(uint32_t socket)
{
  ES_WIFI_Conn_t conn;
  conn.Number = socket;
  
  return (ES_WIFI_StopClientConnection(&g_es_wifi_ctx, &conn)== ES_WIFI_STATUS_OK);
}