A simple MQTT library for the ESP8266
Find a file
A.M. Rowsell cfb041a679
Added Doxyfile. See full log.
Since this is my first "quasi-professional" project that I actually
want others to use, I wanted there to be decent quality documentation
for all the functions and structs that the end user would need to use.
I had tried Doxygen before but never had much luck, mostly because
I didn't bother to put in the effort to read the documentation closely.
So this time around I did, and the output so far looks quite good
and has a lot of detail about the functions, and the two typedef
structs that are key to everything working.

Added code to check if wifi is connected before allowing the TCP
connection to be attempted. Similar code for mqtt_connect, checking
if we have a valid TCP connection to the server. Waiting for
wifi uses a new global timer. These changes affected the indentation
of huge chunks of the code, as they got wrapped in ifs.

We now take advantage of the void *reverse pointer in the espconn
struct to point to the mqtt_session_t that is active. This is a nice
touch that Espressif added, so you can access arbitrary data from
within callbacks. This will allow us to (soon) have user callbacks
to deal with MQTT messages.

Also added license notices to all the source code files.
2018-08-29 02:36:11 -04:00
.gitignore Added Doxyfile. See full log. 2018-08-29 02:36:11 -04:00
Doxyfile Added Doxyfile. See full log. 2018-08-29 02:36:11 -04:00
LICENSE Changed license to Mozilla Public License v2.0 2018-08-18 06:11:39 +00:00
Makefile Added Doxyfile. See full log. 2018-08-29 02:36:11 -04:00
mqtt.c Added Doxyfile. See full log. 2018-08-29 02:36:11 -04:00
mqtt.h Added Doxyfile. See full log. 2018-08-29 02:36:11 -04:00
README.md Added Doxyfile. See full log. 2018-08-29 02:36:11 -04:00
strtoarr.py Added Doxyfile. See full log. 2018-08-29 02:36:11 -04:00
user_config.h Initial commit 2018-08-13 23:34:17 -04:00

MQTT Library for ESP8266 SDK

written by Alexander Rowsell (MrAureliusR)
Released under the terms of the Mozilla Public License v2.0 -- see LICENSE for more detail.

Summary

While working on a series of tutorials for Hackaday, I realized MQTT would be the perfect solution for one of the projects I was presenting. However, I didn't want to use a pre-existing MQTT library - that would be boring, and I wouldn't be able to teach the readers as much. Instead, I started to write one from scratch.

At this point, it's a bit messy. It still needs a lot of work. This documentation will help me keep everything organized and easy-to-use.

API Structures

typedef struct {
  uint8_t ip[4];
  uint32_t port;
  uint32_t localPort;
  uint8_t *client_id;
  uint32_t client_id_len;
  uint8_t *topic_name;
  uint32_t topic_name_len;
  uint8_t qos_level;
  uint8_t *username;
  uint32_t username_len;
  uint8_t *password;
  uint32_t password_len;
  char valid_connection;
  struct espconn *activeConnection;
} mqtt_session_t;

This is the main structure that contains all the information about the connection to the server. The IP address, port, client_id, topic_name, username, and password are all specified here by the end user. localPort is set by the API, and qos_level should be set to 0. *activeConnection will point to the espconn struct that represents the TCP connection to the server. The end-user shouldn't need to touch anything in this struct.

API Functions

LOCAL uint8_t ICACHE_FLASH_ATTR mqtt_send(mqtt_session_t *session, uint8_t *data, uint32_t len, mqtt_message_type msgType);

This is the only function that needs to be called by the user. You pass the mqtt_session_t that you have created with all the fields filled in, you pass *data if you are publishing (NULL otherwise), len which is the length of *data (again, only if publishing, 0 otherwise), and then the message type you want to send, which is one of these enums:

typedef enum mqtt_message_enum
{
  MQTT_MSG_TYPE_CONNECT = 1,
  MQTT_MSG_TYPE_CONNACK = 2,
  MQTT_MSG_TYPE_PUBLISH = 3,
  MQTT_MSG_TYPE_PUBACK = 4,
  MQTT_MSG_TYPE_PUBREC = 5,
  MQTT_MSG_TYPE_PUBREL = 6,
  MQTT_MSG_TYPE_PUBCOMP = 7,
  MQTT_MSG_TYPE_SUBSCRIBE = 8,
  MQTT_MSG_TYPE_SUBACK = 9,
  MQTT_MSG_TYPE_UNSUBSCRIBE = 10,
  MQTT_MSG_TYPE_UNSUBACK = 11,
  MQTT_MSG_TYPE_PINGREQ = 12,
  MQTT_MSG_TYPE_PINGRESP = 13,
  MQTT_MSG_TYPE_DISCONNECT = 14
} mqtt_message_type;

Obviously, the user will only be choosing CONNECT, PUBLISH, SUBSCRIBE, UNSUBSCRIBE, PINGREQ and DISCONNECT. The rest are messages from the broker back to the client. If you send an incorrect message type, nothing will happen, and no TCP packets will be sent to the broker.

Due to a strange bug with the way pointers to strings are passed between functions, all the fields in the mqtt_session_t need to be allocated and have their contents memcpy'd. They also need to be in hex format. This is a pain in the butt, so there is an included Python 3 script to create the arrays for you. Running it is as simple as the following:

$ chmod +x strtoarr.py
$ ./strtoarr.py MyUsername username
static const char username[10] = { 0x4d, 0x79, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65 };
static const uint8_t username_len = 10;

Then copy and paste the result into your code. Allocate the correct amount of memory, then copy and pass the pointer into the struct:

static const char username[10] = { 0x4d, 0x79, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65 };
static const uint8_t username_len = 10;
pGlobalSession->username_len = username_len;
pGlobalSession->username = os_zalloc(sizeof(uint8_t) * pGlobalSession->username_len);
os_memcpy(pGlobalSession->username, testUser, pGlobalSession->username_len); 

Slightly more complex than just passing the address of a char array, but until I can figure out why the ESP8266 butchers strings that are passed by reference, this is how it has to be.

A very basic, fully working example that doesn't do anything but establish a connection might look like this:

LOCAL mqtt_session_t globalSession;
LOCAL mqtt_session_t *pGlobalSession = &globalSession;
char ssid[32] = "WirelessAP";
char passkey[64] = "myAPpassword";
struct station_config stationConf;
gpio_init(); // init gpio so we can use the LED
wifi_status_led_install(0, PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0); // set GPIO0 as status LED
stationConf.bssid_set = 0;
os_memcpy(&stationConf.ssid, ssid, 32); // copy the ssid and passkey into the station_config struct
os_memcpy(&stationConf.password, passkey, 64);
wifi_set_opmode_current(0x01); //station mode
wifi_station_set_config_current(&stationConf); // tell it about our config, this auto-connects us as well

// testUser = MrAureliusR
static const char testUser[11] = { 0x4d, 0x72, 0x41, 0x75, 0x72, 0x65, 0x6c, 0x69, 0x75, 0x73, 0x52 };
static const uint8_t testUser_len = 11;
// testPass = test
static const char testPass[4] = { 0x74, 0x65, 0x73, 0x74 };
static const uint8_t testPass_len = 4;
// testTopic = test
static const char testTopic[4] = { 0x74, 0x65, 0x73, 0x74 };
static const uint8_t testTopic_len = 4;
// clientID = FRZN0 (this can be anything you like, under 21 characters)
static const char clientID[5] = { 0x46, 0x52, 0x5a, 0x4e, 0x30 };
static const uint8_t clientID_len = 5;
pGlobalSession->port = 1883; // mqtt port
static const char esp_tcp_server_ip[4] = {8, 8, 8, 8}; // the IP address of the server you want to connect to

// copy all these into their proper locations
os_memcpy(pGlobalSession->ip, esp_tcp_server_ip, 4);
pGlobalSession->username_len = testUser_len;
pGlobalSession->username = os_zalloc(sizeof(uint8_t) * pGlobalSession->username_len);
os_memcpy(pGlobalSession->username, testUser, pGlobalSession->username_len);
pGlobalSession->password_len = testPass_len;
pGlobalSession->password = os_zalloc(sizeof(uint8_t) * pGlobalSession->password_len);
os_memcpy(pGlobalSession->password, testPass, pGlobalSession->password_len);
pGlobalSession->topic_name_len = testTopic_len;
pGlobalSession->topic_name = os_zalloc(sizeof(uint8_t) * pGlobalSession->topic_name_len);
os_memcpy(pGlobalSession->topic_name, testTopic, pGlobalSession->topic_name_len);
pGlobalSession->client_id_len = clientID_len;
pGlobalSession->client_id = os_zalloc(sizeof(uint8_t) * pGlobalSession->client_id_len);
os_memcpy(pGlobalSession->client_id, clientID, pGlobalSession->client_id_len);

After this, you can set up a task or timer to actually call mqtt_send with the pointer to mqtt_session_t and the MQTT_MSG_TYPE_CONNECT. A connect function might look like this:

LOCAL void ICACHE_FLASH_ATTR con(void *arg) {
  mqtt_session_t *pSession = (mqtt_session_t *)arg;
  tcp_connect(pSession); // establish tcp connection
  os_delay_us(60000); 
  // a future version of the API won't require a delay here
  // if you are having problems, try adding more os_delay_us(60000); here. Sometimes the TCP
  // connection takes longer to start
  mqtt_send(pSession, NULL, 0, MQTT_MSG_TYPE_CONNECT);
}