diff --git a/app/include/user_modules.h b/app/include/user_modules.h index 27c4d4c1..7acf9234 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -27,6 +27,7 @@ #define LUA_USE_MODULES_HTTP //#define LUA_USE_MODULES_HX711 #define LUA_USE_MODULES_I2C +//#define LUA_USE_MODULES_MDNS #define LUA_USE_MODULES_MQTT #define LUA_USE_MODULES_NET #define LUA_USE_MODULES_NODE diff --git a/app/modules/mdns.c b/app/modules/mdns.c new file mode 100644 index 00000000..dc186864 --- /dev/null +++ b/app/modules/mdns.c @@ -0,0 +1,160 @@ +// Module for access to the espconn_mdns functions + +#include "module.h" +#include "lauxlib.h" + +#include "c_string.h" +#include "c_stdlib.h" + +#include "c_types.h" +#include "mem.h" +#include "lwip/ip_addr.h" +#include "espconn.h" +#include "user_interface.h" + +typedef struct wrapper { + struct mdns_info mdns_info; + char data; +} wrapper_t; + +static wrapper_t *info; + +typedef enum phase { + PHASE_CALCULATE_LENGTH, + PHASE_COPY_DATA +} phase_t; + +static char *advance_over_string(char *s) +{ + while (*s++) { + } + // s now points after the null + return s; +} + +// +// mdns.close() +// +static int mdns_close(lua_State *L) +{ + if (info) { + espconn_mdns_close(); + c_free(info); + info = NULL; + } + return 0; +} + +// +// this handles all the arguments. Two passes are necessary -- +// one to calculate the size of the block, and the other to +// copy the data. It is vitally important that these two +// passes are kept in step. +// +static wrapper_t *process_args(lua_State *L, phase_t phase, size_t *sizep) +{ + wrapper_t *result = NULL; + char *p = NULL; + + if (phase == PHASE_COPY_DATA) { + result = (wrapper_t *) c_zalloc(sizeof(wrapper_t) + *sizep); + if (!result) { + return NULL; + } + p = &result->data; + } + + if (phase == PHASE_CALCULATE_LENGTH) { + luaL_checktype(L, 1, LUA_TSTRING); + luaL_checktype(L, 2, LUA_TSTRING); + (void) luaL_checkinteger(L, 3); + *sizep += c_strlen(luaL_checkstring(L, 1)) + 1; + *sizep += c_strlen(luaL_checkstring(L, 2)) + 1; + } else { + c_strcpy(p, luaL_checkstring(L, 1)); + result->mdns_info.host_name = p; + p = advance_over_string(p); + + c_strcpy(p, luaL_checkstring(L, 2)); + result->mdns_info.server_name = p; + p = advance_over_string(p); + + result->mdns_info.server_port = luaL_checkinteger(L, 3); + } + + if (lua_gettop(L) >= 4) { + luaL_checktype(L, 4, LUA_TTABLE); + lua_pushnil(L); // first key + int slot = 0; + while (lua_next(L, 4) != 0 && slot < sizeof(result->mdns_info.txt_data) / sizeof(result->mdns_info.txt_data[0])) { + if (phase == PHASE_CALCULATE_LENGTH) { + luaL_checktype(L, -2, LUA_TSTRING); + *sizep += c_strlen(luaL_checkstring(L, -2)) + 1; + *sizep += c_strlen(luaL_checkstring(L, -1)) + 1; + } else { + // put in the key + c_strcpy(p, luaL_checkstring(L, -2)); + result->mdns_info.txt_data[slot] = p; + p = advance_over_string(p); + + // now smash in the value + const char *value = luaL_checkstring(L, -1); + p[-1] = '='; + c_strcpy(p, value); + p = advance_over_string(p); + } + lua_pop(L, 1); + } + } + + return result; +} + +// +// mdns.register(hostname, servicename, port [, attributes]) +// +static int mdns_register(lua_State *L) +{ + size_t len = 0; + + (void) process_args(L, PHASE_CALCULATE_LENGTH, &len); + + struct ip_info ipconfig; + + uint8_t mode = wifi_get_opmode(); + + if (!wifi_get_ip_info((mode == 2) ? SOFTAP_IF : STATION_IF, &ipconfig) || !ipconfig.ip.addr) { + return luaL_error(L, "No network connection"); + } + + wrapper_t *result = process_args(L, PHASE_COPY_DATA, &len); + + if (!result) { + return luaL_error( L, "failed to allocate info block" ); + } + + result->mdns_info.ipAddr = ipconfig.ip.addr; + + // Close up the old session (if any). This cannot fail + // so no chance of losing the memory in 'result' + + mdns_close(L); + + // Save the result as it appears that espconn_mdns_init needs + // to have the data valid while it is running. + + info = result; + + espconn_mdns_init(&(info->mdns_info)); + + return 0; +} + +// Module function map +static const LUA_REG_TYPE mdns_map[] = { + { LSTRKEY("register"), LFUNCVAL(mdns_register) }, + { LSTRKEY("close"), LFUNCVAL(mdns_close) }, + { LNILKEY, LNILVAL } +}; + +NODEMCU_MODULE(MDNS, "mdns", mdns_map, NULL); diff --git a/docs/en/modules/mdns.md b/docs/en/modules/mdns.md new file mode 100644 index 00000000..b80be6d0 --- /dev/null +++ b/docs/en/modules/mdns.md @@ -0,0 +1,38 @@ +# mdns Module + +Multicast DNS is used as part of Bonjour / Zeroconf. This allows system to identify themselves and the services that they provide on a local area network. Clients are then +able to discover these systems and connect to them. + +## mdns.register() +Register a hostname and start the mDns service. If the service is already running, then it +will be restarted with the new parameters. + +#### Syntax +`mdns.register(hostname, servicename, port [, attributes])` + +#### Parameters +- `hostname` The hostname for this device. Alphanumeric characters are best. +- `servicename` The servicename for this device. Alphanumeric characters are best. This will get prefixed with '_' and suffixed with ._tcp +- `port` The port number for the primary service. +- `attributes` A optional table of up to 10 attributes to be exposed. The keys must all be strings. + +#### Returns +Nothing. + +#### Errors +Various errors can be generated during argument validation. The nodemcu must have an IP address at the time of the call, otherwise an error is thrown. + +#### Example + + mdns.register("fishtank", "http", 80, { hardware='NodeMCU'}) + +Using dns-sd on OSX, you can see fishtank.local as providing the _http._tcp service. You can also browse directly to fishtank.local. In safari you can get all the mdns web pages as part of your bookmarks menu. + +## mdns.close() +Shut down the mDns service. This is not normally needed. + +#### Syntax +`mdns.close()` + +#### Returns +Nothing. diff --git a/mkdocs.yml b/mkdocs.yml index cd03b7eb..56627fb4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,7 @@ pages: - 'http': 'en/modules/http.md' - 'hx711' : 'en/modules/hx711.md' - 'i2c' : 'en/modules/i2c.md' + - 'mdns': 'en/modules/mdns.md' - 'mqtt': 'en/modules/mqtt.md' - 'net': 'en/modules/net.md' - 'node': 'en/modules/node.md'