EmbeddedRelated.com
Blogs

Bellegram, a wireless DIY doorbell that sends you a Telegram message

Sergio R CaprileJuly 9, 2023

This nice button uses the tiny M5 STAMP PICO and Cesanta's Mongoose as an HTTPS client to POST to the Telegram Bot API and send a message; the code is completely written in C. To try it, besides small fingers, you'll need to have a Wi-Fi network up and running and of course a Telegram account.

EDIT: updated to Mongoose 7.12

Hardware

For this demo there is no additional hardware needed, we just use the M5 STAMP PICO built-in (micro) button, I just soldered a two-pin .1" connector to power the module with a 5V USB cellphone charger during the demo, and a 6-pin .1" reused and hand-cut female header to connect the adapter used to program the module.

This article is available in PDF format for easy printing

The ESP32-PICO-D4 microcontroller works at 3.3V but the module has a built-in SMPS regulator to be able to work at 5V.

How to build

Before building we need to have a Telegram bot setup. This will provide us with a REST API we can call to get and send messages, that is, everything you send to your bot in a chat, can be picked up by GETting an API endpoint and parsing a JSON message, while POSTing to an API endpoint a JSON message causes your bot to send you a message that you'll see in the chat. You can also set if this triggers a notification or not.

The steps to create a Telegram bot can be simplified to:

  1. Create a bot and get its token, this requires that you follow the link above but is basically opening a chat with @BotFather and following its prompts
  2. Add this new bot to your conversations, that is, send it a /start message
  3. Get your chat id. For this, you GET an endpoint in your bot API and look for the proper field; the endpoint URL will look like <a href="https://api.telegram.org/your_bot_token/getUpdates">https://api.telegram.org/your_bot_token/getUpdates</a>, and the content you will retrieve will be something like
    {
      "ok":true,
      "result":[{
        "update_id":624210433,
        "message":{
          "message_id":1,
          "from":{"id":123456789,"is_bot":false,"first_name":"MyName","language_code":"en"
        },
        "chat":{
          "id":123456789,
          "first_name":"MyName",
          "type":"private"
        },
        "date":1679845218,
        "text":"/start",
        "entities":[{
          "offset":0,
          "length":6,
          "type":"bot_command"
        }]
       }
      }]
    }
        

Building requires Docker, it uses the latest ESP-IDF image from Espressif. If you don't have Docker, you should know how to build using your local copy of the IDF and tools, so just check the Makefile for how to put credentials into the EXTRA_CFLAGS environment variable.

Clone this Github repository and change to the "bellegram" directory, then call make to build with your data:

$ git clone https://github.com/scaprile/mongoose_apps.git
$ cd bellegram
$ make WIFI_SSID=mySSID WIFI_PASS=mypassword BOT_TOKEN=mytoken CHAT_ID=123456789

Finally, plug your board and flash it

$ make flash

Check your log to see when it connects to your Wi-Fi network, press the button, and you'll receive a Telegram message in your phone.

How does it work ?

The code is based on Mongoose HTTP client examples.

Mongoose is an event-driven framework that decouples us from the underlying networking stack and possible operating system, calling an event handler callback whenever there is an event of interest; for this to work, we need to periodically call its event manager, usually in an infinite loop. Inside this infinite loop we read the GPIO connected to the button and if it has been pressed we ask Mongoose to connect to the Telegram API URL for our bot, at the sendMessage endpoint

Note that we pass the enable variable address to the event manager; it will then pass that address to the event handler callback function when it calls it.

When the button is pressed, we call mg_http_connect() and disable further actions on the button until the process has finished; Mongoose then connects to the Telegram servers and when that happens our event handler callback will be called and passed the event. At that point, we need to initialize the TLS handshake process, and then send the data to be POSTed. That data will be a JSON message like this one:

{"text": "MESSAGE", "disable_notification": false}

The server will of course respond with a result code, but we do not pay attention to it. In a real-life application, we should expect and parse the results.

As we will not send any further messages, we close the connection and enable further processing of the button by setting enable to true (the event manager handed us a pointer to it).

Let's go deeper

"A dream within a dream ?"

Mongoose uses a Berkeley Sockets interface to interact with the underlying networking stack. The ESP-IDF uses a patched version of lwIP running on FreeRTOS, and MbedTLS to handle TLS. Mongoose then interacts with lwIP using Berkeley Sockets; lwIP internally may run in several threads over an RTOS, and will use a propietary decoupling mechanism called "netconn" that allows different tasks to interact with its RAW API.

When we call mg_http_connect(), Mongoose parses the URL and then opens a socket to send a UDP request to a DNS server to resolve this host name; once this process has successfully finished, it opens another socket and asks the stack to connect to the resolved IP address on the corresponding TCP port. lwIP will then take care of all the TCP related processing, interacting with the Wi-Fi driver.

When the server accepts the connection request, the Wi-Fi driver will handle the received frame to lwIP, which will buffer it and signal our socket that the connection has been accepted. When the event manager runs, it asks lwIP for outstanding events and sleeps for 50ms (a select() call blocks with timeout) if there are none. On an event, the call to select() will return and a flag will be set. Mongoose will then know what to do and call the corresponding event handler.

Once the TCP connection is established, the event manager calls our event handler callback function, passing an MG_EV_CONNECT event as argument. In that function, we call mg_tls_init() to initiate the TLS handshake process. This will be done by MbedTLS, and Mongoose will take care of it behind the scenes. We then build the HTTP header and JSON body and call mg_send() to send it. This function call will place data in a Mongoose buffer, and the event manager will later call the socket send function as the TCP/IP stack signals there is room for more data to be sent. As the content we are sending is pretty small, this will happen right away.

Using TLS requires having CA certificates to be able to validate the host to whom we are connecting. These certificates are collected in a file, and to avoid dealing with a file system inside the microcontroller, Mongoose provides a way to pack a group of static files and embed that into the code, so it can be flashed along with the code when programming the microcontroller.



To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: