ESP32 MQTT tutorial

MQTT stands for Messaging queue telemetry transport 

Broker & server is termed as the same meaning 

It is a lightweight publish/subscribe messaging protocol 



We will go through this example 

https://github.com/espressif/esp-idf/tree/master/examples/protocols/mqtt/tcp


The example uses ESP-MQTT library https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/protocols/mqtt.html


ESP-MQTT library is used to implement the MQTT protocol client 


By default MQTT client uses the event Loop library to post  MQTT events so in code you have to create the default event loop so that events can be captured in the code


ESP_ERROR_CHECK(esp_event_loop_create_default());


MQTT related Events are 

MQTT_EVENT_CONNECTED

MQTT_EVENT_PUBLISHED

MQTT_EVENT_DISCONNECTED

MQTT_EVENT_DATA

MQTT_EVENT_SUBSCRIBED

MQTT_EVENT_ERROR

MQTT_EVENT_UNSUBSCRIBED


 

To make a ESP-MQTT based client a esp_mqtt_client_config_t structure type variable should be created 


const esp_mqtt_client_config_t mqtt_cfg = {
        .uri = CONFIG_BROKER_URI,
};


Here the CONFIG_BROKER_URI is the address of the broker to which the MQTT client will connect. This MACRO is set by idf.py menuconfig 


URI example mqtt://mqtt.eclipse.org 


The MQTT client configuration structure has many members, i am going to list out the important ones here 


mqtt_event_callback_t event_handle;

typedef esp_err_t (* mqtt_event_callback_t)(esp_mqtt_event_handle_t event);


Callback function to which mqtt events will go 

esp_event_loop_handle_t event_loop_handle;

Handle for MQTT event loop library 

const char *host;

MQTT broker address as ipv4 string

const char *uri;

MQTT broker address as URI

uint32_t port;

MQTT port

const char *client_id;

Default client ID format “ESP32_%CHIPID%“

const char *username;

MQTT username

const char *password;

MQTT password 

int disable_clean_session;

MQTT clean session, default is true 

int keepalive;

Default: 120 sec 

bool disable_auto_reconnect;

Default: false, MQTT client will reconnect to the broker at error/disconnect 

int task_prio;

The default priority is 5, which can be changed by menuconfig 

int task_stack;

Default is 6144 bytes, can be changed by menuconfig 

int buffer_size; 

MQTT send/receive buffer, default is 1024


After the configuration of the structure, we have to create a MQTT Client handle based on the configuration using the following function. 


esp_mqtt_client_handle_t          esp_mqtt_client_init (const esp_mqtt_client_config_t *config);


esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);


We can assume that this handle is our mqtt Client , we have to use this handle in other parts of the code 


Now we have to register for the mqtt event using the following function provided by the ESP-MQTT API 


esp_err_t esp_mqtt_client_register_event(
            esp_mqtt_client_handle_t client,                       // mqtt client handle
            esp_mqtt_event_id_t event,                              // event type
            esp_event_handler_t event_handler,                //handler callback
            void* event_handler_arg
);


esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);

We have registered the client for any ESP EVENTS 


Here    esp_event_handler_t   type parameter event_handler is a pointer to a function . it has the following signature 

typedef void * esp_event_loop_handle_t


It is a pointer to a function. the function should return void and the argument list is unspecified. As the API is written in C , so it is not required for the function to be prototyped before being defined or used. 


Ref: https://stackoverflow.com/questions/3982470/what-does-typedef-void-something-mean

we pass mqtt_event_handler() function as an argument for this parameter and define this function in our application source code 


static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
    mqtt_event_handler_cb(event_data);
}


You can see that this handler function calls another callback function mqtt_event_handler_cb()

This callback function is the function where the MQTT events are processed 


static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{
// handle MQTT events
}


At registration the handler function was passed now , we will start the client

esp_mqtt_client_start(client);


Now what happens is that the ESP-MQTT client starts running and when MQTT events occur the mqtt_event_handler() function gets called, this function prints the event type and passes the event_data argument to mqtt_event_handler_cb() function where the events get processed 


The event_data argument is of type esp_mqtt_event_handle_t which is actually a pointer of type esp_mqtt_event_t

/**
* MQTT event configuration structure
*/
typedef struct {
    esp_mqtt_event_id_t event_id;       /*!< MQTT event type */
    esp_mqtt_client_handle_t client;    /*!< MQTT client handle for this event */
    void *user_context;                 /*!< User context passed from MQTT client config */
    char *data;                         /*!< Data associated with this event */
    int data_len;                       /*!< Length of the data for this event */
    int total_data_len;                 /*!< Total length of the data (longer data are supplied with multiple events) */
    int current_data_offset;            /*!< Actual offset for the data associated with this event */
    char *topic;                        /*!< Topic associated with this event */
    int topic_len;                      /*!< Length of the topic for this event associated with this event */
    int msg_id;                         /*!< MQTT message id of message */
    int session_present;                /*!< MQTT session_present flag for connection event */
    void* error_handle;                 /*!< esp-tls error handle referencing last error/flags captured in transports */
} esp_mqtt_event_t;

typedef esp_mqtt_event_t *esp_mqtt_event_handle_t;


Now that we have understood the code, let's build the example code 


Broker Address Set 

We will use a public broker to test the code, i have used mqtt.eclipse.org 


As it is a public broker it may be unavailable, so let’s check if it is active or not in our host 


We will open a terminal [CTRL+ALT+T] where we will subscribe on a topic, then we will open another terminal and publish a message to the broker on the previously input topic


>>subscribe

hassin@hassin-HP:~$ mosquitto_sub -h mqtt.eclipse.org -p 1883 -t 'ESP32/test'

>>publish at another terminal

hassin@hassin-HP:~$ mosquitto_pub -h mqtt.eclipse.org -p 1883 -t 'ESP32/test' -m 'hello world'

If at the subscribed terminal you see ‘hello world’ message printed then MQTT communication is okay


Now let’s set the broker address in our esp32 MQTT example code using menuconfig 

After setting the broker to build a flash the program to the ESP32 and if everything is okay you will mqtt communication is working 


By this example, we have been able to implement the basic publish-subscribe and data receive event mechanism. in next post I will show how to wrap all these in an efficient way so that our application can do other kind of work such as ADC sampling 

ESP32 Socket Programming

 BSD socket API is A set of standard function calls to include internet communication capabilities in a product 


Other sockets API exists but the BSD socket is generally regarded as the standard. 


The sockets API makes use of 2 mechanisms to deliver data to the application level: ports & sockets. 


All TCP/IP stacks have 65,535 ports for both TCP & UDP 


A port is not a physical interface. Once an application has created a socket and bound it to a port, data destined for that port will be delivered to the application. 


A TCP/IP or UDP/IP packet is transmitted to a host, it eventually goes to the correct port then the socket conveys the packet’s data to the application 


Now Let’s go though some common socket calls 


socket()

Creates a socket & returns a reference number for that socket 

connect()

Creates a connection with another host 

send()

Send data over a connected socket 

recv() 

Receive data on the connected host 


Now we will go through this example https://github.com/espressif/esp-idf/tree/master/examples/protocols/sockets/tcp_client 


This example makes esp32 a tcp client. At the host computer,  a script will run as a TCP server. 


ESP32 has a lwIP stack, this stack has the socket API. 

The API can be found in this file 


esp-idf/components/lwip/lwip/src/include/lwip/sockets.h


To create a socket instance we have to use the following function of sockets.h 

static inline int socket(int domain,int type,int protocol)
{ return lwip_socket(domain,type,protocol); }


The type argument can be from the following values 

/* Socket protocol types (TCP/UDP/RAW) */
#define SOCK_STREAM     1
#define SOCK_DGRAM      2
#define SOCK_RAW        3


domain & protocol initialized as 

int addr_family;
int ip_protocol;
.......
.......
addr_family = AF_INET;
ip_protocol = IPPROTO_IP;


The socket() creates a socket and returns the socket number 

int sock =  socket(addr_family, SOCK_STREAM, ip_protocol);


Now we are going to understand another basic structure related to socket programming which is sockaddr_in 

/* members are in network byte order */
struct sockaddr_in {
  u8_t            sin_len;          //length of this structure
  sa_family_t     sin_family;       //AF_INET
  in_port_t       sin_port;         //transport layer port
  struct in_addr  sin_addr;         //the ip address
#define SIN_ZERO_LEN 8
  char            sin_zero[SIN_ZERO_LEN];
};


We have to create this structure type variable and use it to assign the server’s IP address to which we want to connect 


#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR
#define PORT CONFIG_EXAMPLE_PORT


struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(PORT);
inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1);


Then this variable is used at the socket connection method 

int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));

This above function will connect to the server 

Similarly, other function such as send() and recv() of the API is used for communication 


The example code connects to wifi using example_connect() function, this is a blocking function and the block waits until a connection is established. the default event loop must be created before as wifi connection code is event based

tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect());


The code creates a freeRtos task that tries to connect to the server in an infinite loop and if connected then it communicates with the server in another infinite loop 


Running the Server Script

hassin@hassin-HP:~/esp/esp-idf/examples/protocols/sockets/scripts$ ifconfig


enx34298f9117bd: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.11.96  netmask 255.255.255.0  broadcast 192.168.11.255


hassin@hassin-HP:~/esp/esp-idf/examples/protocols/sockets/scripts$ python3 tcpserver.py


Socket created
Socket binded
Socket listening


So the server is running on our host whose IP is 192.168.11.96 


Now we need to assign this IP address to CONFIG_EXAMPLE_IPV4_ADDR macro 


This can be done by idf.py menuconfig , in the same way, we assigned wifi SSID and Password values using the menuconfig  



After assigning the server’s IP address and Port , program esp32 . 

After getting connected to wifi , esp32 will connect to the server [host machine] and the server and client will communicate with each other 



Here on the left side ESP32 log is printed and on the right side the python server’s log 

---------------------------------------------------------------------------------------------------------------------------------------------

Reference 

http://wiki.treck.com/Introduction_to_BSD_Sockets



Categories

Pages

Firmware Engineer

My photo
Works on Firmware, Embedded Linux, Smart Metering, RTOS, IoT backend

Contact Form

Name

Email *

Message *

Copyrighted by Hassin. Powered by Blogger.