Skip to content

Commit

Permalink
Implement initial version for websocket module
Browse files Browse the repository at this point in the history
  • Loading branch information
luismfonseca committed Aug 11, 2016
1 parent 8a7007e commit 73a01ce
Show file tree
Hide file tree
Showing 9 changed files with 1,448 additions and 1 deletion.
4 changes: 3 additions & 1 deletion app/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ SUBDIRS= \
dhtlib \
tsl2561 \
net \
http
http \
websocket

endif # } PDIR

Expand Down Expand Up @@ -87,6 +88,7 @@ COMPONENTS_eagle.app.v6 = \
dhtlib/libdhtlib.a \
tsl2561/tsl2561lib.a \
http/libhttp.a \
websocket/libwebsocket.a \
net/libnodemcu_net.a \
modules/libmodules.a \

Expand Down
1 change: 1 addition & 0 deletions app/include/user_modules.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
//#define LUA_USE_MODULES_U8G
#define LUA_USE_MODULES_UART
//#define LUA_USE_MODULES_UCG
//#define LUA_USE_MODULES_WEBSOCKET
#define LUA_USE_MODULES_WIFI
//#define LUA_USE_MODULES_WS2801
//#define LUA_USE_MODULES_WS2812
Expand Down
1 change: 1 addition & 0 deletions app/modules/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ INCLUDES += -I ../smart
INCLUDES += -I ../cjson
INCLUDES += -I ../dhtlib
INCLUDES += -I ../http
INCLUDES += -I ../websocket
PDIR := ../$(PDIR)
sinclude $(PDIR)Makefile

304 changes: 304 additions & 0 deletions app/modules/websocket.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// Module for websockets

// Example usage:
// ws = websocket.createClient()
// ws:onConnection(function() ws:send('hi') end)
// ws:onReceive(function(_, data, opcode) print(data) end)
// ws:onClose(function(_, reasonCode) print('ws closed', reasonCode) end)
// ws:connect('ws://example.com')

#include "lmem.h"
#include "lualib.h"
#include "lauxlib.h"
#include "platform.h"
#include "module.h"

#include "c_types.h"
#include "c_string.h"

#include "websocketclient.h"

#define METATABLE_WSCLIENT "websocket.client"

typedef struct ws_data {
int self_ref;
int onConnection;
int onReceive;
int onClose;
} ws_data;

static void websocketclient_onConnectionCallback(ws_info *ws) {
NODE_DBG("websocketclient_onConnectionCallback\n");

lua_State *L = lua_getstate();

if (ws == NULL || ws->reservedData == NULL) {
luaL_error(L, "Client websocket is nil.\n");
return;
}
ws_data *data = (ws_data *) ws->reservedData;

if (data->onConnection != LUA_NOREF) {
lua_rawgeti(L, LUA_REGISTRYINDEX, data->onConnection); // load the callback function
lua_rawgeti(L, LUA_REGISTRYINDEX, data->self_ref); // pass itself, #1 callback argument
lua_call(L, 1, 0);
}
}

static void websocketclient_onReceiveCallback(ws_info *ws, char *message, int opCode) {
NODE_DBG("websocketclient_onReceiveCallback\n");

lua_State *L = lua_getstate();

if (ws == NULL || ws->reservedData == NULL) {
luaL_error(L, "Client websocket is nil.\n");
return;
}
ws_data *data = (ws_data *) ws->reservedData;

if (data->onReceive != LUA_NOREF) {
lua_rawgeti(L, LUA_REGISTRYINDEX, data->onReceive); // load the callback function
lua_rawgeti(L, LUA_REGISTRYINDEX, data->self_ref); // pass itself, #1 callback argument
lua_pushstring(L, message); // #2 callback argument
lua_pushnumber(L, opCode); // #3 callback argument
lua_call(L, 3, 0);
}
}

static void websocketclient_onCloseCallback(ws_info *ws, int errorCode) {
NODE_DBG("websocketclient_onCloseCallback\n");

lua_State *L = lua_getstate();

if (ws == NULL || ws->reservedData == NULL) {
luaL_error(L, "Client websocket is nil.\n");
return;
}
ws_data *data = (ws_data *) ws->reservedData;

if (data->onClose != LUA_NOREF) {
lua_rawgeti(L, LUA_REGISTRYINDEX, data->onClose); // load the callback function
lua_rawgeti(L, LUA_REGISTRYINDEX, data->self_ref); // pass itself, #1 callback argument
lua_pushnumber(L, errorCode); // pass the error code, #2 callback argument
lua_call(L, 2, 0);
}

// free self-reference to allow gc (no futher callback will be called until next ws:connect())
lua_gc(L, LUA_GCSTOP, 0); // required to avoid freeing ws_data
luaL_unref(L, LUA_REGISTRYINDEX, data->self_ref);
data->self_ref = LUA_NOREF;
lua_gc(L, LUA_GCRESTART, 0);
}

static int websocket_createClient(lua_State *L) {
NODE_DBG("websocket_createClient\n");

// create user data
ws_data *data = (ws_data *) luaM_malloc(L, sizeof(ws_data));
data->onConnection = LUA_NOREF;
data->onReceive = LUA_NOREF;
data->onClose = LUA_NOREF;
data->self_ref = LUA_NOREF; // only set when ws:connect is called

ws_info *ws = (ws_info *) lua_newuserdata(L, sizeof(ws_info));
ws->connectionState = 0;
ws->onConnection = &websocketclient_onConnectionCallback;
ws->onReceive = &websocketclient_onReceiveCallback;
ws->onFailure = &websocketclient_onCloseCallback;
ws->reservedData = data;

// set its metatable
luaL_getmetatable(L, METATABLE_WSCLIENT);
lua_setmetatable(L, -2);

return 1;
}

static int websocketclient_onConnection(lua_State *L) {
NODE_DBG("websocketclient_onConnection\n");
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);
luaL_argcheck(L, ws, 1, "Client websocket expected");

ws_data *data = (ws_data *) ws->reservedData;

if (lua_type(L, 2) != LUA_TNIL && lua_type(L, 2) != LUA_TFUNCTION && lua_type(L, 2) != LUA_TLIGHTFUNCTION) {
return luaL_typerror(L, 2, "function or nil");
}

if (data->onConnection != LUA_NOREF) {
luaL_unref(L, LUA_REGISTRYINDEX, data->onConnection);
data->onConnection = LUA_NOREF;
}

if (lua_type(L, 2) != LUA_TNIL) {
lua_pushvalue(L, 2); // copy argument (func) to the top of stack
data->onConnection = luaL_ref(L, LUA_REGISTRYINDEX);
}

return 0;
}

static int websocketclient_onReceive(lua_State *L) {
NODE_DBG("websocketclient_onReceive\n");
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);
luaL_argcheck(L, ws, 1, "Client websocket expected");

ws_data *data = (ws_data *) ws->reservedData;

if (lua_type(L, 2) != LUA_TNIL && lua_type(L, 2) != LUA_TFUNCTION && lua_type(L, 2) != LUA_TLIGHTFUNCTION) {
return luaL_typerror(L, 2, "function or nil");
}

if (data->onReceive != LUA_NOREF) {
luaL_unref(L, LUA_REGISTRYINDEX, data->onReceive);
data->onReceive = LUA_NOREF;
}

if (lua_type(L, 2) != LUA_TNIL) {
lua_pushvalue(L, 2); // copy argument (func) to the top of stack
data->onReceive = luaL_ref(L, LUA_REGISTRYINDEX);
}

return 0;
}

static int websocketclient_onClose(lua_State *L) {
NODE_DBG("websocketclient_onClose\n");
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);
luaL_argcheck(L, ws, 1, "Client websocket expected");

ws_data *data = (ws_data *) ws->reservedData;

if (lua_type(L, 2) != LUA_TNIL && lua_type(L, 2) != LUA_TFUNCTION && lua_type(L, 2) != LUA_TLIGHTFUNCTION) {
return luaL_typerror(L, 2, "function or nil");
}

if (data->onClose != LUA_NOREF) {
luaL_unref(L, LUA_REGISTRYINDEX, data->onClose);
data->onClose = LUA_NOREF;
}

if (lua_type(L, 2) != LUA_TNIL) {
lua_pushvalue(L, 2); // copy argument (func) to the top of stack
data->onClose = luaL_ref(L, LUA_REGISTRYINDEX);
}

return 0;
}

static int websocketclient_connect(lua_State *L) {
NODE_DBG("websocketclient_connect is called.\n");

ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);
luaL_argcheck(L, ws, 1, "Client websocket expected");

ws_data *data = (ws_data *) ws->reservedData;

if (ws->connectionState != 0 && ws->connectionState != 4) {
return luaL_error(L, "Websocket already connecting or connected.\n");
}
ws->connectionState = 0;

lua_pushvalue(L, 1); // copy userdata to the top of stack to allow ref
data->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);

const char *url = luaL_checkstring(L, 2);
ws_connect(ws, url);

return 0;
}

static int websocketclient_send(lua_State *L) {
NODE_DBG("websocketclient_send is called.\n");

ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);
luaL_argcheck(L, ws, 1, "Client websocket expected");

ws_data *data = (ws_data *) ws->reservedData;

if (ws->connectionState != 3) {
// should this be an onFailure callback instead?
return luaL_error(L, "Websocket isn't connected.\n");
}

int msgLength;
const char *msg = luaL_checklstring(L, 2, &msgLength);

int opCode = 1; // default: text message
if (lua_gettop(L) == 3) {
opCode = luaL_checkint(L, 3);
}

ws_send(ws, opCode, msg, (unsigned short) msgLength);
return 0;
}

static int websocketclient_close(lua_State *L) {
NODE_DBG("websocketclient_close.\n");
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);

ws_close(ws);
return 0;
}

static int websocketclient_gc(lua_State *L) {
NODE_DBG("websocketclient_gc\n");

ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);
luaL_argcheck(L, ws, 1, "Client websocket expected");

ws_data *data = (ws_data *) ws->reservedData;

luaL_unref(L, LUA_REGISTRYINDEX, data->onConnection);
luaL_unref(L, LUA_REGISTRYINDEX, data->onReceive);

if (data->onClose != LUA_NOREF) {
if (ws->connectionState != 4) { // only call if connection open
lua_rawgeti(L, LUA_REGISTRYINDEX, data->onClose);

lua_pushnumber(L, -100);
lua_call(L, 1, 0);
}
luaL_unref(L, LUA_REGISTRYINDEX, data->onClose);
}

if (data->self_ref != LUA_NOREF) {
lua_gc(L, LUA_GCSTOP, 0); // required to avoid freeing ws_data
luaL_unref(L, LUA_REGISTRYINDEX, data->self_ref);
data->self_ref = LUA_NOREF;
lua_gc(L, LUA_GCRESTART, 0);
}

NODE_DBG("freeing lua data\n");
luaM_free(L, data);
NODE_DBG("done freeing lua data\n");

return 0;
}

static const LUA_REG_TYPE websocket_map[] =
{
{ LSTRKEY("createClient"), LFUNCVAL(websocket_createClient) },
{ LNILKEY, LNILVAL }
};

static const LUA_REG_TYPE websocketclient_map[] =
{
{ LSTRKEY("onConnection"), LFUNCVAL(websocketclient_onConnection) },
{ LSTRKEY("onReceive"), LFUNCVAL(websocketclient_onReceive) },
{ LSTRKEY("onClose"), LFUNCVAL(websocketclient_onClose) },
{ LSTRKEY("connect"), LFUNCVAL(websocketclient_connect) },
{ LSTRKEY("send"), LFUNCVAL(websocketclient_send) },
{ LSTRKEY("close"), LFUNCVAL(websocketclient_close) },
{ LSTRKEY("__gc" ), LFUNCVAL(websocketclient_gc) },
{ LSTRKEY("__index"), LROVAL(websocketclient_map) },
{ LNILKEY, LNILVAL }
};

int loadWebsocketModule(lua_State *L) {
luaL_rometatable(L, METATABLE_WSCLIENT, (void *) websocketclient_map);

return 0;
}

NODEMCU_MODULE(WEBSOCKET, "websocket", websocket_map, loadWebsocketModule);
49 changes: 49 additions & 0 deletions app/websocket/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

#############################################################
# 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 = libwebsocket.a
endif

STD_CFLAGS=-std=gnu11 -Wimplicit

#############################################################
# 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 ./include
INCLUDES += -I ../include
INCLUDES += -I ../libc
INCLUDES += -I ../../include
PDIR := ../$(PDIR)
sinclude $(PDIR)Makefile

Loading

0 comments on commit 73a01ce

Please sign in to comment.