From f6768258726f80a6acb1dcf27b943fdd54cadedd Mon Sep 17 00:00:00 2001 From: funshine Date: Fri, 23 Jan 2015 11:48:05 +0800 Subject: [PATCH] merge mqtt branch to master and build pre_build bin --- README.md | 46 +- app/Makefile | 2 + app/include/user_config.h | 3 +- app/modules/Makefile | 1 + app/modules/auxmods.h | 3 + app/modules/modules.h | 9 + app/modules/mqtt.c | 1051 +++++++++++++++++ app/mqtt/Makefile | 44 + app/mqtt/mqtt_msg.c | 457 +++++++ app/mqtt/mqtt_msg.h | 129 ++ examples/fragment.lua | 6 + ...odemcu_latest.bin => nodemcu_20150118.bin} | Bin pre_build/latest/nodemcu_latest.bin | Bin 376332 -> 382688 bytes tools/.gitattributes | 11 + 14 files changed, 1758 insertions(+), 4 deletions(-) create mode 100644 app/modules/mqtt.c create mode 100644 app/mqtt/Makefile create mode 100644 app/mqtt/mqtt_msg.c create mode 100644 app/mqtt/mqtt_msg.h rename pre_build/0.9.5/{nodemcu_latest.bin => nodemcu_20150118.bin} (100%) create mode 100644 tools/.gitattributes diff --git a/README.md b/README.md index 87a4ce80..1bb17511 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Flash tool for NodeMCU [nodemcu-flasher](https://github.com/nodemcu/nodemcu-flas wiki: [nodemcu wiki](https://github.com/nodemcu/nodemcu-firmware/wiki)
home: [nodemcu.com](http://www.nodemcu.com)
-bbs: [中文论坛Chinese bbs](http://bbs.nodemcu.com)
-Tencent QQ group QQ群: 309957875
+bbs: [Chinese bbs](http://bbs.nodemcu.com)
+Tencent QQ group: 309957875
# Summary - Easy to access wireless router @@ -26,6 +26,10 @@ Tencent QQ group QQ群: 309957875
- add coap module # Change log +2015-01-23
+merge mqtt branch to master.
+build pre_build bin. + 2015-01-18
merge mqtt module to [new branch mqtt](https://github.com/nodemcu/nodemcu-firmware/tree/mqtt) from [https://github.com/tuanpmt/esp_mqtt](https://github.com/tuanpmt/esp_mqtt).
merge spi module from iabdalkader:spi.
@@ -40,7 +44,7 @@ fix file.read() api, take 0xFF as a regular byte, not EOF.
pre_build/latest/nodemcu_512k_latest.bin is removed. use pre_build/latest/nodemcu_latest.bin instead. [more change log](https://github.com/nodemcu/nodemcu-firmware/wiki/nodemcu_api_en#change_log)
-[更多变更日志](https://github.com/nodemcu/nodemcu-firmware/wiki/nodemcu_api_cn#change_log) +[more change_log cn](https://github.com/nodemcu/nodemcu-firmware/wiki/nodemcu_api_cn#change_log) ##GPIO NEW TABLE ( Build 20141219 and later) @@ -186,6 +190,42 @@ baudrate:9600 .."Connection: keep-alive\r\nAccept: */*\r\n\r\n") ``` +####Connect to MQTT Broker + +```lua +-- init mqtt client with keepalive timer 120sec +m = mqtt.Client("clientid", 120, "user", "password") + +-- setup Last Will and Testament (optional) +-- Broker will publish a message with qos = 0, retain = 0, data = "offline" +-- to topic "/lwt" if client don't send keepalive packet +m:lwt("/lwt", "offline", 0, 0) + +m:on("connect", function(con) print ("connected") end) +m:on("offline", function(con) print ("offline") end) + +-- on publish message receive event +m:on("message", function(conn, topic, data) + print(topic .. ":" ) + if data ~= nil then + print(data) + end +end) + +-- for secure: m:connect("192.168.11.118", 1880, 1) +m:connect("192.168.11.118", 1880, 0, function(conn) print("connected") end) + +-- subscribe topic with qos = 0 +m:subscribe("/topic",0, function(conn) print("subscribe success") end) + +-- publish a message with data = hello, QoS = 0, retain = 0 +m:publish("/topic","hello",0,0, function(conn) print("sent") end) + +m:close(); +-- you can call m:connect again + +``` + ####Or a simple http server ```lua diff --git a/app/Makefile b/app/Makefile index 753595d6..584d4545 100644 --- a/app/Makefile +++ b/app/Makefile @@ -31,6 +31,7 @@ SUBDIRS= \ platform \ libc \ lua \ + mqtt \ smart \ wofs \ modules \ @@ -77,6 +78,7 @@ COMPONENTS_eagle.app.v6 = \ platform/libplatform.a \ libc/liblibc.a \ lua/liblua.a \ + mqtt/mqtt.a \ smart/smart.a \ wofs/wofs.a \ spiffs/spiffs.a \ diff --git a/app/include/user_config.h b/app/include/user_config.h index 6211f09a..1af91c0a 100644 --- a/app/include/user_config.h +++ b/app/include/user_config.h @@ -7,7 +7,7 @@ #define NODE_VERSION_INTERNAL 0U #define NODE_VERSION "NodeMCU 0.9.5" -#define BUILD_DATE "build 20150118" +#define BUILD_DATE "build 20150123" // #define FLASH_512K // #define FLASH_1M @@ -61,6 +61,7 @@ #define LUA_USE_MODULES_UART #define LUA_USE_MODULES_OW #define LUA_USE_MODULES_BIT +#define LUA_USE_MODULES_MQTT #endif /* LUA_USE_MODULES */ #define LUA_NUMBER_INTEGRAL diff --git a/app/modules/Makefile b/app/modules/Makefile index 1d9f19d9..fb170792 100644 --- a/app/modules/Makefile +++ b/app/modules/Makefile @@ -39,6 +39,7 @@ endif INCLUDES := $(INCLUDES) -I $(PDIR)include INCLUDES += -I ./ INCLUDES += -I ../libc +INCLUDES += -I ../mqtt INCLUDES += -I ../lua INCLUDES += -I ../platform INCLUDES += -I ../wofs diff --git a/app/modules/auxmods.h b/app/modules/auxmods.h index d8166643..6ab6ef46 100644 --- a/app/modules/auxmods.h +++ b/app/modules/auxmods.h @@ -61,6 +61,9 @@ LUALIB_API int ( luaopen_i2c )( lua_State *L ); #define AUXLIB_WIFI "wifi" LUALIB_API int ( luaopen_wifi )( lua_State *L ); +#define AUXLIB_MQTT "mqtt" +LUALIB_API int ( luaopen_mqtt )( lua_State *L ); + #define AUXLIB_NODE "node" LUALIB_API int ( luaopen_node )( lua_State *L ); diff --git a/app/modules/modules.h b/app/modules/modules.h index eda8e16d..f28775d5 100644 --- a/app/modules/modules.h +++ b/app/modules/modules.h @@ -37,6 +37,14 @@ #define ROM_MODULES_NET #endif +#if defined(LUA_USE_MODULES_MQTT) +#define MODULES_MQTT "mqtt" +#define ROM_MODULES_MQTT \ + _ROM(MODULES_MQTT, luaopen_mqtt, mqtt_map) +#else +#define ROM_MODULES_MQTT +#endif + #if defined(LUA_USE_MODULES_I2C) #define MODULES_I2C "i2c" #define ROM_MODULES_I2C \ @@ -113,6 +121,7 @@ ROM_MODULES_GPIO \ ROM_MODULES_PWM \ ROM_MODULES_WIFI \ + ROM_MODULES_MQTT \ ROM_MODULES_I2C \ ROM_MODULES_SPI \ ROM_MODULES_TMR \ diff --git a/app/modules/mqtt.c b/app/modules/mqtt.c new file mode 100644 index 00000000..332a9e87 --- /dev/null +++ b/app/modules/mqtt.c @@ -0,0 +1,1051 @@ +// Module for mqtt + +//#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" +#include "platform.h" +#include "auxmods.h" +#include "lrotable.h" + +#include "c_string.h" +#include "c_stdlib.h" + +#include "c_types.h" +#include "mem.h" +#include "espconn.h" + +#include "mqtt_msg.h" + +static lua_State *gL = NULL; + +#define MQTT_BUF_SIZE 1024 +#define MQTT_DEFAULT_KEEPALIVE 60 +#define MQTT_MAX_CLIENT_LEN 64 +#define MQTT_MAX_USER_LEN 64 +#define MQTT_MAX_PASS_LEN 64 +#define MQTT_SEND_TIMEOUT 5 + +typedef enum { + MQTT_INIT, + MQTT_CONNECT_SEND, + MQTT_CONNECT_SENDING, + MQTT_DATA +} tConnState; + +typedef struct mqtt_event_data_t +{ + uint8_t type; + const char* topic; + const char* data; + uint16_t topic_length; + uint16_t data_length; + uint16_t data_offset; +} mqtt_event_data_t; + +typedef struct mqtt_state_t +{ + uint16_t port; + int auto_reconnect; + mqtt_connect_info_t* connect_info; + uint8_t* in_buffer; + uint8_t* out_buffer; + int in_buffer_length; + int out_buffer_length; + uint16_t message_length; + uint16_t message_length_read; + mqtt_message_t* outbound_message; + mqtt_connection_t mqtt_connection; + + uint16_t pending_msg_id; + int pending_msg_type; + int pending_publish_qos; +} mqtt_state_t; + +typedef struct lmqtt_userdata +{ + struct espconn *pesp_conn; + int self_ref; + int cb_connect_ref; + int cb_disconnect_ref; + int cb_message_ref; + int cb_suback_ref; + int cb_puback_ref; + mqtt_state_t mqtt_state; + mqtt_connect_info_t connect_info; + uint32_t keep_alive_tick; + uint32_t send_timeout; + uint8_t secure; + uint8_t connected; + ETSTimer mqttTimer; + tConnState connState; +}lmqtt_userdata; + +static void mqtt_socket_disconnected(void *arg) // tcp only +{ + NODE_DBG("mqtt_socket_disconnected is called.\n"); + struct espconn *pesp_conn = arg; + if(pesp_conn == NULL) + return; + lmqtt_userdata *mud = (lmqtt_userdata *)pesp_conn->reverse; + if(mud == NULL) + return; + if(mud->cb_disconnect_ref != LUA_NOREF && mud->self_ref != LUA_NOREF) + { + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->cb_disconnect_ref); + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->self_ref); // pass the userdata(client) to callback func in lua + lua_call(gL, 1, 0); + } + mud->connected = 0; + os_timer_disarm(&mud->mqttTimer); + + if(pesp_conn->proto.tcp) + c_free(pesp_conn->proto.tcp); + pesp_conn->proto.tcp = NULL; + if(mud->pesp_conn) + c_free(mud->pesp_conn); + mud->pesp_conn = NULL; // espconn is already disconnected + lua_gc(gL, LUA_GCSTOP, 0); + if(mud->self_ref != LUA_NOREF){ + luaL_unref(gL, LUA_REGISTRYINDEX, mud->self_ref); + mud->self_ref = LUA_NOREF; // unref this, and the mqtt.socket userdata will delete it self + } + lua_gc(gL, LUA_GCRESTART, 0); +} + +static void mqtt_socket_reconnected(void *arg, sint8_t err) +{ + NODE_DBG("mqtt_socket_reconnected is called.\n"); + mqtt_socket_disconnected(arg); +} + +static void deliver_publish(lmqtt_userdata * mud, uint8_t* message, int length) +{ + const char comma[] = ","; + mqtt_event_data_t event_data; + + event_data.topic_length = length; + event_data.topic = mqtt_get_publish_topic(message, &event_data.topic_length); + + event_data.data_length = length; + event_data.data = mqtt_get_publish_data(message, &event_data.data_length); + + if(mud->cb_message_ref == LUA_NOREF) + return; + if(mud->self_ref == LUA_NOREF) + return; + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->cb_message_ref); + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->self_ref); // pass the userdata to callback func in lua + // expose_array(gL, pdata, len); + // *(pdata+len) = 0; + // NODE_DBG(pdata); + // NODE_DBG("\n"); + lua_pushlstring(gL, event_data.topic, event_data.topic_length); + if(event_data.data_length > 0){ + lua_pushlstring(gL, event_data.data, event_data.data_length); + lua_call(gL, 3, 0); + } else { + lua_call(gL, 2, 0); + } +} + +static void mqtt_socket_received(void *arg, char *pdata, unsigned short len) +{ + NODE_DBG("mqtt_socket_received is called.\n"); + + uint8_t msg_type; + uint8_t msg_qos; + uint16_t msg_id; + + struct espconn *pesp_conn = arg; + if(pesp_conn == NULL) + return; + lmqtt_userdata *mud = (lmqtt_userdata *)pesp_conn->reverse; + if(mud == NULL) + return; + +READPACKET: + if(len > MQTT_BUF_SIZE && len <= 0) + return; + + c_memcpy(mud->mqtt_state.in_buffer, pdata, len); + mud->mqtt_state.outbound_message = NULL; + switch(mud->connState){ + case MQTT_CONNECT_SENDING: + if(mqtt_get_type(mud->mqtt_state.in_buffer) != MQTT_MSG_TYPE_CONNACK){ + NODE_DBG("MQTT: Invalid packet\r\n"); + mud->connState = MQTT_INIT; + if(mud->secure){ + espconn_secure_disconnect(pesp_conn); + } + else { + espconn_disconnect(pesp_conn); + } + } else { + mud->connState = MQTT_DATA; + NODE_DBG("MQTT: Connected\r\n"); + if(mud->cb_connect_ref == LUA_NOREF) + return; + if(mud->self_ref == LUA_NOREF) + return; + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->cb_connect_ref); + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->self_ref); // pass the userdata(client) to callback func in lua + lua_call(gL, 1, 0); + return; + } + break; + + case MQTT_DATA: + mud->mqtt_state.message_length_read = len; + mud->mqtt_state.message_length = mqtt_get_total_length(mud->mqtt_state.in_buffer, mud->mqtt_state.message_length_read); + msg_type = mqtt_get_type(mud->mqtt_state.in_buffer); + msg_qos = mqtt_get_qos(mud->mqtt_state.in_buffer); + msg_id = mqtt_get_id(mud->mqtt_state.in_buffer, mud->mqtt_state.in_buffer_length); + + NODE_DBG("MQTT_DATA: type: %d, qos: %d, msg_id: %d, pending_id: %d\r\n", + msg_type, + msg_qos, + msg_id, + mud->mqtt_state.pending_msg_id); + switch(msg_type) + { + case MQTT_MSG_TYPE_SUBACK: + if(mud->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_SUBSCRIBE && mud->mqtt_state.pending_msg_id == msg_id) + NODE_DBG("MQTT: Subscribe successful\r\n"); + if (mud->cb_suback_ref == LUA_NOREF) + break; + if (mud->self_ref == LUA_NOREF) + break; + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->cb_suback_ref); + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->self_ref); + lua_call(gL, 1, 0); + break; + case MQTT_MSG_TYPE_UNSUBACK: + if(mud->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE && mud->mqtt_state.pending_msg_id == msg_id) + NODE_DBG("MQTT: UnSubscribe successful\r\n"); + break; + case MQTT_MSG_TYPE_PUBLISH: + if(msg_qos == 1) + mud->mqtt_state.outbound_message = mqtt_msg_puback(&mud->mqtt_state.mqtt_connection, msg_id); + else if(msg_qos == 2) + mud->mqtt_state.outbound_message = mqtt_msg_pubrec(&mud->mqtt_state.mqtt_connection, msg_id); + + deliver_publish(mud, mud->mqtt_state.in_buffer, mud->mqtt_state.message_length_read); + break; + case MQTT_MSG_TYPE_PUBACK: + if(mud->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && mud->mqtt_state.pending_msg_id == msg_id){ + NODE_DBG("MQTT: Publish with QoS = 1 successful\r\n"); + if(mud->cb_puback_ref == LUA_NOREF) + break; + if(mud->self_ref == LUA_NOREF) + break; + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->cb_puback_ref); + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->self_ref); // pass the userdata to callback func in lua + lua_call(gL, 1, 0); + } + + break; + case MQTT_MSG_TYPE_PUBREC: + mud->mqtt_state.outbound_message = mqtt_msg_pubrel(&mud->mqtt_state.mqtt_connection, msg_id); + NODE_DBG("MQTT: Response PUBREL\r\n"); + break; + case MQTT_MSG_TYPE_PUBREL: + mud->mqtt_state.outbound_message = mqtt_msg_pubcomp(&mud->mqtt_state.mqtt_connection, msg_id); + NODE_DBG("MQTT: Response PUBCOMP\r\n"); + break; + case MQTT_MSG_TYPE_PUBCOMP: + if(mud->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && mud->mqtt_state.pending_msg_id == msg_id){ + NODE_DBG("MQTT: Publish with QoS = 2 successful\r\n"); + if(mud->cb_puback_ref == LUA_NOREF) + break; + if(mud->self_ref == LUA_NOREF) + break; + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->cb_puback_ref); + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->self_ref); // pass the userdata to callback func in lua + lua_call(gL, 1, 0); + } + break; + case MQTT_MSG_TYPE_PINGREQ: + mud->mqtt_state.outbound_message = mqtt_msg_pingresp(&mud->mqtt_state.mqtt_connection); + break; + case MQTT_MSG_TYPE_PINGRESP: + // Ignore + break; + } + // NOTE: this is done down here and not in the switch case above + // because the PSOCK_READBUF_LEN() won't work inside a switch + // statement due to the way protothreads resume. + if(msg_type == MQTT_MSG_TYPE_PUBLISH) + { + + len = mud->mqtt_state.message_length_read; + + if(mud->mqtt_state.message_length < mud->mqtt_state.message_length_read) + { + len -= mud->mqtt_state.message_length; + pdata += mud->mqtt_state.message_length; + + NODE_DBG("Get another published message\r\n"); + goto READPACKET; + } + } + break; + } + + if(mud->mqtt_state.outbound_message != NULL){ + if(mud->secure) + espconn_secure_sent(pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + else + espconn_sent(pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + mud->mqtt_state.outbound_message = NULL; + } + return; +} + +static void mqtt_socket_sent(void *arg) +{ + // NODE_DBG("mqtt_socket_sent is called.\n"); + struct espconn *pesp_conn = arg; + if(pesp_conn == NULL) + return; + lmqtt_userdata *mud = (lmqtt_userdata *)pesp_conn->reverse; + if(mud == NULL) + return; + if(!mud->connected) + return; + // call mqtt_sent() + mud->send_timeout = 0; + if(mud->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && mud->mqtt_state.pending_publish_qos == 0) { + if(mud->cb_puback_ref == LUA_NOREF) + return; + if(mud->self_ref == LUA_NOREF) + return; + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->cb_puback_ref); + lua_rawgeti(gL, LUA_REGISTRYINDEX, mud->self_ref); // pass the userdata to callback func in lua + lua_call(gL, 1, 0); + } +} + +static int mqtt_socket_client( lua_State* L ); +static void mqtt_socket_connected(void *arg) +{ + NODE_DBG("mqtt_socket_connected is called.\n"); + struct espconn *pesp_conn = arg; + if(pesp_conn == NULL) + return; + lmqtt_userdata *mud = (lmqtt_userdata *)pesp_conn->reverse; + if(mud == NULL) + return; + mud->connected = true; + espconn_regist_recvcb(pesp_conn, mqtt_socket_received); + espconn_regist_sentcb(pesp_conn, mqtt_socket_sent); + espconn_regist_disconcb(pesp_conn, mqtt_socket_disconnected); + + // call mqtt_connect() to start a mqtt connect stage. + mqtt_msg_init(&mud->mqtt_state.mqtt_connection, mud->mqtt_state.out_buffer, mud->mqtt_state.out_buffer_length); + mud->mqtt_state.outbound_message = mqtt_msg_connect(&mud->mqtt_state.mqtt_connection, mud->mqtt_state.connect_info); + NODE_DBG("Send MQTT connection infomation, data len: %d, d[0]=%d \r\n", mud->mqtt_state.outbound_message->length, mud->mqtt_state.outbound_message->data[0]); + if(mud->secure){ + espconn_secure_sent(pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + } + else + { + espconn_sent(pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + } + mud->mqtt_state.outbound_message = NULL; + mud->connState = MQTT_CONNECT_SENDING; + return; +} + +void mqtt_socket_timer(void *arg) +{ + lmqtt_userdata *mud = (lmqtt_userdata*) arg; + + if(mud->connState == MQTT_DATA){ + mud->keep_alive_tick ++; + if(mud->keep_alive_tick > mud->mqtt_state.connect_info->keepalive){ + mud->mqtt_state.pending_msg_type = MQTT_MSG_TYPE_PINGREQ; + mud->send_timeout = MQTT_SEND_TIMEOUT; + NODE_DBG("\r\nMQTT: Send keepalive packet\r\n"); + mud->mqtt_state.outbound_message = mqtt_msg_pingreq(&mud->mqtt_state.mqtt_connection); + + if(mud->secure) + espconn_secure_sent(mud->pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + else + espconn_sent(mud->pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + mud->keep_alive_tick = 0; + } + } + if(mud->send_timeout > 0) + mud->send_timeout --; +} + +// Lua: mqtt.Client(clientid, keepalive, user, pass) +static int mqtt_socket_client( lua_State* L ) +{ + NODE_DBG("mqtt_socket_client is called.\n"); + + lmqtt_userdata *mud; + char tempid[20] = {0}; + c_sprintf(tempid, "%s%x", "NodeMCU_", system_get_chip_id() ); + NODE_DBG(tempid); + NODE_DBG("\n"); + size_t il = c_strlen(tempid); + const char *clientId = tempid, *username = NULL, *password = NULL; + int stack = 1; + unsigned secure = 0; + int top = lua_gettop(L); + + // create a object + mud = (lmqtt_userdata *)lua_newuserdata(L, sizeof(lmqtt_userdata)); + // pre-initialize it, in case of errors + mud->self_ref = LUA_NOREF; + mud->cb_connect_ref = LUA_NOREF; + mud->cb_disconnect_ref = LUA_NOREF; + + mud->cb_message_ref = LUA_NOREF; + mud->cb_suback_ref = LUA_NOREF; + mud->cb_puback_ref = LUA_NOREF; + mud->pesp_conn = NULL; + mud->secure = 0; + + mud->keep_alive_tick = 0; + mud->send_timeout = 0; + mud->connState = MQTT_INIT; + c_memset(&mud->mqttTimer, 0, sizeof(ETSTimer)); + c_memset(&mud->mqtt_state, 0, sizeof(mqtt_state_t)); + c_memset(&mud->connect_info, 0, sizeof(mqtt_connect_info_t)); + + // set its metatable + luaL_getmetatable(L, "mqtt.socket"); + lua_setmetatable(L, -2); + + if( lua_isstring(L,stack) ) // deal with the clientid string + { + clientId = luaL_checklstring( L, stack, &il ); + stack++; + } + + // TODO: check the zalloc result. + mud->connect_info.client_id = (uint8_t *)c_zalloc(il+1); + if(!mud->connect_info.client_id){ + return luaL_error(L, "not enough memory"); + } + c_memcpy(mud->connect_info.client_id, clientId, il); + mud->connect_info.client_id[il] = 0; + + mud->mqtt_state.in_buffer = (uint8_t *)c_zalloc(MQTT_BUF_SIZE); + if(!mud->mqtt_state.in_buffer){ + return luaL_error(L, "not enough memory"); + } + + mud->mqtt_state.out_buffer = (uint8_t *)c_zalloc(MQTT_BUF_SIZE); + if(!mud->mqtt_state.out_buffer){ + return luaL_error(L, "not enough memory"); + } + + mud->mqtt_state.in_buffer_length = MQTT_BUF_SIZE; + mud->mqtt_state.out_buffer_length = MQTT_BUF_SIZE; + + mud->connState = MQTT_INIT; + mud->connect_info.clean_session = 1; + mud->connect_info.will_qos = 0; + mud->connect_info.will_retain = 0; + mud->keep_alive_tick = 0; + mud->connect_info.keepalive = 0; + mud->mqtt_state.connect_info = &mud->connect_info; + + gL = L; // global L for mqtt module. + + if(lua_isnumber( L, stack )) + { + mud->connect_info.keepalive = luaL_checkinteger( L, stack); + stack++; + } + + if(mud->connect_info.keepalive == 0){ + mud->connect_info.keepalive = MQTT_DEFAULT_KEEPALIVE; + return 1; + } + + if(lua_isstring( L, stack )){ + username = luaL_checklstring( L, stack, &il ); + stack++; + } + if(username == NULL) + return 1; + mud->connect_info.username = (uint8_t *)c_zalloc(il + 1); + if(!mud->connect_info.username){ + return luaL_error(L, "not enough memory"); + } + + c_memcpy(mud->connect_info.username, username, il); + mud->connect_info.username[il] = 0; + + if(lua_isstring( L, stack )){ + password = luaL_checklstring( L, stack, &il ); + stack++; + } + if(password == NULL) + return 1; + mud->connect_info.password = (uint8_t *)c_zalloc(il + 1); + if(!mud->connect_info.password){ + return luaL_error(L, "not enough memory"); + } + + c_memcpy(mud->connect_info.password, password, il); + mud->connect_info.password[il] = 0; + + NODE_DBG("MQTT: Init info: %s, %s, %s\r\n", mud->connect_info.client_id, mud->connect_info.username, mud->connect_info.password); + + return 1; +} + +// Lua: mqtt.delete( socket ) +// call close() first +// socket: unref everything +static int mqtt_delete( lua_State* L ) +{ + NODE_DBG("mqtt_delete is called.\n"); + + lmqtt_userdata *mud = (lmqtt_userdata *)luaL_checkudata(L, 1, "mqtt.socket"); + luaL_argcheck(L, mud, 1, "mqtt.socket expected"); + if(mud==NULL){ + NODE_DBG("userdata is nil.\n"); + return 0; + } + + os_timer_disarm(&mud->mqttTimer); + mud->connected = 0; + if(mud->pesp_conn){ // for client connected to tcp server, this should set NULL in disconnect cb + mud->pesp_conn->reverse = NULL; + if(mud->pesp_conn->proto.tcp) + c_free(mud->pesp_conn->proto.tcp); + mud->pesp_conn->proto.tcp = NULL; + c_free(mud->pesp_conn); + mud->pesp_conn = NULL; // for socket, it will free this when disconnected + } + + if(mud->connect_info.will_topic){ + c_free(mud->connect_info.client_id); + } + + if(mud->connect_info.will_message){ + c_free(mud->connect_info.will_message); + } + + if(mud->connect_info.client_id) + c_free(mud->connect_info.client_id); + if(mud->connect_info.username) + c_free(mud->connect_info.username); + if(mud->connect_info.password) + c_free(mud->connect_info.password); + if(mud->mqtt_state.in_buffer) + c_free(mud->mqtt_state.in_buffer); + if(mud->mqtt_state.out_buffer) + c_free(mud->mqtt_state.out_buffer); + + // free (unref) callback ref + if(LUA_NOREF!=mud->cb_connect_ref){ + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_connect_ref); + mud->cb_connect_ref = LUA_NOREF; + } + if(LUA_NOREF!=mud->cb_disconnect_ref){ + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_disconnect_ref); + mud->cb_disconnect_ref = LUA_NOREF; + } + if(LUA_NOREF!=mud->cb_message_ref){ + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_message_ref); + mud->cb_message_ref = LUA_NOREF; + } + if(LUA_NOREF!=mud->cb_suback_ref){ + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_suback_ref); + mud->cb_suback_ref = LUA_NOREF; + } + if(LUA_NOREF!=mud->cb_puback_ref){ + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_puback_ref); + mud->cb_puback_ref = LUA_NOREF; + } + lua_gc(gL, LUA_GCSTOP, 0); + if(LUA_NOREF!=mud->self_ref){ + luaL_unref(L, LUA_REGISTRYINDEX, mud->self_ref); + mud->self_ref = LUA_NOREF; + } + lua_gc(gL, LUA_GCRESTART, 0); + return 0; +} + +static void socket_connect(struct espconn *pesp_conn) +{ + if(pesp_conn == NULL) + return; + lmqtt_userdata *mud = (lmqtt_userdata *)pesp_conn->reverse; + if(mud == NULL) + return; + + if(mud->secure){ + espconn_secure_connect(pesp_conn); + } + else + { + espconn_connect(pesp_conn); + } + + NODE_DBG("socket_connect is called.\n"); +} + +static void socket_dns_found(const char *name, ip_addr_t *ipaddr, void *arg); +static dns_reconn_count = 0; +static ip_addr_t host_ip; // for dns +static void socket_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) +{ + NODE_DBG("socket_dns_found is called.\n"); + struct espconn *pesp_conn = arg; + if(pesp_conn == NULL){ + NODE_DBG("pesp_conn null.\n"); + return; + } + + if(ipaddr == NULL) + { + dns_reconn_count++; + if( dns_reconn_count >= 5 ){ + NODE_ERR( "DNS Fail!\n" ); + // Note: should delete the pesp_conn or unref self_ref here. + mqtt_socket_disconnected(arg); // although not connected, but fire disconnect callback to release every thing. + return; + } + NODE_ERR( "DNS retry %d!\n", dns_reconn_count ); + host_ip.addr = 0; + espconn_gethostbyname(pesp_conn, name, &host_ip, socket_dns_found); + return; + } + + // ipaddr->addr is a uint32_t ip + if(ipaddr->addr != 0) + { + dns_reconn_count = 0; + c_memcpy(pesp_conn->proto.tcp->remote_ip, &(ipaddr->addr), 4); + NODE_DBG("TCP ip is set: "); + NODE_DBG(IPSTR, IP2STR(&(ipaddr->addr))); + NODE_DBG("\n"); + socket_connect(pesp_conn); + } +} + +// Lua: mqtt:connect( host, port, secure, function(client) ) +static int mqtt_socket_lwt( lua_State* L ) +{ + uint8_t stack = 1; + size_t topicSize, il; + NODE_DBG("mqtt_socket_lwt.\n"); + lmqtt_userdata *mud = NULL; + const char *lwtTopic, *lwtMsg; + uint8_t lwtQoS, lwtRetain; + + mud = (lmqtt_userdata *)luaL_checkudata( L, stack, "mqtt.socket" ); + luaL_argcheck( L, mud, stack, "mqtt.socket expected" ); + + if(mud == NULL) + return 0; + + stack++; + lwtTopic = luaL_checklstring( L, stack, &topicSize ); + if (lwtTopic == NULL) + { + return luaL_error( L, "need lwt topic"); + } + + stack++; + lwtMsg = luaL_checklstring( L, stack, &il ); + if (lwtMsg == NULL) + { + return luaL_error( L, "need lwt topic"); + } + + mud->connect_info.will_topic = (uint8_t*) c_zalloc( topicSize + 1 ); + if(!mud->connect_info.will_topic){ + return luaL_error( L, "not enough memory"); + } + c_memcpy(mud->connect_info.will_topic, lwtTopic, topicSize); + mud->connect_info.will_topic[topicSize] = 0; + + mud->connect_info.will_message = (uint8_t*) c_zalloc( il + 1 ); + if(!mud->connect_info.will_message){ + return luaL_error( L, "not enough memory"); + } + c_memcpy(mud->connect_info.will_message, lwtMsg, il); + mud->connect_info.will_message[il] = 0; + + + stack++; + mud->connect_info.will_qos = luaL_checkinteger( L, stack ); + + stack++; + mud->connect_info.will_retain = luaL_checkinteger( L, stack ); + + NODE_DBG("mqtt_socket_lwt: topic: %s, message: %s, qos: %d, retain: %d\n", + mud->connect_info.will_topic, + mud->connect_info.will_message, + mud->connect_info.will_qos, + mud->connect_info.will_retain); + return 0; +} + +// Lua: mqtt:connect( host, port, secure, function(client) ) +static int mqtt_socket_connect( lua_State* L ) +{ + NODE_DBG("mqtt_socket_connect is called.\n"); + lmqtt_userdata *mud = NULL; + unsigned port = 1883; + size_t il; + ip_addr_t ipaddr; + const char *domain; + int stack = 1; + unsigned secure = 0; + int top = lua_gettop(L); + + mud = (lmqtt_userdata *)luaL_checkudata(L, stack, "mqtt.socket"); + luaL_argcheck(L, mud, stack, "mqtt.socket expected"); + stack++; + if(mud == NULL) + return 0; + + if(mud->pesp_conn) + c_free(mud->pesp_conn); + struct espconn *pesp_conn = NULL; + pesp_conn = mud->pesp_conn = (struct espconn *)c_zalloc(sizeof(struct espconn)); + if(!pesp_conn) + return luaL_error(L, "not enough memory"); + + pesp_conn->proto.udp = NULL; + pesp_conn->proto.tcp = (esp_tcp *)c_zalloc(sizeof(esp_tcp)); + if(!pesp_conn->proto.tcp){ + c_free(pesp_conn); + pesp_conn = mud->pesp_conn = NULL; + return luaL_error(L, "not enough memory"); + } + // reverse is for the callback function + pesp_conn->reverse = mud; + pesp_conn->type = ESPCONN_TCP; + pesp_conn->state = ESPCONN_NONE; + mud->connected = 0; + + if( (stack<=top) && lua_isstring(L,stack) ) // deal with the domain string + { + domain = luaL_checklstring( L, stack, &il ); + + stack++; + if (domain == NULL) + { + domain = "127.0.0.1"; + } + ipaddr.addr = ipaddr_addr(domain); + c_memcpy(pesp_conn->proto.tcp->remote_ip, &ipaddr.addr, 4); + NODE_DBG("TCP ip is set: "); + NODE_DBG(IPSTR, IP2STR(&ipaddr.addr)); + NODE_DBG("\n"); + } + + if ( (stack<=top) && lua_isnumber(L, stack) ) + { + port = lua_tointeger(L, stack); + stack++; + NODE_DBG("TCP port is set: %d.\n", port); + } + pesp_conn->proto.tcp->remote_port = port; + pesp_conn->proto.tcp->local_port = espconn_port(); + + if ( (stack<=top) && lua_isnumber(L, stack) ) + { + secure = lua_tointeger(L, stack); + stack++; + if ( secure != 0 && secure != 1 ){ + secure = 0; // default to 0 + } + } else { + secure = 0; // default to 0 + } + mud->secure = secure; // save + + // call back function when a connection is obtained, tcp only + if ((stack<=top) && (lua_type(L, stack) == LUA_TFUNCTION || lua_type(L, stack) == LUA_TLIGHTFUNCTION)){ + lua_pushvalue(L, stack); // copy argument (func) to the top of stack + if(mud->cb_connect_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_connect_ref); + mud->cb_connect_ref = luaL_ref(L, LUA_REGISTRYINDEX); + stack++; + } + + lua_pushvalue(L, 1); // copy userdata to the top of stack + if(mud->self_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, mud->self_ref); + mud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + espconn_regist_connectcb(pesp_conn, mqtt_socket_connected); + espconn_regist_reconcb(pesp_conn, mqtt_socket_reconnected); + + if((ipaddr.addr == IPADDR_NONE) && (c_memcmp(domain,"255.255.255.255",16) != 0)) + { + host_ip.addr = 0; + dns_reconn_count = 0; + if(ESPCONN_OK == espconn_gethostbyname(pesp_conn, domain, &host_ip, socket_dns_found)){ + socket_dns_found(domain, &host_ip, pesp_conn); // ip is returned in host_ip. + } + } + else + { + socket_connect(pesp_conn); + } + + os_timer_disarm(&mud->mqttTimer); + os_timer_setfn(&mud->mqttTimer, (os_timer_func_t *)mqtt_socket_timer, mud); + os_timer_arm(&mud->mqttTimer, 1000, 1); + + return 0; +} + +// Lua: mqtt:close() +// client disconnect and unref itself +static int mqtt_socket_close( lua_State* L ) +{ + NODE_DBG("mqtt_socket_close is called.\n"); + int i = 0; + lmqtt_userdata *mud = NULL; + + mud = (lmqtt_userdata *)luaL_checkudata(L, 1, "mqtt.socket"); + luaL_argcheck(L, mud, 1, "mqtt.socket expected"); + if(mud == NULL) + return 0; + + if(mud->pesp_conn == NULL) + return 0; + + // call mqtt_disconnect() + + if(mud->secure){ + if(mud->pesp_conn->proto.tcp->remote_port || mud->pesp_conn->proto.tcp->local_port) + espconn_secure_disconnect(mud->pesp_conn); + } + else + { + if(mud->pesp_conn->proto.tcp->remote_port || mud->pesp_conn->proto.tcp->local_port) + espconn_disconnect(mud->pesp_conn); + } + return 0; +} + +// Lua: mqtt:on( "method", function() ) +static int mqtt_socket_on( lua_State* L ) +{ + NODE_DBG("mqtt_on is called.\n"); + lmqtt_userdata *mud; + size_t sl; + + mud = (lmqtt_userdata *)luaL_checkudata(L, 1, "mqtt.socket"); + luaL_argcheck(L, mud, 1, "mqtt.socket expected"); + if(mud==NULL){ + NODE_DBG("userdata is nil.\n"); + return 0; + } + + const char *method = luaL_checklstring( L, 2, &sl ); + if (method == NULL) + return luaL_error( L, "wrong arg type" ); + + luaL_checkanyfunction(L, 3); + lua_pushvalue(L, 3); // copy argument (func) to the top of stack + + if( sl == 7 && c_strcmp(method, "connect") == 0){ + if(mud->cb_connect_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_connect_ref); + mud->cb_connect_ref = luaL_ref(L, LUA_REGISTRYINDEX); + }else if( sl == 7 && c_strcmp(method, "offline") == 0){ + if(mud->cb_disconnect_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_disconnect_ref); + mud->cb_disconnect_ref = luaL_ref(L, LUA_REGISTRYINDEX); + }else if( sl == 7 && c_strcmp(method, "message") == 0){ + if(mud->cb_message_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_message_ref); + mud->cb_message_ref = luaL_ref(L, LUA_REGISTRYINDEX); + }else{ + lua_pop(L, 1); + return luaL_error( L, "method not supported" ); + } + + return 0; +} + +// Lua: mqtt:subscribe(topic, qos, function()) +static int mqtt_socket_subscribe( lua_State* L ) +{ + NODE_DBG("mqtt_socket_subscribe is called.\n"); + uint8_t stack = 1, qos = 0, retain = 0; + const char *topic; + size_t il; + lmqtt_userdata *mud; + + mud = (lmqtt_userdata *)luaL_checkudata(L, stack, "mqtt.socket"); + luaL_argcheck(L, mud, stack, "mqtt.socket expected"); + stack++; + + if(mud->send_timeout != 0) + return luaL_error( L, "sending in process" ); + + if(!mud->connected) + return luaL_error(L, "not connected"); + + topic = luaL_checklstring( L, stack, &il ); + stack++; + if(topic == NULL) + return luaL_error( L, "need topic name" ); + + qos = luaL_checkinteger( L, stack); + stack++; + + mud->mqtt_state.outbound_message = mqtt_msg_subscribe(&mud->mqtt_state.mqtt_connection, + topic, qos, + &mud->mqtt_state.pending_msg_id); + mud->send_timeout = MQTT_SEND_TIMEOUT; + mud->mqtt_state.pending_msg_type = MQTT_MSG_TYPE_SUBSCRIBE; + mud->mqtt_state.pending_publish_qos = mqtt_get_qos(mud->mqtt_state.outbound_message->data); + + if (lua_type(L, stack) == LUA_TFUNCTION || lua_type(L, stack) == LUA_TLIGHTFUNCTION){ + lua_pushvalue(L, stack); // copy argument (func) to the top of stack + if(mud->cb_suback_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_suback_ref); + mud->cb_suback_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + + if(mud->secure) + espconn_secure_sent(mud->pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + else + espconn_sent(mud->pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + + return 0; +} + +// Lua: mqtt:publish( topic, payload, qos, retain, function() ) +static int mqtt_socket_publish( lua_State* L ) +{ + // NODE_DBG("mqtt_publish is called.\n"); + struct espconn *pesp_conn = NULL; + lmqtt_userdata *mud; + size_t l; + uint8_t stack = 1; + mud = (lmqtt_userdata *)luaL_checkudata(L, stack, "mqtt.socket"); + luaL_argcheck(L, mud, stack, "mqtt.socket expected"); + stack++; + if(mud==NULL){ + NODE_DBG("userdata is nil.\n"); + return 0; + } + + if(mud->pesp_conn == NULL){ + NODE_DBG("mud->pesp_conn is NULL.\n"); + return 0; + } + if(mud->send_timeout != 0) + return luaL_error( L, "sending in process" ); + pesp_conn = mud->pesp_conn; + +#if 0 + char temp[20] = {0}; + c_sprintf(temp, IPSTR, IP2STR( &(pesp_conn->proto.tcp->remote_ip) ) ); + NODE_DBG("remote "); + NODE_DBG(temp); + NODE_DBG(":"); + NODE_DBG("%d",pesp_conn->proto.tcp->remote_port); + NODE_DBG(" sending data.\n"); +#endif + const char *topic = luaL_checklstring( L, stack, &l ); + stack ++; + if (topic == NULL) + return luaL_error( L, "need topic" ); + + const char *payload = luaL_checklstring( L, stack, &l ); + stack ++; + uint8_t qos = luaL_checkinteger( L, stack); + stack ++; + uint8_t retain = luaL_checkinteger( L, stack); + stack ++; + + + mud->mqtt_state.outbound_message = mqtt_msg_publish(&mud->mqtt_state.mqtt_connection, + topic, payload, l, + qos, retain, + &mud->mqtt_state.pending_msg_id); + mud->mqtt_state.pending_msg_type = MQTT_MSG_TYPE_PUBLISH; + mud->mqtt_state.pending_publish_qos = qos; + mud->send_timeout = MQTT_SEND_TIMEOUT; + if (lua_type(L, stack) == LUA_TFUNCTION || lua_type(L, stack) == LUA_TLIGHTFUNCTION){ + lua_pushvalue(L, stack); // copy argument (func) to the top of stack + if(mud->cb_puback_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, mud->cb_puback_ref); + mud->cb_puback_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + + if(mud->secure) + espconn_secure_sent(pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + else + espconn_sent(pesp_conn, mud->mqtt_state.outbound_message->data, mud->mqtt_state.outbound_message->length); + mud->mqtt_state.outbound_message = NULL; + return 0; +} + +// Module function map +#define MIN_OPT_LEVEL 2 +#include "lrodefs.h" + +static const LUA_REG_TYPE mqtt_socket_map[] = +{ + { LSTRKEY( "lwt" ), LFUNCVAL ( mqtt_socket_lwt ) }, + { LSTRKEY( "connect" ), LFUNCVAL ( mqtt_socket_connect ) }, + { LSTRKEY( "close" ), LFUNCVAL ( mqtt_socket_close ) }, + { LSTRKEY( "publish" ), LFUNCVAL ( mqtt_socket_publish ) }, + { LSTRKEY( "subscribe" ), LFUNCVAL ( mqtt_socket_subscribe ) }, + { LSTRKEY( "on" ), LFUNCVAL ( mqtt_socket_on ) }, + { LSTRKEY( "__gc" ), LFUNCVAL ( mqtt_delete ) }, +#if LUA_OPTIMIZE_MEMORY > 0 + { LSTRKEY( "__index" ), LROVAL ( mqtt_socket_map ) }, +#endif + { LNILKEY, LNILVAL } +}; + +const LUA_REG_TYPE mqtt_map[] = +{ + { LSTRKEY( "Client" ), LFUNCVAL ( mqtt_socket_client ) }, +#if LUA_OPTIMIZE_MEMORY > 0 + + { LSTRKEY( "__metatable" ), LROVAL( mqtt_map ) }, +#endif + { LNILKEY, LNILVAL } +}; + +LUALIB_API int ICACHE_FLASH_ATTR luaopen_mqtt( lua_State *L ) +{ +#if LUA_OPTIMIZE_MEMORY > 0 + luaL_rometatable(L, "mqtt.socket", (void *)mqtt_socket_map); // create metatable for mqtt.socket + return 0; +#else // #if LUA_OPTIMIZE_MEMORY > 0 + int n; + luaL_register( L, AUXLIB_MQTT, mqtt_map ); + + // Set it as its own metatable + lua_pushvalue( L, -1 ); + lua_setmetatable( L, -2 ); + + // Module constants + // MOD_REG_NUMBER( L, "TCP", TCP ); + + // create metatable + luaL_newmetatable(L, "mqtt.socket"); + // metatable.__index = metatable + lua_pushliteral(L, "__index"); + lua_pushvalue(L,-2); + lua_rawset(L,-3); + // Setup the methods inside metatable + luaL_register( L, NULL, mqtt_socket_map ); + + return 1; +#endif // #if LUA_OPTIMIZE_MEMORY > 0 +} diff --git a/app/mqtt/Makefile b/app/mqtt/Makefile new file mode 100644 index 00000000..3fd35717 --- /dev/null +++ b/app/mqtt/Makefile @@ -0,0 +1,44 @@ + +############################################################# +# Required variables for each makefile +# Discard this section from all parent makefiles +# Expected variables (with automatic defaults): +# CSRCS (all "C" files in the dir) +# SUBDIRS (all subdirs with a Makefile) +# GEN_LIBS - list of libs to be generated () +# GEN_IMAGES - list of images to be generated () +# COMPONENTS_xxx - a list of libs/objs in the form +# subdir/lib to be extracted and rolled up into +# a generated lib/image xxx.a () +# +ifndef PDIR +GEN_LIBS = mqtt.a +endif + +############################################################# +# Configuration i.e. compile options etc. +# Target specific stuff (defines etc.) goes in here! +# Generally values applying to a tree are captured in the +# makefile at its root level - these are then overridden +# for a subtree within the makefile rooted therein +# +#DEFINES += + +############################################################# +# Recursion Magic - Don't touch this!! +# +# Each subtree potentially has an include directory +# corresponding to the common APIs applicable to modules +# rooted at that subtree. Accordingly, the INCLUDE PATH +# of a module can only contain the include directories up +# its parent path, and not its siblings +# +# Required for each makefile to inherit from the parent +# + +INCLUDES := $(INCLUDES) -I $(PDIR)include +INCLUDES += -I ./ +INCLUDES += -I ../libc +PDIR := ../$(PDIR) +sinclude $(PDIR)Makefile + diff --git a/app/mqtt/mqtt_msg.c b/app/mqtt/mqtt_msg.c new file mode 100644 index 00000000..5e49bfb1 --- /dev/null +++ b/app/mqtt/mqtt_msg.c @@ -0,0 +1,457 @@ +/* +* Copyright (c) 2014, Stephen Robinson +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holder nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +*/ + +#include +#include "mqtt_msg.h" + +#define MQTT_MAX_FIXED_HEADER_SIZE 3 + +enum mqtt_connect_flag +{ + MQTT_CONNECT_FLAG_USERNAME = 1 << 7, + MQTT_CONNECT_FLAG_PASSWORD = 1 << 6, + MQTT_CONNECT_FLAG_WILL_RETAIN = 1 << 5, + MQTT_CONNECT_FLAG_WILL = 1 << 2, + MQTT_CONNECT_FLAG_CLEAN_SESSION = 1 << 1 +}; + +struct __attribute((__packed__)) mqtt_connect_variable_header +{ + uint8_t lengthMsb; + uint8_t lengthLsb; + uint8_t magic[6]; + uint8_t version; + uint8_t flags; + uint8_t keepaliveMsb; + uint8_t keepaliveLsb; +}; + +static int append_string(mqtt_connection_t* connection, const char* string, int len) +{ + if(connection->message.length + len + 2 > connection->buffer_length) + return -1; + + connection->buffer[connection->message.length++] = len >> 8; + connection->buffer[connection->message.length++] = len & 0xff; + memcpy(connection->buffer + connection->message.length, string, len); + connection->message.length += len; + + return len + 2; +} + +static uint16_t append_message_id(mqtt_connection_t* connection, uint16_t message_id) +{ + // If message_id is zero then we should assign one, otherwise + // we'll use the one supplied by the caller + while(message_id == 0) + message_id = ++connection->message_id; + + if(connection->message.length + 2 > connection->buffer_length) + return 0; + + connection->buffer[connection->message.length++] = message_id >> 8; + connection->buffer[connection->message.length++] = message_id & 0xff; + + return message_id; +} + +static int init_message(mqtt_connection_t* connection) +{ + connection->message.length = MQTT_MAX_FIXED_HEADER_SIZE; + return MQTT_MAX_FIXED_HEADER_SIZE; +} + +static mqtt_message_t* fail_message(mqtt_connection_t* connection) +{ + connection->message.data = connection->buffer; + connection->message.length = 0; + return &connection->message; +} + +static mqtt_message_t* fini_message(mqtt_connection_t* connection, int type, int dup, int qos, int retain) +{ + int remaining_length = connection->message.length - MQTT_MAX_FIXED_HEADER_SIZE; + + if(remaining_length > 127) + { + connection->buffer[0] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); + connection->buffer[1] = 0x80 | (remaining_length % 128); + connection->buffer[2] = remaining_length / 128; + connection->message.length = remaining_length + 3; + connection->message.data = connection->buffer; + } + else + { + connection->buffer[1] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); + connection->buffer[2] = remaining_length; + connection->message.length = remaining_length + 2; + connection->message.data = connection->buffer + 1; + } + + return &connection->message; +} + +void mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length) +{ + memset(connection, 0, sizeof(connection)); + connection->buffer = buffer; + connection->buffer_length = buffer_length; +} + +int mqtt_get_total_length(uint8_t* buffer, uint16_t length) +{ + int i; + int totlen = 0; + + for(i = 1; i < length; ++i) + { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if((buffer[i] & 0x80) == 0) + { + ++i; + break; + } + } + totlen += i; + + return totlen; +} + +const char* mqtt_get_publish_topic(uint8_t* buffer, uint16_t* length) +{ + int i; + int totlen = 0; + int topiclen; + + for(i = 1; i < *length; ++i) + { + totlen += (buffer[i] & 0x7f) << (7 * (i -1)); + if((buffer[i] & 0x80) == 0) + { + ++i; + break; + } + } + totlen += i; + + if(i + 2 >= *length) + return NULL; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if(i + topiclen > *length) + return NULL; + + *length = topiclen; + return (const char*)(buffer + i); +} + +const char* mqtt_get_publish_data(uint8_t* buffer, uint16_t* length) +{ + int i; + int totlen = 0; + int topiclen; + + for(i = 1; i < *length; ++i) + { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if((buffer[i] & 0x80) == 0) + { + ++i; + break; + } + } + totlen += i; + + if(i + 2 >= *length) + return NULL; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if(i + topiclen >= *length){ + *length = 0; + return NULL; + } + i += topiclen; + + if(mqtt_get_qos(buffer) > 0) + { + if(i + 2 >= *length) + return NULL; + i += 2; + } + + if(totlen < i) + return NULL; + + if(totlen <= *length) + *length = totlen - i; + else + *length = *length - i; + return (const char*)(buffer + i); +} + +uint16_t mqtt_get_id(uint8_t* buffer, uint16_t length) +{ + if(length < 1) + return 0; + + switch(mqtt_get_type(buffer)) + { + case MQTT_MSG_TYPE_PUBLISH: + { + int i; + int topiclen; + + for(i = 1; i < length; ++i) + { + if((buffer[i] & 0x80) == 0) + { + ++i; + break; + } + } + + if(i + 2 >= length) + return 0; + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if(i + topiclen >= length) + return 0; + i += topiclen; + + if(mqtt_get_qos(buffer) > 0) + { + if(i + 2 >= length) + return 0; + //i += 2; + } else { + return 0; + } + + return (buffer[i] << 8) | buffer[i + 1]; + } + case MQTT_MSG_TYPE_PUBACK: + case MQTT_MSG_TYPE_PUBREC: + case MQTT_MSG_TYPE_PUBREL: + case MQTT_MSG_TYPE_PUBCOMP: + case MQTT_MSG_TYPE_SUBACK: + case MQTT_MSG_TYPE_UNSUBACK: + case MQTT_MSG_TYPE_SUBSCRIBE: + { + // This requires the remaining length to be encoded in 1 byte, + // which it should be. + if(length >= 4 && (buffer[1] & 0x80) == 0) + return (buffer[2] << 8) | buffer[3]; + else + return 0; + } + + default: + return 0; + } +} + +mqtt_message_t* mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info) +{ + struct mqtt_connect_variable_header* variable_header; + + init_message(connection); + + if(connection->message.length + sizeof(*variable_header) > connection->buffer_length) + return fail_message(connection); + variable_header = (void*)(connection->buffer + connection->message.length); + connection->message.length += sizeof(*variable_header); + + variable_header->lengthMsb = 0; + variable_header->lengthLsb = 6; + memcpy(variable_header->magic, "MQIsdp", 6); + variable_header->version = 3; + variable_header->flags = 0; + variable_header->keepaliveMsb = info->keepalive >> 8; + variable_header->keepaliveLsb = info->keepalive & 0xff; + + if(info->clean_session) + variable_header->flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION; + + if(info->client_id != NULL && info->client_id[0] != '\0') + { + if(append_string(connection, info->client_id, strlen(info->client_id)) < 0) + return fail_message(connection); + } + else + return fail_message(connection); + + if(info->will_topic != NULL && info->will_topic[0] != '\0') + { + if(append_string(connection, info->will_topic, strlen(info->will_topic)) < 0) + return fail_message(connection); + + if(append_string(connection, info->will_message, strlen(info->will_message)) < 0) + return fail_message(connection); + + variable_header->flags |= MQTT_CONNECT_FLAG_WILL; + if(info->will_retain) + variable_header->flags |= MQTT_CONNECT_FLAG_WILL_RETAIN; + variable_header->flags |= (info->will_qos & 3) << 4; + } + + if(info->username != NULL && info->username[0] != '\0') + { + if(append_string(connection, info->username, strlen(info->username)) < 0) + return fail_message(connection); + + variable_header->flags |= MQTT_CONNECT_FLAG_USERNAME; + } + + if(info->password != NULL && info->password[0] != '\0') + { + if(append_string(connection, info->password, strlen(info->password)) < 0) + return fail_message(connection); + + variable_header->flags |= MQTT_CONNECT_FLAG_PASSWORD; + } + + return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); +} + +mqtt_message_t* mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id) +{ + init_message(connection); + + if(topic == NULL || topic[0] == '\0') + return fail_message(connection); + + if(append_string(connection, topic, strlen(topic)) < 0) + return fail_message(connection); + + if(qos > 0) + { + if((*message_id = append_message_id(connection, 0)) == 0) + return fail_message(connection); + } + else + *message_id = 0; + + if(connection->message.length + data_length > connection->buffer_length) + return fail_message(connection); + memcpy(connection->buffer + connection->message.length, data, data_length); + connection->message.length += data_length; + + return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); +} + +mqtt_message_t* mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id) +{ + init_message(connection); + if(append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); +} + +mqtt_message_t* mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id) +{ + init_message(connection); + if(append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); +} + +mqtt_message_t* mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id) +{ + init_message(connection); + if(append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); +} + +mqtt_message_t* mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id) +{ + init_message(connection); + if(append_message_id(connection, message_id) == 0) + return fail_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); +} + +mqtt_message_t* mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id) +{ + init_message(connection); + + if(topic == NULL || topic[0] == '\0') + return fail_message(connection); + + if((*message_id = append_message_id(connection, 0)) == 0) + return fail_message(connection); + + if(append_string(connection, topic, strlen(topic)) < 0) + return fail_message(connection); + + if(connection->message.length + 1 > connection->buffer_length) + return fail_message(connection); + connection->buffer[connection->message.length++] = qos; + + return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t* mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id) +{ + init_message(connection); + + if(topic == NULL || topic[0] == '\0') + return fail_message(connection); + + if((*message_id = append_message_id(connection, 0)) == 0) + return fail_message(connection); + + if(append_string(connection, topic, strlen(topic)) < 0) + return fail_message(connection); + + return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t* mqtt_msg_pingreq(mqtt_connection_t* connection) +{ + init_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0); +} + +mqtt_message_t* mqtt_msg_pingresp(mqtt_connection_t* connection) +{ + init_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_PINGRESP, 0, 0, 0); +} + +mqtt_message_t* mqtt_msg_disconnect(mqtt_connection_t* connection) +{ + init_message(connection); + return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); +} diff --git a/app/mqtt/mqtt_msg.h b/app/mqtt/mqtt_msg.h new file mode 100644 index 00000000..225ba642 --- /dev/null +++ b/app/mqtt/mqtt_msg.h @@ -0,0 +1,129 @@ +/* + * File: mqtt_msg.h + * Author: Minh Tuan + * + * Created on July 12, 2014, 1:05 PM + */ + +#ifndef MQTT_MSG_H +#define MQTT_MSG_H +#include "c_types.h" +#ifdef __cplusplus +extern "C" { +#endif + +/* +* Copyright (c) 2014, Stephen Robinson +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holder nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +*/ +/* 7 6 5 4 3 2 1 0*/ +/*| --- Message Type---- | DUP Flag | QoS Level | Retain | +/* Remaining Length */ + + +enum mqtt_message_type +{ + 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 +}; + +typedef struct mqtt_message +{ + uint8_t* data; + uint16_t length; + +} mqtt_message_t; + +typedef struct mqtt_connection +{ + mqtt_message_t message; + + uint16_t message_id; + uint8_t* buffer; + uint16_t buffer_length; + +} mqtt_connection_t; + +typedef struct mqtt_connect_info +{ + char* client_id; + char* username; + char* password; + char* will_topic; + char* will_message; + int keepalive; + int will_qos; + int will_retain; + int clean_session; + +} mqtt_connect_info_t; + + +static inline int mqtt_get_type(uint8_t* buffer) { return (buffer[0] & 0xf0) >> 4; } +static inline int mqtt_get_dup(uint8_t* buffer) { return (buffer[0] & 0x08) >> 3; } +static inline int mqtt_get_qos(uint8_t* buffer) { return (buffer[0] & 0x06) >> 1; } +static inline int mqtt_get_retain(uint8_t* buffer) { return (buffer[0] & 0x01); } + +void mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length); +int mqtt_get_total_length(uint8_t* buffer, uint16_t length); +const char* mqtt_get_publish_topic(uint8_t* buffer, uint16_t* length); +const char* mqtt_get_publish_data(uint8_t* buffer, uint16_t* length); +uint16_t mqtt_get_id(uint8_t* buffer, uint16_t length); + +mqtt_message_t* mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info); +mqtt_message_t* mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id); +mqtt_message_t* mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id); +mqtt_message_t* mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id); +mqtt_message_t* mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id); +mqtt_message_t* mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id); +mqtt_message_t* mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id); +mqtt_message_t* mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id); +mqtt_message_t* mqtt_msg_pingreq(mqtt_connection_t* connection); +mqtt_message_t* mqtt_msg_pingresp(mqtt_connection_t* connection); +mqtt_message_t* mqtt_msg_disconnect(mqtt_connection_t* connection); + + +#ifdef __cplusplus +} +#endif + +#endif /* MQTT_MSG_H */ + diff --git a/examples/fragment.lua b/examples/fragment.lua index 196232c1..d619b321 100644 --- a/examples/fragment.lua +++ b/examples/fragment.lua @@ -313,3 +313,9 @@ uart.on("data", 0 ,function(input) if input=="q" then uart.on("data") else print uart.on("data","\r",function(input) if input=="quit" then uart.on("data") else print(input) end end, 1) for k, v in pairs(file.list()) do print('file:'..k..' len:'..v) end + +m=mqtt.Client() +m:connect("192.168.18.101",1883) +m:subscribe("/topic",0,function(m) print("sub done") end) +m:on("message",function(m,t,pl) print(t..":") if pl~=nil then print(pl) end end ) +m:publish("/topic","hello",0,0) diff --git a/pre_build/0.9.5/nodemcu_latest.bin b/pre_build/0.9.5/nodemcu_20150118.bin similarity index 100% rename from pre_build/0.9.5/nodemcu_latest.bin rename to pre_build/0.9.5/nodemcu_20150118.bin diff --git a/pre_build/latest/nodemcu_latest.bin b/pre_build/latest/nodemcu_latest.bin index 3fd92e0bf927e43b9da53013303d96a7db82f94a..2317b6030144cc2067d789ea0cb49dbac39a03ef 100644 GIT binary patch delta 53988 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

C|0w(ytL> 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⁣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 m#k=5k0@v%K;E diff --git a/tools/.gitattributes b/tools/.gitattributes new file mode 100644 index 00000000..b93363f7 --- /dev/null +++ b/tools/.gitattributes @@ -0,0 +1,11 @@ +# Enforce Unix newlines +*.css text eol=lf +*.html text eol=lf +*.js text eol=lf +*.json text eol=lf +*.less text eol=lf +*.md text eol=lf +*.svg text eol=lf +*.yml text eol=lf +*.py text eol=lf +*.sh text eol=lf