nodemcu-firmware/components/modules/mqtt.c

697 lines
28 KiB
C

// Module for interfacing with an MQTT broker
#include "esp_log.h"
#include "lauxlib.h"
#include "lmem.h"
#include "lnodeaux.h"
#include "module.h"
#include "platform.h"
#include "task/task.h"
#include <string.h>
#include "mqtt_client.h"
#define MQTT_MAX_HOST_LEN 64
#define MQTT_MAX_CLIENT_LEN 32
#define MQTT_MAX_USERNAME_LEN 32
#define MQTT_MAX_PASSWORD_LEN 65
#define MQTT_MAX_LWT_TOPIC 32
#define MQTT_MAX_LWT_MSG 128
#define MQTT_METATABLE "mqtt.mt"
#define TAG "MQTT"
// mqtt_context struct contains information to wrap a esp_mqtt client in lua
typedef struct {
esp_mqtt_client_handle_t client; // handle to mqtt client
char* client_id; // mqtt client ID
char* username; // mqtt username
char* password; // mqtt password
char* lwt_topic; // mqtt last will/testament topic
char* lwt_msg; // mqtt last will message
int lwt_qos; // mqtt LWT qos level
int lwt_retain; // mqtt LWT retain flag
int keepalive; // keepalive ping period in seconds
int disable_clean_session; // Whether to not clean the session on reconnect
union {
struct {
lua_ref_t on_connect_cb; // maps to "connect" event
lua_ref_t on_message_cb; // maps to "message" event
lua_ref_t on_offline_cb; // maps to "offline" event
lua_ref_t connected_ok_cb;
lua_ref_t connected_nok_cb;
lua_ref_t published_ok_cb;
lua_ref_t subscribed_ok_cb;
lua_ref_t unsubscribed_ok_cb;
lua_ref_t self;
lua_ref_t cert_pem;
lua_ref_t client_cert_pem;
lua_ref_t client_key_pem;
};
lua_ref_t lua_refs[12];
};
} mqtt_context_t;
typedef struct {
mqtt_context_t *ctx;
esp_mqtt_event_t event;
} event_info_t;
// event_handler_t is the function signature for all events
typedef void (*event_handler_t)(lua_State* L, mqtt_context_t* mqtt_context, esp_mqtt_event_handle_t event);
// eventnames contains a list of the events that can be set in lua
// with client:on(eventName, function)
// The order is important, as they map directly to callbacks
// in the union/struct above
const char* const eventnames[] = {"connect", "message", "offline", NULL};
// nodemcu task handlers for receiving events
task_handle_t event_handler_task_id = 0;
// event_clone makes a copy of the mqtt event received so we can pass it on
// and the mqtt library can discard it.
static event_info_t *event_clone(esp_mqtt_event_handle_t ev, mqtt_context_t *ctx) {
// allocate memory for the copy
event_info_t *clone = (event_info_t *)malloc(sizeof(event_info_t));
ESP_LOGD(TAG, "event_clone(): event %p, event id %d, msg %d", ev, ev->event_id, ev->msg_id);
clone->ctx = ctx;
esp_mqtt_event_handle_t ev1 = &clone->event;
// make a shallow copy:
*ev1 = *ev;
// if the event carries data, make also a copy of it:
if (ev->data != NULL) {
if (ev->data_len > 0) {
ev1->data = malloc(ev->data_len + 1); // null-terminate the data, useful for debugging
memcpy(ev1->data, ev->data, ev->data_len);
ev1->data[ev1->data_len] = '\0';
ESP_LOGD(TAG, "event_clone():malloc: event %p, msg %d, data %p, num %d", ev1, ev1->msg_id, ev1->data, ev1->data_len);
} else {
ev1->data = NULL;
}
}
// if the event carries a topic, make also a copy of it:
if (ev->topic != NULL) {
if (ev->topic_len > 0) {
ev1->topic = malloc(ev->topic_len + 1); // null-terminate the data, useful for debugging
memcpy(ev1->topic, ev->topic, ev->topic_len);
ev1->topic[ev1->topic_len] = '\0';
ESP_LOGD(TAG, "event_clone():malloc: event %p, msg %d, topic %p, num %d", ev1, ev1->msg_id, ev1->topic, ev1->topic_len);
} else {
ev1->topic = NULL;
}
}
return clone;
}
// event_free deallocates all the memory associated with a cloned event
static void event_free(event_info_t *clone) {
esp_mqtt_event_handle_t ev = &clone->event;
if (ev->data != NULL) {
ESP_LOGD(TAG, "event_free():free: event %p, msg %d, data %p", ev, ev->msg_id, ev->data);
free(ev->data);
}
if (ev->topic != NULL) {
ESP_LOGD(TAG, "event_free():free: event %p, msg %d, topic %p", ev, ev->msg_id, ev->topic);
free(ev->topic);
}
free(clone);
}
// event_connected is run when the mqtt client connected
static void event_connected(lua_State* L, mqtt_context_t* mqtt_context, esp_mqtt_event_handle_t event) {
// if the user set a one-shot connected callback, execute it:
if (luaX_valid_ref(mqtt_context->connected_ok_cb)) {
lua_rawgeti(L, LUA_REGISTRYINDEX, mqtt_context->connected_ok_cb); // push the callback function reference to the stack
luaX_push_weak_ref(L, mqtt_context->self); // push a reference to the client (first parameter)
ESP_LOGD(TAG, "CB:connect: calling registered one-shot connect callback");
int res = luaL_pcallx(L, 1, 0); //call the connect callback: function(client)
if (res != 0)
ESP_LOGD(TAG, "CB:connect: Error when calling one-shot connect callback - (%d) %s", res, luaL_checkstring(L, -1));
//after connecting ok, we clear _both_ the one-shot callbacks:
luaX_unset_ref(L, &mqtt_context->connected_ok_cb);
luaX_unset_ref(L, &mqtt_context->connected_nok_cb);
}
// now we check for the standard connect callback registered with 'mqtt:on()'
if (luaX_valid_ref(mqtt_context->on_connect_cb)) {
ESP_LOGD(TAG, "CB:connect: calling registered standard connect callback");
lua_rawgeti(L, LUA_REGISTRYINDEX, mqtt_context->on_connect_cb); // push the callback function reference to the stack
luaX_push_weak_ref(L, mqtt_context->self); // push a reference to the client (first parameter)
int res = luaL_pcallx(L, 1, 0); //call the connect callback: function(client)
if (res != 0)
ESP_LOGD(TAG, "CB:connect: Error when calling connect callback - (%d) %s", res, luaL_checkstring(L, -1));
}
}
// event_disconnected is run after a connection to the MQTT broker breaks.
static void event_disconnected(lua_State* L, mqtt_context_t* mqtt_context, esp_mqtt_event_handle_t event) {
if (mqtt_context->client == NULL) {
ESP_LOGD(TAG, "MQTT Client was NULL on a disconnect event");
}
// destroy the wrapped mqtt_client object
esp_mqtt_client_destroy(mqtt_context->client);
mqtt_context->client = NULL;
// if the user set a one-shot connect error callback, execute it:
if (luaX_valid_ref(mqtt_context->connected_nok_cb)) {
lua_rawgeti(L, LUA_REGISTRYINDEX, mqtt_context->connected_nok_cb); // push the callback function reference to the stack
luaX_push_weak_ref(L, mqtt_context->self); // push a reference to the client (first parameter)
lua_pushinteger(L, -6); // esp sdk mqtt lib does not provide reason codes. Push "-6" to be backward compatible with ESP8266 API
ESP_LOGD(TAG, "CB:disconnect: calling registered one-shot disconnect callback");
int res = luaL_pcallx(L, 2, 0); //call the disconnect callback with 2 parameters: function(client, reason)
if (res != 0)
ESP_LOGD(TAG, "CB:disconnect: Error when calling one-shot disconnect callback - (%d) %s", res, luaL_checkstring(L, -1));
//after connecting ok, we clear _both_ the one-shot callbacks
luaX_unset_ref(L, &mqtt_context->connected_ok_cb);
luaX_unset_ref(L, &mqtt_context->connected_nok_cb);
}
// now we check for the standard offline callback registered with 'mqtt:on()'
if (luaX_valid_ref(mqtt_context->on_offline_cb)) {
ESP_LOGD(TAG, "CB:disconnect: calling registered standard on_offline_cb callback");
lua_rawgeti(L, LUA_REGISTRYINDEX, mqtt_context->on_offline_cb); // push the callback function reference to the stack
luaX_push_weak_ref(L, mqtt_context->self); // push a reference to the client (first parameter)
int res = luaL_pcallx(L, 1, 0); //call the offline callback: function(client)
if (res != 0)
ESP_LOGD(TAG, "CB:disconnect: Error when calling offline callback - (%d) %s", res, luaL_checkstring(L, -1));
}
}
// event_subscribed is called when the last subscribe call is successful
static void event_subscribed(lua_State* L, mqtt_context_t* mqtt_context, esp_mqtt_event_handle_t event) {
if (!luaX_valid_ref(mqtt_context->subscribed_ok_cb)) return;
ESP_LOGD(TAG, "CB:subscribe: calling registered one-shot subscribe callback");
lua_rawgeti(L, LUA_REGISTRYINDEX, mqtt_context->subscribed_ok_cb); // push the function reference on the stack
luaX_push_weak_ref(L, mqtt_context->self); // push the client object on the stack
luaX_unset_ref(L, &mqtt_context->subscribed_ok_cb); // forget the callback since it is one-shot
int res = luaL_pcallx(L, 1, 0); //call the connect callback with one parameter: function(client)
if (res != 0)
ESP_LOGD(TAG, "CB:subscribe: Error when calling one-shot subscribe callback - (%d) %s", res, luaL_checkstring(L, -1));
}
//event_published is called when a publish operation completes
static void event_published(lua_State* L, mqtt_context_t* mqtt_context, esp_mqtt_event_handle_t event) {
if (!luaX_valid_ref(mqtt_context->published_ok_cb)) return;
ESP_LOGD(TAG, "CB:publish: calling registered one-shot publish callback");
lua_rawgeti(L, LUA_REGISTRYINDEX, mqtt_context->published_ok_cb); // push the callback function reference to the stack
luaX_push_weak_ref(L, mqtt_context->self); // push the client reference to the stack
luaX_unset_ref(L, &mqtt_context->published_ok_cb); // forget this callback since it is one-shot
int res = luaL_pcallx(L, 1, 0); //call the connect callback with 1 parameter: function(client)
if (res != 0)
ESP_LOGD(TAG, "CB:publish: Error when calling one-shot publish callback - (%d) %s", res, luaL_checkstring(L, -1));
}
// event_unsubscribed is called when a subscription is successful
static void event_unsubscribed(lua_State* L, mqtt_context_t* mqtt_context, esp_mqtt_event_handle_t event) {
if (!luaX_valid_ref(mqtt_context->unsubscribed_ok_cb)) return;
ESP_LOGD(TAG, "CB:unsubscribe: calling registered one-shot unsubscribe callback");
lua_rawgeti(L, LUA_REGISTRYINDEX, mqtt_context->unsubscribed_ok_cb); // push callback function reference on the stack
luaX_push_weak_ref(L, mqtt_context->self); // push a reference to the client
luaX_unset_ref(L, &mqtt_context->unsubscribed_ok_cb); // forget callback as it is one-shot
int res = luaL_pcallx(L, 1, 0); //call the connect callback with one parameter: function(client)
if (res != 0)
ESP_LOGD(TAG, "CB:unsubscribe: Error when calling one-shot unsubscribe callback - (%d) %s", res, luaL_checkstring(L, -1));
}
//event_data_received is called when data is received on a subscribed topic
static void event_data_received(lua_State* L, mqtt_context_t* mqtt_context, esp_mqtt_event_handle_t event) {
if (!luaX_valid_ref(mqtt_context->on_message_cb)) return;
lua_rawgeti(L, LUA_REGISTRYINDEX, mqtt_context->on_message_cb);
int numArg = 2;
luaX_push_weak_ref(L, mqtt_context->self);
lua_pushlstring(L, event->topic, event->topic_len);
if (event->data != NULL) {
lua_pushlstring(L, event->data, event->data_len);
numArg++;
}
int res = luaL_pcallx(L, numArg, 0); //call the messagecallback
if (res != 0)
ESP_LOGD(TAG, "CB:data: Error when calling message callback - (%d) %s", res, luaL_checkstring(L, -1));
}
// event_task_handler takes a nodemcu task message and dispatches it to the appropriate event_xxx callback above.
static void event_task_handler(task_param_t param, task_prio_t prio) {
// extract the event data out of the task param
event_info_t *info = (event_info_t *)param;
esp_mqtt_event_handle_t event = &info->event;
mqtt_context_t* mqtt_context = info->ctx;
// Check if this event is about an object that is in the process of garbage collection:
if (!luaX_valid_ref(mqtt_context->self)) {
ESP_LOGW(TAG, "caught stray event: %d", event->event_id); // this can happen if the userdata object is dereferenced while attempting to connect
goto task_handler_end; // free resources and abort
}
lua_State* L = lua_getstate(); //returns main Lua state
if (L == NULL) {
goto task_handler_end; // free resources and abort
}
ESP_LOGD(TAG, "event_task_handler: event_id: %d state %p, settings %p, stack top %d", event->event_id, L, mqtt_context, lua_gettop(L));
event_handler_t eventhandler = NULL;
switch (event->event_id) {
case MQTT_EVENT_DATA:
eventhandler = event_data_received;
break;
case MQTT_EVENT_CONNECTED:
eventhandler = event_connected;
break;
case MQTT_EVENT_DISCONNECTED:
eventhandler = event_disconnected;
break;
case MQTT_EVENT_SUBSCRIBED:
eventhandler = event_subscribed;
break;
case MQTT_EVENT_UNSUBSCRIBED:
eventhandler = event_unsubscribed;
break;
case MQTT_EVENT_PUBLISHED:
eventhandler = event_published;
break;
default:
goto task_handler_end; // free resources and abort
}
int top = lua_gettop(L); // save the stack status to restore it later
lua_checkstack(L, 5); // make sure there are at least 5 slots available
// pin our object by putting a reference on the stack,
// so it can't be garbage collected during user callback execution.
luaX_push_weak_ref(L, mqtt_context->self);
eventhandler(L, mqtt_context, event);
lua_settop(L, top); // leave the stack as it was
task_handler_end:
event_free(info); // free the cloned event info
}
// mqtt_event_handler receives all events from the esp mqtt library and converts them
// to a task message
static void mqtt_event_handler(void *handler_arg, esp_event_base_t base, int32_t event_id, void *event_data) {
(void)base;
(void)event_id;
event_info_t *clone = event_clone(event_data, handler_arg);
if (!task_post_medium(event_handler_task_id, (task_param_t)clone))
event_free(clone);
}
// Lua: on()
// mqtt_on allows to set the callback associated to mqtt events
static int mqtt_on(lua_State* L) {
if (!lua_isfunction(L, 3)) //check whether we are passed a callback function
return 0;
int event = luaL_checkoption(L, 2, "message", eventnames); // map passed event name to an index in the eventnames array
mqtt_context_t* mqtt_context = (mqtt_context_t*)luaL_checkudata(L, 1, MQTT_METATABLE); //retrieve the mqtt_context
luaX_set_ref(L, 3, &mqtt_context->lua_refs[event]); // set the callback reference
return 0;
}
// Lua: mqtt:connect(host[, port[, secure[, autoreconnect]]][, function(client)[, function(client, reason)]])
// mqtt_connect starts a connection with the mqtt broker
static int mqtt_connect(lua_State* L) {
mqtt_context_t* mqtt_context = (mqtt_context_t*)luaL_checkudata(L, 1, MQTT_METATABLE); //retrieve the mqtt context
if (mqtt_context->client) { // destroy existing client. This disconnects an existing connection using this object
esp_mqtt_client_destroy(mqtt_context->client);
mqtt_context->client = NULL;
}
// initialize a mqtt config structure set to zero
esp_mqtt_client_config_t config;
memset(&config, 0, sizeof(esp_mqtt_client_config_t));
// process function parameters populating the mqtt config structure
const char *host = luaL_checkstring(L, 2);
bool is_mqtt_uri = strncmp(host, "mqtt://", 7) == 0;
bool is_mqtts_uri = strncmp(host, "mqtts://", 8) == 0;
bool is_ws_uri = strncmp(host, "ws://", 5) == 0;
bool is_wss_uri = strncmp(host, "wss://", 6) == 0;
if (is_mqtt_uri || is_mqtts_uri || is_ws_uri || is_wss_uri)
config.broker.address.uri = host;
else
config.broker.address.hostname = host;
// set defaults:
int secure = 0;
int reconnect = 0;
int port = 1883;
int n = 3;
const char * cert_pem = NULL;
const char * client_cert_pem = NULL;
const char * client_key_pem = NULL;
size_t cert_pem_len = 0;
size_t client_cert_pem_len = 0;
size_t client_key_pem_len = 0;
if (lua_isnumber(L, n)) {
if (is_mqtt_uri || is_mqtts_uri)
return luaL_error(L, "port arg must be nil if giving full uri");
port = luaL_checknumber(L, n);
n++;
} else if (lua_type(L, n) == LUA_TNIL) {
n++;
}
if (lua_isnumber(L, n)) {
if (is_mqtt_uri || is_mqtts_uri)
return luaL_error(L, "secure on/off determined by uri");
secure = !!luaL_checkinteger(L, n);
n++;
} else if (lua_type(L, n) == LUA_TNIL) {
n++;
} else {
if (lua_istable(L, n)) {
secure = true;
lua_getfield(L, n, "ca_cert");
if ((cert_pem = luaL_optlstring(L, -1, NULL, &cert_pem_len)) != NULL) {
luaX_set_ref(L, -1, &mqtt_context->cert_pem);
}
lua_pop(L, 1);
//
lua_getfield(L, n, "client_cert");
if ((client_cert_pem = luaL_optlstring(L, -1, NULL, &client_cert_pem_len)) != NULL) {
luaX_set_ref(L, -1, &mqtt_context->client_cert_pem);
}
lua_pop(L, 1);
//
lua_getfield(L, n, "client_key");
if ((client_key_pem = luaL_optlstring(L, -1, NULL, &client_key_pem_len)) != NULL) {
luaX_set_ref(L, -1, &mqtt_context->client_key_pem);
}
lua_pop(L, 1);
//
n++;
}
}
if (lua_isnumber(L, n)) {
reconnect = !!luaL_checkinteger(L, n);
n++;
}
if (lua_isfunction(L, n)) {
luaX_set_ref(L, n, &mqtt_context->connected_ok_cb);
n++;
}
if (lua_isfunction(L, n)) {
luaX_set_ref(L, n, &mqtt_context->connected_nok_cb);
n++;
}
ESP_LOGD(TAG, "connect: mqtt_context*: %p", mqtt_context);
if (config.broker.address.uri == NULL)
{
config.broker.address.port = port;
config.broker.address.transport =
secure ? MQTT_TRANSPORT_OVER_SSL : MQTT_TRANSPORT_OVER_TCP;
}
config.broker.verification.certificate = cert_pem;
config.broker.verification.certificate_len = cert_pem_len;
config.credentials.authentication.certificate = client_cert_pem;
config.credentials.authentication.certificate_len = client_cert_pem_len;
config.credentials.authentication.key = client_key_pem;
config.credentials.authentication.key_len = client_key_pem_len;
config.credentials.authentication.password = mqtt_context->password;
config.credentials.client_id = mqtt_context->client_id;
config.credentials.username = mqtt_context->username;
config.network.disable_auto_reconnect = (reconnect == 0);
config.session.disable_clean_session = mqtt_context->disable_clean_session;
config.session.keepalive = mqtt_context->keepalive;
config.session.last_will.msg = mqtt_context->lwt_msg;
config.session.last_will.topic = mqtt_context->lwt_topic;
// create a mqtt client instance
mqtt_context->client = esp_mqtt_client_init(&config);
if (mqtt_context->client == NULL) {
luaL_error(L, "MQTT library failed to start");
return 0;
}
// register the event handler with mqtt_context as the handler arg
esp_mqtt_client_register_event(
mqtt_context->client, ESP_EVENT_ANY_ID, mqtt_event_handler, mqtt_context);
// actually start the mqtt client and connect
esp_err_t err = esp_mqtt_client_start(mqtt_context->client);
if (err != ESP_OK) {
luaL_error(L, "Error starting mqtt client");
}
lua_pushboolean(L, true); // return true (ok)
return 1;
}
// Lua: mqtt:close()
// mqtt_close terminates the current connection
static int mqtt_close(lua_State* L) {
mqtt_context_t* mqtt_context = (mqtt_context_t*)luaL_checkudata(L, 1, MQTT_METATABLE);
if (mqtt_context->client == NULL)
return 0;
ESP_LOGD(TAG, "Closing MQTT client %p", mqtt_context->client);
esp_mqtt_client_destroy(mqtt_context->client);
mqtt_context->client = NULL;
return 0;
}
// Lua: mqtt:lwt(topic, message[, qos[, retain]])
// mqtt_lwt sets last will / testament topic and message
// must be called before connecting
static int mqtt_lwt(lua_State* L) {
mqtt_context_t* mqtt_context = (mqtt_context_t*)luaL_checkudata(L, 1, MQTT_METATABLE);
// free previous topic and messasge, if any.
luaX_free_string(L, mqtt_context->lwt_topic);
luaX_free_string(L, mqtt_context->lwt_msg);
// save a copy of topic and message to pass to the client
// when connecting
mqtt_context->lwt_topic = luaX_alloc_string(L, 2, MQTT_MAX_LWT_TOPIC);
mqtt_context->lwt_msg = luaX_alloc_string(L, 3, MQTT_MAX_LWT_MSG);
//process optional parameters
int n = 4;
if (lua_isnumber(L, n)) {
mqtt_context->lwt_qos = (int)lua_tonumber(L, n);
n++;
}
if (lua_isnumber(L, n)) {
mqtt_context->lwt_retain = (int)lua_tonumber(L, n);
n++;
}
ESP_LOGD(TAG, "Set LWT topic '%s', qos %d, retain %d",
mqtt_context->lwt_topic, mqtt_context->lwt_qos, mqtt_context->lwt_retain);
return 0;
}
//Lua: mqtt:publish(topic, payload, qos, retain[, function(client)])
// returns true on success, false otherwise
// mqtt_publish publishes a message on the given topic
static int mqtt_publish(lua_State* L) {
mqtt_context_t* mqtt_context = (mqtt_context_t*)luaL_checkudata(L, 1, MQTT_METATABLE);
esp_mqtt_client_handle_t client = mqtt_context->client;
if (client == NULL) {
lua_pushboolean(L, false); // return false (error)
return 1;
}
const char* topic = luaL_checkstring(L, 2);
size_t data_size;
const char* data = luaL_checklstring(L, 3, &data_size);
int qos = luaL_checkint(L, 4);
int retain = luaL_checkint(L, 5);
if (lua_isfunction(L, 6)) { // set one-shot on publish callback
luaX_set_ref(L, 6, &mqtt_context->published_ok_cb);
}
ESP_LOGD(TAG, "MQTT publish client %p, topic %s, %d bytes", client, topic, data_size);
int msg_id = esp_mqtt_client_publish(client, topic, data, data_size, qos, retain);
lua_pushboolean(L, msg_id >= 0); // if msg_id < 0 there was an error.
return 1;
}
// Lua: mqtt:subscribe(topic, qos[, function(client)]) OR mqtt:subscribe(table[, function(client)])
// returns true on success, false otherwise
// mqtt_subscribe subscribes to the given topic
static int mqtt_subscribe(lua_State* L) {
mqtt_context_t* mqtt_context = (mqtt_context_t*)luaL_checkudata(L, 1, MQTT_METATABLE);
esp_mqtt_client_handle_t client = mqtt_context->client;
if (client == NULL) {
lua_pushboolean(L, false); // return false (error)
return 1;
}
const char* topic = luaL_checkstring(L, 2);
int qos = luaL_checkint(L, 3);
if (lua_isfunction(L, 4)) // if a callback is provided, set it.
luaX_set_ref(L, 4, &mqtt_context->subscribed_ok_cb);
ESP_LOGD(TAG, "MQTT subscribe client %p, topic %s", client, topic);
esp_err_t err = esp_mqtt_client_subscribe(client, topic, qos);
lua_pushboolean(L, err == ESP_OK);
return 1; // one value returned, true on success, false on error.
}
// Lua: mqtt:unsubscribe(topic[, function(client)])
// TODO: accept also mqtt:unsubscribe(table[, function(client)])
// returns true on success, false otherwise
// mqtt_unsubscribe unsubscribes from the given topic
static int mqtt_unsubscribe(lua_State* L) {
mqtt_context_t* mqtt_context = (mqtt_context_t*)luaL_checkudata(L, 1, MQTT_METATABLE);
esp_mqtt_client_handle_t client = mqtt_context->client;
if (client == NULL) {
lua_pushboolean(L, false); // return false (error)
return 1;
}
const char* topic = luaL_checkstring(L, 2);
if (lua_isfunction(L, 3))
luaX_set_ref(L, 3, &mqtt_context->unsubscribed_ok_cb);
ESP_LOGD(TAG, "MQTT unsubscribe client %p, topic %s", client, topic);
esp_err_t err = esp_mqtt_client_unsubscribe(client, topic);
lua_pushboolean(L, err == ESP_OK);
return 1; // return 1 value: true OK, false error.
}
// mqtt_deleted is called on garbage collection
static int mqtt_delete(lua_State* L) {
mqtt_context_t* mqtt_context = (mqtt_context_t*)luaL_checkudata(L, 1, MQTT_METATABLE);
// forget all callbacks
for (int i = 0; i < sizeof(mqtt_context->lua_refs) / sizeof(lua_ref_t); i++) {
luaX_unset_ref(L, &mqtt_context->lua_refs[i]);
}
// if there is a client active, shut it down.
if (mqtt_context->client != NULL) {
ESP_LOGD(TAG, "stopping MQTT client %p;", mqtt_context);
// destroy the client. This is a blocking call.
// If a connection request was ongoing this will block and
// a disconnect callback could be fired before coming back here.
esp_mqtt_client_destroy(mqtt_context->client);
}
// free all dynamic strings
luaX_free_string(L, mqtt_context->client_id);
luaX_free_string(L, mqtt_context->username);
luaX_free_string(L, mqtt_context->password);
luaX_free_string(L, mqtt_context->lwt_msg);
luaX_free_string(L, mqtt_context->lwt_topic);
ESP_LOGD(TAG, "MQTT client garbage collected");
return 0;
}
// Lua: mqtt.Client(clientid, keepalive[, username, password, cleansession])
// mqtt_new creates a new instance of our mqtt userdata lua object
static int mqtt_new(lua_State* L) {
//create a new lua userdata object and initialize to 0.
mqtt_context_t* mqtt_context = (mqtt_context_t*)lua_newuserdata(L, sizeof(mqtt_context_t));
memset(mqtt_context, 0, sizeof(mqtt_context_t));
// initialize all callbacks to LUA_NOREF, indicating they're unset.
for (int i = 0; i < sizeof(mqtt_context->lua_refs) / sizeof(lua_ref_t); i++) {
mqtt_context->lua_refs[i] = LUA_NOREF;
}
// keep a weak reference to our userdata object so we can pass it as a parameter to user callbacks
lua_pushvalue(L, -1);
mqtt_context->self = luaX_weak_ref(L);
// store the parameters passed:
mqtt_context->client_id = luaX_alloc_string(L, 1, MQTT_MAX_CLIENT_LEN);
ESP_LOGD(TAG, "MQTT client id %s", mqtt_context->client_id);
mqtt_context->keepalive = luaL_checkinteger(L, 2);
int n = 2;
if (lua_isstring(L, 3)) {
mqtt_context->username = luaX_alloc_string(L, 3, MQTT_MAX_USERNAME_LEN);
n++;
}
if (lua_isstring(L, 4)) {
mqtt_context->password = luaX_alloc_string(L, 4, MQTT_MAX_PASSWORD_LEN);
n++;
}
if (lua_isnumber(L, 5)) {
mqtt_context->disable_clean_session = (luaL_checknumber(L, 5) == 0);
n++;
}
luaL_getmetatable(L, MQTT_METATABLE);
lua_setmetatable(L, -2);
if (event_handler_task_id == 0) { // if this is the first time, create nodemcu tasks for every event type
event_handler_task_id = task_get_id(event_task_handler);
}
return 1; //one object returned, the mqtt context wrapped in a lua userdata object
}
// map client methods to functions:
LROT_BEGIN(mqtt_metatable, NULL, LROT_MASK_GC_INDEX)
LROT_FUNCENTRY(__gc, mqtt_delete)
LROT_TABENTRY(__index, mqtt_metatable)
LROT_FUNCENTRY(connect, mqtt_connect)
LROT_FUNCENTRY(close, mqtt_close)
LROT_FUNCENTRY(lwt, mqtt_lwt)
LROT_FUNCENTRY(publish, mqtt_publish)
LROT_FUNCENTRY(subscribe, mqtt_subscribe)
LROT_FUNCENTRY(unsubscribe, mqtt_unsubscribe)
LROT_FUNCENTRY(on, mqtt_on)
LROT_END(mqtt_metatable, NULL, LROT_MASK_GC_INDEX)
// Module function map
LROT_BEGIN(mqtt, NULL, 0)
LROT_FUNCENTRY(Client, mqtt_new)
LROT_END(mqtt, NULL, 0)
int luaopen_mqtt(lua_State* L) {
luaL_rometatable(L, MQTT_METATABLE, LROT_TABLEREF(mqtt_metatable)); // create metatable for mqtt
return 0;
}
NODEMCU_MODULE(MQTT, "mqtt", mqtt, luaopen_mqtt);