diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..45aedeab --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: cpp +before_install: +- sudo apt-get install -y python-serial srecord +install: +- wget https://github.com/GeorgeHahn/nodemcu-firmware/raw/travis/tools/esp-open-sdk.tar.gz -O tools/esp-open-sdk.tar.gz +- tar -zxvf tools/esp-open-sdk.tar.gz +- export PATH=$PATH:$PWD/esp-open-sdk/sdk:$PWD/esp-open-sdk/xtensa-lx106-elf/bin +script: +- make all +- cd bin/ +- file_name="nodemcu-firmware_v${TRAVIS_TAG}.${TRAVIS_BUILD_NUMBER}.bin" +- srec_cat -output ${file_name} -binary 0x00000.bin -binary -fill 0xff 0x00000 0x10000 0x10000.bin -binary -offset 0x10000 +deploy: + provider: releases + api_key: + secure: Swecz5lWvsuSbchSbVQ1rmCPN9nQIN5p/HlZNIEdEgAgnoLcJxRV4P8poVTB37jiA8Pck+8x2nWXpg74Rqik0i3KlPNvDfg5o4rIazWLNs4bc1Tbcpt44XAzFKKLYnDnWQUGcqjk7BcAXuNAF2X/fPBCVhFbHVg3Z7cDb32RsNw= + file: "$TRAVIS_BUILD_DIR/bin/${file_name}" + skip_cleanup: true + on: + tags: true + repo: nodemcu/nodemcu-firmware diff --git a/README.md b/README.md index d5a142d4..fb55a684 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ -# **NodeMcu** # +# **NodeMCU** # version 0.9.5 + +[![Build Status](https://travis-ci.org/nodemcu/nodemcu-firmware.svg)](https://travis-ci.org/nodemcu/nodemcu-firmware) + ###A lua based firmware for wifi-soc esp8266 Build on [ESP8266 sdk 0.9.5](http://bbs.espressif.com/viewtopic.php?f=5&t=154)
Lua core based on [eLua project](http://www.eluaproject.net/)
@@ -7,24 +10,26 @@ File system based on [spiffs](https://github.com/pellepl/spiffs)
Open source development kit for NodeMCU [nodemcu-devkit](https://github.com/nodemcu/nodemcu-devkit)
Flash tool for NodeMCU [nodemcu-flasher](https://github.com/nodemcu/nodemcu-flasher)
-wiki: [nodemcu wiki](https://github.com/nodemcu/nodemcu-firmware/wiki)
-api: [nodemcu api](https://github.com/nodemcu/nodemcu-firmware/wiki/nodemcu_api_en)
+wiki: [NodeMCU wiki](https://github.com/nodemcu/nodemcu-firmware/wiki)
+api: [NodeMCU api](https://github.com/nodemcu/nodemcu-firmware/wiki/nodemcu_api_en)
home: [nodemcu.com](http://www.nodemcu.com)
bbs: [Chinese bbs](http://bbs.nodemcu.com)
+docs: [NodeMCU docs](http://www.nodemcu.com/docs/)
Tencent QQ group: 309957875
# Summary - Easy to access wireless router -- Based on Lua 5.1.4 (without *io, math, debug, os* module.) +- Based on Lua 5.1.4 (without *debug, os* module.) - Event-Drive programming preferred. -- Build-in file, timer, pwm, i2c, spi, 1-wire, net, mqtt, gpio, wifi, adc, uart and system api. +- Build-in file, timer, pwm, i2c, spi, 1-wire, net, mqtt, coap, gpio, wifi, adc, uart and system api. - GPIO pin re-mapped, use the index to access gpio, i2c, pwm. # To Do List (pull requests are very welcomed) +- loadable c module - fix wifi smart connect - add spi module (done) - add mqtt module (done) -- add coap module (in coap branch) +- add coap module (done) - cross compiler (done) # Change log @@ -98,15 +103,19 @@ build pre_build bin. #### [*] D0(GPIO16) can only be used as gpio read/write. no interrupt supported. no pwm/i2c/ow supported. #Build option -####file ./app/include/user_config.h +####file ./app/include/user_modules.h ```c -// #define FLASH_512K -// #define FLASH_1M -// #define FLASH_2M -// #define FLASH_4M -#define FLASH_AUTOSIZE -... +#define LUA_USE_BUILTIN_STRING // for string.xxx() +#define LUA_USE_BUILTIN_TABLE // for table.xxx() +#define LUA_USE_BUILTIN_COROUTINE // for coroutine.xxx() +#define LUA_USE_BUILTIN_MATH // for math.xxx(), partially work +// #define LUA_USE_BUILTIN_IO // for io.xxx(), partially work + +// #define LUA_USE_BUILTIN_OS // for os.xxx(), not work +// #define LUA_USE_BUILTIN_DEBUG // for debug.xxx(), not work + #define LUA_USE_MODULES + #ifdef LUA_USE_MODULES #define LUA_USE_MODULES_NODE #define LUA_USE_MODULES_FILE @@ -115,15 +124,17 @@ build pre_build bin. #define LUA_USE_MODULES_NET #define LUA_USE_MODULES_PWM #define LUA_USE_MODULES_I2C +#define LUA_USE_MODULES_SPI #define LUA_USE_MODULES_TMR #define LUA_USE_MODULES_ADC #define LUA_USE_MODULES_UART #define LUA_USE_MODULES_OW #define LUA_USE_MODULES_BIT +#define LUA_USE_MODULES_MQTT +// #define LUA_USE_MODULES_COAP // need about 4k more ram for now +#define LUA_USE_MODULES_U8G #define LUA_USE_MODULES_WS2812 #endif /* LUA_USE_MODULES */ -... -// LUA_NUMBER_INTEGRAL ``` #Flash the firmware diff --git a/app/.gitignore b/app/.gitignore index 2e2287fd..c6d8ed75 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,2 +1,3 @@ *.output* +mapfile !.gitignore diff --git a/app/Makefile b/app/Makefile index 0f318cea..78f95518 100644 --- a/app/Makefile +++ b/app/Makefile @@ -30,6 +30,7 @@ SUBDIRS= \ platform \ libc \ lua \ + coap \ mqtt \ u8glib \ smart \ @@ -77,6 +78,7 @@ COMPONENTS_eagle.app.v6 = \ platform/libplatform.a \ libc/liblibc.a \ lua/liblua.a \ + coap/coap.a \ mqtt/mqtt.a \ u8glib/u8glib.a \ smart/smart.a \ @@ -106,7 +108,8 @@ LINKFLAGS_eagle.app.v6 = \ -lsmartconfig \ -lssl \ $(DEP_LIBS_eagle.app.v6) \ - -Wl,--end-group + -Wl,--end-group \ + -lm DEPENDS_eagle.app.v6 = \ $(LD_FILE) \ diff --git a/app/coap/LICENSE.txt b/app/coap/LICENSE.txt new file mode 100644 index 00000000..4dd7b4c1 --- /dev/null +++ b/app/coap/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2013 Toby Jaffey + 2015 Zeroday Hong nodemcu.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/coap/Makefile b/app/coap/Makefile new file mode 100644 index 00000000..3947069c --- /dev/null +++ b/app/coap/Makefile @@ -0,0 +1,45 @@ + +############################################################# +# 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 = coap.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 +INCLUDES += -I ../lua +PDIR := ../$(PDIR) +sinclude $(PDIR)Makefile + diff --git a/app/coap/coap.c b/app/coap/coap.c new file mode 100644 index 00000000..6dded680 --- /dev/null +++ b/app/coap/coap.c @@ -0,0 +1,555 @@ +#include "user_config.h" +#include "c_stdio.h" +#include "c_string.h" +#include "coap.h" +#include "uri.h" + +extern void endpoint_setup(void); +extern const coap_endpoint_t endpoints[]; + +#ifdef COAP_DEBUG +void coap_dumpHeader(coap_header_t *hdr) +{ + c_printf("Header:\n"); + c_printf(" ver 0x%02X\n", hdr->ver); + c_printf(" t 0x%02X\n", hdr->ver); + c_printf(" tkl 0x%02X\n", hdr->tkl); + c_printf(" code 0x%02X\n", hdr->code); + c_printf(" id 0x%02X%02X\n", hdr->id[0], hdr->id[1]); +} + +void coap_dump(const uint8_t *buf, size_t buflen, bool bare) +{ + if (bare) + { + while(buflen--) + c_printf("%02X%s", *buf++, (buflen > 0) ? " " : ""); + } + else + { + c_printf("Dump: "); + while(buflen--) + c_printf("%02X%s", *buf++, (buflen > 0) ? " " : ""); + c_printf("\n"); + } +} +#endif + +int coap_parseHeader(coap_header_t *hdr, const uint8_t *buf, size_t buflen) +{ + if (buflen < 4) + return COAP_ERR_HEADER_TOO_SHORT; + hdr->ver = (buf[0] & 0xC0) >> 6; + if (hdr->ver != 1) + return COAP_ERR_VERSION_NOT_1; + hdr->t = (buf[0] & 0x30) >> 4; + hdr->tkl = buf[0] & 0x0F; + hdr->code = buf[1]; + hdr->id[0] = buf[2]; + hdr->id[1] = buf[3]; + return 0; +} + +int coap_buildHeader(const coap_header_t *hdr, uint8_t *buf, size_t buflen) +{ + // build header + if (buflen < 4) + return COAP_ERR_BUFFER_TOO_SMALL; + + buf[0] = (hdr->ver & 0x03) << 6; + buf[0] |= (hdr->t & 0x03) << 4; + buf[0] |= (hdr->tkl & 0x0F); + buf[1] = hdr->code; + buf[2] = hdr->id[0]; + buf[3] = hdr->id[1]; + return 4; +} + +int coap_parseToken(coap_buffer_t *tokbuf, const coap_header_t *hdr, const uint8_t *buf, size_t buflen) +{ + if (hdr->tkl == 0) + { + tokbuf->p = NULL; + tokbuf->len = 0; + return 0; + } + else + if (hdr->tkl <= 8) + { + if (4 + hdr->tkl > buflen) + return COAP_ERR_TOKEN_TOO_SHORT; // tok bigger than packet + tokbuf->p = buf+4; // past header + tokbuf->len = hdr->tkl; + return 0; + } + else + { + // invalid size + return COAP_ERR_TOKEN_TOO_SHORT; + } +} + +int coap_buildToken(const coap_buffer_t *tokbuf, const coap_header_t *hdr, uint8_t *buf, size_t buflen) +{ + // inject token + uint8_t *p; + if (buflen < 4 + hdr->tkl) + return COAP_ERR_BUFFER_TOO_SMALL; + p = buf + 4; + if ((hdr->tkl > 0) && (hdr->tkl != tokbuf->len)) + return COAP_ERR_UNSUPPORTED; + + if (hdr->tkl > 0) + c_memcpy(p, tokbuf->p, hdr->tkl); + + // http://tools.ietf.org/html/draft-ietf-core-coap-18#section-3.1 + // inject options + return hdr->tkl; +} + +// advances p +int coap_parseOption(coap_option_t *option, uint16_t *running_delta, const uint8_t **buf, size_t buflen) +{ + const uint8_t *p = *buf; + uint16_t len, delta; + uint8_t headlen = 1; + + if (buflen < headlen) // too small + return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; + + delta = (p[0] & 0xF0) >> 4; + len = p[0] & 0x0F; + + // These are untested and may be buggy + if (delta == 13) + { + headlen++; + if (buflen < headlen) + return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; + delta = p[1] + 13; + p++; + } + else + if (delta == 14) + { + headlen += 2; + if (buflen < headlen) + return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; + delta = ((p[1] << 8) | p[2]) + 269; + p+=2; + } + else + if (delta == 15) + return COAP_ERR_OPTION_DELTA_INVALID; + + if (len == 13) + { + headlen++; + if (buflen < headlen) + return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; + len = p[1] + 13; + p++; + } + else + if (len == 14) + { + headlen += 2; + if (buflen < headlen) + return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; + len = ((p[1] << 8) | p[2]) + 269; + p+=2; + } + else + if (len == 15) + return COAP_ERR_OPTION_LEN_INVALID; + + if ((p + 1 + len) > (*buf + buflen)) + return COAP_ERR_OPTION_TOO_BIG; + + //printf("option num=%d\n", delta + *running_delta); + option->num = delta + *running_delta; + option->buf.p = p+1; + option->buf.len = len; + //coap_dump(p+1, len, false); + + // advance buf + *buf = p + 1 + len; + *running_delta += delta; + + return 0; +} + +// http://tools.ietf.org/html/draft-ietf-core-coap-18#section-3.1 +int coap_parseOptionsAndPayload(coap_option_t *options, uint8_t *numOptions, coap_buffer_t *payload, const coap_header_t *hdr, const uint8_t *buf, size_t buflen) +{ + size_t optionIndex = 0; + const uint8_t *p = buf + 4 + hdr->tkl; + const uint8_t *end = buf + buflen; + int rc; + uint16_t delta = 0; + if (p > end) + return COAP_ERR_OPTION_OVERRUNS_PACKET; // out of bounds + + //coap_dump(p, end - p); + + // 0xFF is payload marker + while((optionIndex < *numOptions) && (p < end) && (*p != 0xFF)) + { + if (0 != (rc = coap_parseOption(&options[optionIndex], &delta, &p, end-p))) + return rc; + optionIndex++; + } + *numOptions = optionIndex; + + if (p+1 < end && *p == 0xFF) // payload marker + { + payload->p = p+1; + payload->len = end-(p+1); + } + else + { + payload->p = NULL; + payload->len = 0; + } + + return 0; +} + +int coap_buildOptionHeader(unsigned short optDelta, size_t length, uint8_t *buf, size_t buflen) +{ + int n = 0; + uint8_t *p = buf; + uint8_t len, delta = 0; + + if (buflen < 5) + return COAP_ERR_BUFFER_TOO_SMALL; + + coap_option_nibble(optDelta, &delta); + coap_option_nibble(length, &len); + + *p++ = (0xFF & (delta << 4 | len)); + n++; + if (delta == 13) + { + *p++ = (optDelta - 13); + n++; + } + else + if (delta == 14) + { + *p++ = ((optDelta-269) >> 8); + *p++ = (0xFF & (optDelta-269)); + n+=2; + } + if (len == 13) + { + *p++ = (length - 13); + n++; + } + else + if (len == 14) + { + *p++ = (length >> 8); + *p++ = (0xFF & (length-269)); + n+=2; + } + return n; +} + +#ifdef COAP_DEBUG +void coap_dumpOptions(coap_option_t *opts, size_t numopt) +{ + size_t i; + c_printf(" Options:\n"); + for (i=0;ihdr); + coap_dumpOptions(pkt->opts, pkt->numopts); + c_printf("Payload: "); + coap_dump(pkt->payload.p, pkt->payload.len, true); + c_printf("\n"); +} +#endif + +int coap_parse(coap_packet_t *pkt, const uint8_t *buf, size_t buflen) +{ + int rc; + + // coap_dump(buf, buflen, false); + + if (0 != (rc = coap_parseHeader(&pkt->hdr, buf, buflen))) + return rc; +// coap_dumpHeader(&hdr); + if (0 != (rc = coap_parseToken(&pkt->tok, &pkt->hdr, buf, buflen))) + return rc; + pkt->numopts = MAXOPT; + if (0 != (rc = coap_parseOptionsAndPayload(pkt->opts, &(pkt->numopts), &(pkt->payload), &pkt->hdr, buf, buflen))) + return rc; +// coap_dumpOptions(opts, numopt); + return 0; +} + +// options are always stored consecutively, so can return a block with same option num +const coap_option_t * coap_findOptions(const coap_packet_t *pkt, uint8_t num, uint8_t *count) +{ + // FIXME, options is always sorted, can find faster than this + size_t i; + const coap_option_t *first = NULL; + *count = 0; + for (i=0;inumopts;i++) + { + if (pkt->opts[i].num == num) + { + if (NULL == first) + first = &pkt->opts[i]; + (*count)++; + } + else + { + if (NULL != first) + break; + } + } + return first; +} + +int coap_buffer_to_string(char *strbuf, size_t strbuflen, const coap_buffer_t *buf) +{ + if (buf->len+1 > strbuflen) + return COAP_ERR_BUFFER_TOO_SMALL; + c_memcpy(strbuf, buf->p, buf->len); + strbuf[buf->len] = 0; + return 0; +} + +int coap_build(uint8_t *buf, size_t *buflen, const coap_packet_t *pkt) +{ + size_t opts_len = 0, hdr_len = 0, tok_len = 0; + size_t i; + uint8_t *p = buf; + size_t left = *buflen; + uint16_t running_delta = 0; + + hdr_len = coap_buildHeader(&(pkt->hdr), buf, *buflen); + p += hdr_len; + left -= hdr_len; + + tok_len = coap_buildToken(&(pkt->tok), &(pkt->hdr), buf, *buflen); + p += tok_len; + left -= tok_len; + + for (i=0;inumopts;i++) + { + uint8_t len, delta = 0; + uint16_t optDelta = 0; + int rc = 0; + + if (p-buf > *buflen) + return COAP_ERR_BUFFER_TOO_SMALL; + optDelta = pkt->opts[i].num - running_delta; + + rc = coap_buildOptionHeader(optDelta, pkt->opts[i].buf.len, p, left); + p += rc; + left -= rc; + + c_memcpy(p, pkt->opts[i].buf.p, pkt->opts[i].buf.len); + p += pkt->opts[i].buf.len; + left -= pkt->opts[i].buf.len; + running_delta = pkt->opts[i].num; + } + + opts_len = (p - buf) - 4; // number of bytes used by options + + if (pkt->payload.len > 0) + { + if (*buflen < 4 + 1 + pkt->payload.len + opts_len) + return COAP_ERR_BUFFER_TOO_SMALL; + buf[4 + opts_len] = 0xFF; // payload marker + c_memcpy(buf+5 + opts_len, pkt->payload.p, pkt->payload.len); + *buflen = opts_len + 5 + pkt->payload.len; + } + else + *buflen = opts_len + 4; + return 0; +} + +void coap_option_nibble(uint16_t value, uint8_t *nibble) +{ + if (value<13) + { + *nibble = (0xFF & value); + } + else + if (((uint32_t)value)<=0xFF+13) + { + *nibble = 13; + } else if (((uint32_t)value) -269 <=0xFFFF) + { + *nibble = 14; + } +} + +int coap_make_response(coap_rw_buffer_t *scratch, coap_packet_t *pkt, const uint8_t *content, size_t content_len, uint8_t msgid_hi, uint8_t msgid_lo, const coap_buffer_t* tok, coap_responsecode_t rspcode, coap_content_type_t content_type) +{ + pkt->hdr.ver = 0x01; + pkt->hdr.t = COAP_TYPE_ACK; + pkt->hdr.tkl = 0; + pkt->hdr.code = rspcode; + pkt->hdr.id[0] = msgid_hi; + pkt->hdr.id[1] = msgid_lo; + pkt->numopts = 0; + + // need token in response + if (tok) { + pkt->hdr.tkl = tok->len; + pkt->tok = *tok; + } + + // safe because 1 < MAXOPT + pkt->opts[0].num = COAP_OPTION_CONTENT_FORMAT; + pkt->opts[0].buf.p = scratch->p; + if (scratch->len < 2) + return COAP_ERR_BUFFER_TOO_SMALL; + scratch->p[0] = ((uint16_t)content_type & 0xFF00) >> 8; + scratch->p[1] = ((uint16_t)content_type & 0x00FF); + pkt->opts[0].buf.len = 2; + pkt->payload.p = content; + pkt->payload.len = content_len; + return 0; +} + + +unsigned int coap_encode_var_bytes(unsigned char *buf, unsigned int val) { + unsigned int n, i; + + for (n = 0, i = val; i && n < sizeof(val); ++n) + i >>= 8; + + i = n; + while (i--) { + buf[i] = val & 0xff; + val >>= 8; + } + + return n; +} + +static uint8_t _token_data[4]={'n','o','d','e'}; +coap_buffer_t the_token = { _token_data, 4 }; +static unsigned short message_id; + +int coap_make_request(coap_rw_buffer_t *scratch, coap_packet_t *pkt, coap_msgtype_t t, coap_method_t m, coap_uri_t *uri, const uint8_t *payload, size_t payload_len) +{ + int res; + pkt->hdr.ver = 0x01; + pkt->hdr.t = t; + pkt->hdr.tkl = 0; + pkt->hdr.code = m; + pkt->hdr.id[0] = (message_id >> 8) & 0xFF; //msgid_hi; + pkt->hdr.id[1] = message_id & 0xFF; //msgid_lo; + message_id++; + NODE_DBG("message_id: %d.\n", message_id); + pkt->numopts = 0; + + if (the_token.len) { + pkt->hdr.tkl = the_token.len; + pkt->tok = the_token; + } + + if (scratch->len < 2) // TBD... + return COAP_ERR_BUFFER_TOO_SMALL; + + uint8_t *saved = scratch->p; + + /* split arg into Uri-* options */ + // const char *addr = uri->host.s; + // if(uri->host.length && (c_strlen(addr) != uri->host.length || c_memcmp(addr, uri->host.s, uri->host.length) != 0)){ + if(uri->host.length){ + /* add Uri-Host */ + // addr is destination address + pkt->opts[pkt->numopts].num = COAP_OPTION_URI_HOST; + pkt->opts[pkt->numopts].buf.p = uri->host.s; + pkt->opts[pkt->numopts].buf.len = uri->host.length; + pkt->numopts++; + } + + if (uri->port != COAP_DEFAULT_PORT) { + pkt->opts[pkt->numopts].num = COAP_OPTION_URI_PORT; + res = coap_encode_var_bytes(scratch->p, uri->port); + pkt->opts[pkt->numopts].buf.len = res; + pkt->opts[pkt->numopts].buf.p = scratch->p; + scratch->p += res; + scratch->len -= res; + pkt->numopts++; + } + + if (uri->path.length) { + res = coap_split_path(scratch, pkt, uri->path.s, uri->path.length); + } + + if (uri->query.length) { + res = coap_split_query(scratch, pkt, uri->query.s, uri->query.length); + } + + pkt->payload.p = payload; + pkt->payload.len = payload_len; + scratch->p = saved; // save back the pointer. + return 0; +} + +// FIXME, if this looked in the table at the path before the method then +// it could more easily return 405 errors +int coap_handle_req(coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt) +{ + const coap_option_t *opt; + int i; + uint8_t count; + const coap_endpoint_t *ep = endpoints; + + while(NULL != ep->handler) + { + if (ep->method != inpkt->hdr.code) + goto next; + if (NULL != (opt = coap_findOptions(inpkt, COAP_OPTION_URI_PATH, &count))) + { + // if (count != ep->path->count) + if ((count != ep->path->count ) && (count != ep->path->count + 1)) // +1 for /f/[function], /v/[variable] + goto next; + for (i=0;ipath->count;i++) + { + if (opt[i].buf.len != c_strlen(ep->path->elems[i])) + goto next; + if (0 != c_memcmp(ep->path->elems[i], opt[i].buf.p, opt[i].buf.len)) + goto next; + } + // pre-path match! + if (count==ep->path->count+1 && ep->user_entry == NULL) + goto next; + return ep->handler(ep, scratch, inpkt, outpkt, inpkt->hdr.id[0], inpkt->hdr.id[1]); + } +next: + ep++; + } + + coap_make_response(scratch, outpkt, NULL, 0, inpkt->hdr.id[0], inpkt->hdr.id[1], &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE); + + return 0; +} + +void coap_setup(void) +{ + message_id = (unsigned short)rand(); // calculate only once +} + +inline int +check_token(coap_packet_t *pkt) { + return pkt->tok.len == the_token.len && c_memcmp(pkt->tok.p, the_token.p, the_token.len) == 0; +} diff --git a/app/coap/coap.h b/app/coap/coap.h new file mode 100644 index 00000000..1c8827a6 --- /dev/null +++ b/app/coap/coap.h @@ -0,0 +1,186 @@ +#ifndef COAP_H +#define COAP_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "c_stdint.h" +#include "c_stddef.h" +#include "lualib.h" +#include "lauxlib.h" + +#define MAXOPT 16 +#define MAX_MESSAGE_SIZE 1152 +#define MAX_PAYLOAD_SIZE 1024 +#define MAX_REQUEST_SIZE 576 +#define MAX_REQ_SCRATCH_SIZE 60 + +#define COAP_RESPONSE_CLASS(C) (((C) >> 5) & 0xFF) + +// http://tools.ietf.org/html/draft-ietf-core-coap-18#section-3 +typedef struct +{ + uint8_t ver; + uint8_t t; + uint8_t tkl; + uint8_t code; + uint8_t id[2]; +} coap_header_t; + +typedef struct +{ + const uint8_t *p; + size_t len; +} coap_buffer_t; + +typedef struct +{ + uint8_t *p; + size_t len; +} coap_rw_buffer_t; + +typedef struct +{ + uint8_t num; + coap_buffer_t buf; +} coap_option_t; + +typedef struct +{ + coap_header_t hdr; + coap_buffer_t tok; + uint8_t numopts; + coap_option_t opts[MAXOPT]; + coap_buffer_t payload; + coap_rw_buffer_t scratch; // scratch->p = malloc(...) , and free it when done. +} coap_packet_t; + +///////////////////////////////////////// + +//http://tools.ietf.org/html/draft-ietf-core-coap-18#section-12.2 +typedef enum +{ + COAP_OPTION_IF_MATCH = 1, + COAP_OPTION_URI_HOST = 3, + COAP_OPTION_ETAG = 4, + COAP_OPTION_IF_NONE_MATCH = 5, + COAP_OPTION_OBSERVE = 6, + COAP_OPTION_URI_PORT = 7, + COAP_OPTION_LOCATION_PATH = 8, + COAP_OPTION_URI_PATH = 11, + COAP_OPTION_CONTENT_FORMAT = 12, + COAP_OPTION_MAX_AGE = 14, + COAP_OPTION_URI_QUERY = 15, + COAP_OPTION_ACCEPT = 17, + COAP_OPTION_LOCATION_QUERY = 20, + COAP_OPTION_PROXY_URI = 35, + COAP_OPTION_PROXY_SCHEME = 39 +} coap_option_num_t; + +//http://tools.ietf.org/html/draft-ietf-core-coap-18#section-12.1.1 +typedef enum +{ + COAP_METHOD_GET = 1, + COAP_METHOD_POST = 2, + COAP_METHOD_PUT = 3, + COAP_METHOD_DELETE = 4 +} coap_method_t; + +//http://tools.ietf.org/html/draft-ietf-core-coap-18#section-12.1.1 +typedef enum +{ + COAP_TYPE_CON = 0, + COAP_TYPE_NONCON = 1, + COAP_TYPE_ACK = 2, + COAP_TYPE_RESET = 3 +} coap_msgtype_t; + +//http://tools.ietf.org/html/draft-ietf-core-coap-18#section-5.2 +//http://tools.ietf.org/html/draft-ietf-core-coap-18#section-12.1.2 +#define MAKE_RSPCODE(clas, det) ((clas << 5) | (det)) +typedef enum +{ + COAP_RSPCODE_CONTENT = MAKE_RSPCODE(2, 5), + COAP_RSPCODE_NOT_FOUND = MAKE_RSPCODE(4, 4), + COAP_RSPCODE_BAD_REQUEST = MAKE_RSPCODE(4, 0), + COAP_RSPCODE_CHANGED = MAKE_RSPCODE(2, 4) +} coap_responsecode_t; + +//http://tools.ietf.org/html/draft-ietf-core-coap-18#section-12.3 +typedef enum +{ + COAP_CONTENTTYPE_NONE = -1, // bodge to allow us not to send option block + COAP_CONTENTTYPE_TEXT_PLAIN = 0, + COAP_CONTENTTYPE_APPLICATION_LINKFORMAT = 40, +} coap_content_type_t; + +/////////////////////// + +typedef enum +{ + COAP_ERR_NONE = 0, + COAP_ERR_HEADER_TOO_SHORT = 1, + COAP_ERR_VERSION_NOT_1 = 2, + COAP_ERR_TOKEN_TOO_SHORT = 3, + COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER = 4, + COAP_ERR_OPTION_TOO_SHORT = 5, + COAP_ERR_OPTION_OVERRUNS_PACKET = 6, + COAP_ERR_OPTION_TOO_BIG = 7, + COAP_ERR_OPTION_LEN_INVALID = 8, + COAP_ERR_BUFFER_TOO_SMALL = 9, + COAP_ERR_UNSUPPORTED = 10, + COAP_ERR_OPTION_DELTA_INVALID = 11, +} coap_error_t; + +/////////////////////// +typedef struct coap_endpoint_t coap_endpoint_t; + +typedef int (*coap_endpoint_func)(const coap_endpoint_t *ep, coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt, uint8_t id_hi, uint8_t id_lo); +#define MAX_SEGMENTS 3 // 2 = /foo/bar, 3 = /foo/bar/baz +#define MAX_SEGMENTS_SIZE 16 +typedef struct +{ + int count; + const char *elems[MAX_SEGMENTS]; +} coap_endpoint_path_t; + +typedef struct coap_luser_entry coap_luser_entry; + +struct coap_luser_entry{ + lua_State *L; + // int ref; + // char name[MAX_SEGMENTS_SIZE+1]; // +1 for string '\0' + const char *name; + coap_luser_entry *next; +}; + +struct coap_endpoint_t +{ + coap_method_t method; + coap_endpoint_func handler; + const coap_endpoint_path_t *path; + const char *core_attr; + coap_luser_entry *user_entry; +}; + +/////////////////////// +void coap_dumpPacket(coap_packet_t *pkt); +int coap_parse(coap_packet_t *pkt, const uint8_t *buf, size_t buflen); +int coap_buffer_to_string(char *strbuf, size_t strbuflen, const coap_buffer_t *buf); +const coap_option_t *coap_findOptions(const coap_packet_t *pkt, uint8_t num, uint8_t *count); +int coap_build(uint8_t *buf, size_t *buflen, const coap_packet_t *pkt); +void coap_dump(const uint8_t *buf, size_t buflen, bool bare); +int coap_make_response(coap_rw_buffer_t *scratch, coap_packet_t *pkt, const uint8_t *content, size_t content_len, uint8_t msgid_hi, uint8_t msgid_lo, const coap_buffer_t* tok, coap_responsecode_t rspcode, coap_content_type_t content_type); +int coap_handle_req(coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt); +void coap_option_nibble(uint16_t value, uint8_t *nibble); +void coap_setup(void); +void endpoint_setup(void); + +int coap_buildOptionHeader(unsigned short optDelta, size_t length, uint8_t *buf, size_t buflen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/coap/coap_client.c b/app/coap/coap_client.c new file mode 100644 index 00000000..37a1a5f0 --- /dev/null +++ b/app/coap/coap_client.c @@ -0,0 +1,67 @@ +#include "user_config.h" +#include "c_types.h" + +#include "coap.h" +#include "hash.h" +#include "node.h" + +extern coap_queue_t *gQueue; + +void coap_client_response_handler(char *data, unsigned short len, unsigned short size, const uint32_t ip, const uint32_t port) +{ + NODE_DBG("coap_client_response_handler is called.\n"); + coap_packet_t pkt; + int rc; + + if (0 != (rc = coap_parse(&pkt, data, len))){ + NODE_DBG("Bad packet rc=%d\n", rc); + } + else + { +#ifdef COAP_DEBUG + coap_dumpPacket(&pkt); +#endif + /* check if this is a response to our original request */ + if (!check_token(&pkt)) { + /* drop if this was just some message, or send RST in case of notification */ + if (pkt.hdr.t == COAP_TYPE_CON || pkt.hdr.t == COAP_TYPE_NONCON){ + // coap_send_rst(pkt); // send RST response + // or, just ignore it. + } + goto end; + } + + if (pkt.hdr.t == COAP_TYPE_RESET) { + NODE_DBG("got RST\n"); + goto end; + } + + coap_tid_t id = COAP_INVALID_TID; + coap_transaction_id(ip, port, &pkt, &id); + /* transaction done, remove the node from queue */ + // stop timer + coap_timer_stop(); + // remove the node + coap_remove_node(&gQueue, id); + // calculate time elapsed + coap_timer_update(&gQueue); + coap_timer_start(&gQueue); + + if (COAP_RESPONSE_CLASS(pkt.hdr.code) == 2) + { + /* There is no block option set, just read the data and we are done. */ + NODE_DBG("%d.%02d\t", (pkt.hdr.code >> 5), pkt.hdr.code & 0x1F); + NODE_DBG((char *)(pkt.payload.p)); + } + else if (COAP_RESPONSE_CLASS(pkt.hdr.code) >= 4) + { + NODE_DBG("%d.%02d\t", (pkt.hdr.code >> 5), pkt.hdr.code & 0x1F); + NODE_DBG((char *)(pkt.payload.p)); + } + } + +end: + if(!gQueue){ // if there is no node pending in the queue, disconnect from host. + + } +} diff --git a/app/coap/coap_client.h b/app/coap/coap_client.h new file mode 100644 index 00000000..5559adfe --- /dev/null +++ b/app/coap/coap_client.h @@ -0,0 +1,14 @@ +#ifndef _COAP_SERVER_H +#define _COAP_SERVER_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +size_t coap_server_respond(char *data, unsigned short len, unsigned short size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/coap/coap_io.c b/app/coap/coap_io.c new file mode 100644 index 00000000..4b93356c --- /dev/null +++ b/app/coap/coap_io.c @@ -0,0 +1,71 @@ +#include "c_string.h" +#include "coap_io.h" +#include "node.h" +#include "espconn.h" +#include "coap_timer.h" + +extern coap_queue_t *gQueue; + +/* releases space allocated by PDU if free_pdu is set */ +coap_tid_t coap_send(struct espconn *pesp_conn, coap_pdu_t *pdu) { + coap_tid_t id = COAP_INVALID_TID; + uint32_t ip = 0, port = 0; + if ( !pesp_conn || !pdu ) + return id; + + espconn_sent(pesp_conn, (unsigned char *)(pdu->msg.p), pdu->msg.len); + + if(pesp_conn->type == ESPCONN_TCP){ + c_memcpy(&ip, pesp_conn->proto.tcp->remote_ip, sizeof(ip)); + port = pesp_conn->proto.tcp->remote_port; + }else{ + c_memcpy(&ip, pesp_conn->proto.udp->remote_ip, sizeof(ip)); + port = pesp_conn->proto.udp->remote_port; + } + coap_transaction_id(ip, port, pdu->pkt, &id); + return id; +} + +coap_tid_t coap_send_confirmed(struct espconn *pesp_conn, coap_pdu_t *pdu) { + coap_queue_t *node; + coap_tick_t diff; + uint32_t r; + + node = coap_new_node(); + if (!node) { + NODE_DBG("coap_send_confirmed: insufficient memory\n"); + return COAP_INVALID_TID; + } + + node->retransmit_cnt = 0; + node->id = coap_send(pesp_conn, pdu); + if (COAP_INVALID_TID == node->id) { + NODE_DBG("coap_send_confirmed: error sending pdu\n"); + coap_free_node(node); + return COAP_INVALID_TID; + } + r = rand(); + + /* add randomized RESPONSE_TIMEOUT to determine retransmission timeout */ + node->timeout = COAP_DEFAULT_RESPONSE_TIMEOUT * COAP_TICKS_PER_SECOND + + (COAP_DEFAULT_RESPONSE_TIMEOUT >> 1) * + ((COAP_TICKS_PER_SECOND * (r & 0xFF)) >> 8); + + node->pconn = pesp_conn; + node->pdu = pdu; + + /* Set timer for pdu retransmission. If this is the first element in + * the retransmission queue, the base time is set to the current + * time and the retransmission time is node->timeout. If there is + * already an entry in the sendqueue, we must check if this node is + * to be retransmitted earlier. Therefore, node->timeout is first + * normalized to the timeout and then inserted into the queue with + * an adjusted relative time. + */ + coap_timer_stop(); + coap_timer_update(&gQueue); + node->t = node->timeout; + coap_insert_node(&gQueue, node); + coap_timer_start(&gQueue); + return node->id; +} diff --git a/app/coap/coap_io.h b/app/coap/coap_io.h new file mode 100644 index 00000000..801b7328 --- /dev/null +++ b/app/coap/coap_io.h @@ -0,0 +1,20 @@ +#ifndef _COAP_IO_H +#define _COAP_IO_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "espconn.h" +#include "pdu.h" +#include "hash.h" + +coap_tid_t coap_send(struct espconn *pesp_conn, coap_pdu_t *pdu); + +coap_tid_t coap_send_confirmed(struct espconn *pesp_conn, coap_pdu_t *pdu); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/coap/coap_server.c b/app/coap/coap_server.c new file mode 100644 index 00000000..d87bd8f7 --- /dev/null +++ b/app/coap/coap_server.c @@ -0,0 +1,53 @@ +#include "user_config.h" +#include "c_types.h" + +#include "coap.h" + +size_t coap_server_respond(char *data, unsigned short len, unsigned short size) +{ + NODE_DBG("coap_server_respond is called.\n"); + if(len>size){ + NODE_DBG("len:%d, size:%d\n",len,size); + return 0; + } + size_t rsplen = size; + coap_packet_t pkt; + uint8_t scratch_raw[4]; + coap_rw_buffer_t scratch_buf = {scratch_raw, sizeof(scratch_raw)}; + int rc; + +#ifdef COAP_DEBUG + NODE_DBG("Received: "); + coap_dump(data, len, true); + NODE_DBG("\n"); +#endif + + if (0 != (rc = coap_parse(&pkt, data, len))){ + NODE_DBG("Bad packet rc=%d\n", rc); + return 0; + } + else + { + coap_packet_t rsppkt; +#ifdef COAP_DEBUG + coap_dumpPacket(&pkt); +#endif + coap_handle_req(&scratch_buf, &pkt, &rsppkt); + if (0 != (rc = coap_build(data, &rsplen, &rsppkt))){ + NODE_DBG("coap_build failed rc=%d\n", rc); + return 0; + } + else + { +#ifdef COAP_DEBUG + NODE_DBG("Responding: "); + coap_dump(data, rsplen, true); + NODE_DBG("\n"); +#endif +#ifdef COAP_DEBUG + coap_dumpPacket(&rsppkt); +#endif + } + return rsplen; + } +} diff --git a/app/coap/coap_server.h b/app/coap/coap_server.h new file mode 100644 index 00000000..5559adfe --- /dev/null +++ b/app/coap/coap_server.h @@ -0,0 +1,14 @@ +#ifndef _COAP_SERVER_H +#define _COAP_SERVER_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +size_t coap_server_respond(char *data, unsigned short len, unsigned short size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/coap/coap_timer.c b/app/coap/coap_timer.c new file mode 100644 index 00000000..9856ef83 --- /dev/null +++ b/app/coap/coap_timer.c @@ -0,0 +1,78 @@ +#include "node.h" +#include "coap_timer.h" +#include "os_type.h" + +static os_timer_t coap_timer; +static coap_tick_t basetime = 0; + +void coap_timer_elapsed(coap_tick_t *diff){ + coap_tick_t now = system_get_time() / 1000; // coap_tick_t is in ms. also sys_timer + if(now>=basetime){ + *diff = now-basetime; + } else { + *diff = now + SYS_TIME_MAX -basetime; + } + basetime = now; +} + +void coap_timer_tick(void *arg){ + if( !arg ) + return; + coap_queue_t **queue = (coap_queue_t **)arg; + if( !(*queue) ) + return; + + coap_queue_t *node = coap_pop_next( queue ); + /* re-initialize timeout when maximum number of retransmissions are not reached yet */ + if (node->retransmit_cnt < COAP_DEFAULT_MAX_RETRANSMIT) { + node->retransmit_cnt++; + node->t = node->timeout << node->retransmit_cnt; + + NODE_DBG("** retransmission #%d of transaction %d\n", + node->retransmit_cnt, (((uint16_t)(node->pdu->pkt->hdr.id[0]))<<8)+node->pdu->pkt->hdr.id[1]); + node->id = coap_send(node->pconn, node->pdu); + if (COAP_INVALID_TID == node->id) { + NODE_DBG("retransmission: error sending pdu\n"); + coap_delete_node(node); + } else { + coap_insert_node(queue, node); + } + } else { + /* And finally delete the node */ + coap_delete_node( node ); + } + + coap_timer_start(queue); +} + +void coap_timer_setup(coap_queue_t ** queue, coap_tick_t t){ + os_timer_disarm(&coap_timer); + os_timer_setfn(&coap_timer, (os_timer_func_t *)coap_timer_tick, queue); + os_timer_arm(&coap_timer, t, 0); // no repeat +} + +void coap_timer_stop(void){ + os_timer_disarm(&coap_timer); +} + +void coap_timer_update(coap_queue_t ** queue){ + if (!queue) + return; + coap_tick_t diff = 0; + coap_queue_t *first = *queue; + coap_timer_elapsed(&diff); // update: basetime = now, diff = now - oldbase, means time elapsed + if (first) { + // diff ms time is elapsed, re-calculate the first node->t + if (first->t >= diff){ + first->t -= diff; + } else { + first->t = 0; // when timer enabled, time out almost immediately + } + } +} + +void coap_timer_start(coap_queue_t ** queue){ + if(*queue){ // if there is node in the queue, set timeout to its ->t. + coap_timer_setup(queue, (*queue)->t); + } +} diff --git a/app/coap/coap_timer.h b/app/coap/coap_timer.h new file mode 100644 index 00000000..7f14a527 --- /dev/null +++ b/app/coap/coap_timer.h @@ -0,0 +1,31 @@ +#ifndef _COAP_TIMER_H +#define _COAP_TIMER_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "node.h" + +#define SYS_TIME_MAX (0xFFFFFFFF / 1000) + +#define COAP_DEFAULT_RESPONSE_TIMEOUT 2 /* response timeout in seconds */ +#define COAP_DEFAULT_MAX_RETRANSMIT 4 /* max number of retransmissions */ +#define COAP_TICKS_PER_SECOND 1000 // ms +#define DEFAULT_MAX_TRANSMIT_WAIT 90 + +void coap_timer_elapsed(coap_tick_t *diff); + +void coap_timer_setup(coap_queue_t ** queue, coap_tick_t t); + +void coap_timer_stop(void); + +void coap_timer_update(coap_queue_t ** queue); + +void coap_timer_start(coap_queue_t ** queue); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/coap/endpoints.c b/app/coap/endpoints.c new file mode 100644 index 00000000..a3d2d9d6 --- /dev/null +++ b/app/coap/endpoints.c @@ -0,0 +1,282 @@ +#include "c_stdio.h" +#include "c_string.h" +#include "coap.h" + +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" + +#include "os_type.h" + +static char rsp[MAX_PAYLOAD_SIZE] = ""; +const uint16_t rsplen = MAX_PAYLOAD_SIZE; +void build_well_known_rsp(void); + +void endpoint_setup(void) +{ + coap_setup(); + build_well_known_rsp(); +} + +static const coap_endpoint_path_t path_well_known_core = {2, {".well-known", "core"}}; +static int handle_get_well_known_core(const coap_endpoint_t *ep, coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt, uint8_t id_hi, uint8_t id_lo) +{ + return coap_make_response(scratch, outpkt, (const uint8_t *)rsp, c_strlen(rsp), id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_CONTENT, COAP_CONTENTTYPE_APPLICATION_LINKFORMAT); +} + +static const coap_endpoint_path_t path_variable = {2, {"v1", "v"}}; +static int handle_get_variable(const coap_endpoint_t *ep, coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt, uint8_t id_hi, uint8_t id_lo) +{ + const coap_option_t *opt; + uint8_t count; + if (NULL != (opt = coap_findOptions(inpkt, COAP_OPTION_URI_PATH, &count))) + { + if ((count != ep->path->count ) && (count != ep->path->count + 1)) // +1 for /f/[function], /v/[variable] + { + NODE_DBG("should never happen.\n"); + goto end; + } + if (count == ep->path->count + 1) + { + coap_luser_entry *h = ep->user_entry->next; // ->next: skip the first entry(head) + while(NULL != h){ + if (opt[count-1].buf.len != c_strlen(h->name)) + { + h = h->next; + continue; + } + if (0 == c_memcmp(h->name, opt[count-1].buf.p, opt[count-1].buf.len)) + { + NODE_DBG("/v1/v/"); + NODE_DBG((char *)h->name); + NODE_DBG(" match.\n"); + if(h->L == NULL) + return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE); + if(c_strlen(h->name)) + { + lua_getglobal(h->L, h->name); + if (!lua_isnumber(h->L, -1)) { + NODE_DBG ("should be a number.\n"); + lua_pop(h->L, 1); + return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE); + } else { + const char *res = lua_tostring(h->L,-1); + lua_pop(h->L, 1); + return coap_make_response(scratch, outpkt, (const uint8_t *)res, c_strlen(res), id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_CONTENT, COAP_CONTENTTYPE_TEXT_PLAIN); + } + } + } else { + h = h->next; + } + } + }else{ + NODE_DBG("/v1/v match.\n"); + goto end; + } + } + NODE_DBG("none match.\n"); +end: + return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_CONTENT, COAP_CONTENTTYPE_TEXT_PLAIN); +} + +static const coap_endpoint_path_t path_function = {2, {"v1", "f"}}; +static int handle_post_function(const coap_endpoint_t *ep, coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt, uint8_t id_hi, uint8_t id_lo) +{ + const coap_option_t *opt; + uint8_t count; + if (NULL != (opt = coap_findOptions(inpkt, COAP_OPTION_URI_PATH, &count))) + { + if ((count != ep->path->count ) && (count != ep->path->count + 1)) // +1 for /f/[function], /v/[variable] + { + NODE_DBG("should never happen.\n"); + goto end; + } + if (count == ep->path->count + 1) + { + coap_luser_entry *h = ep->user_entry->next; // ->next: skip the first entry(head) + while(NULL != h){ + if (opt[count-1].buf.len != c_strlen(h->name)) + { + h = h->next; + continue; + } + if (0 == c_memcmp(h->name, opt[count-1].buf.p, opt[count-1].buf.len)) + { + NODE_DBG("/v1/f/"); + NODE_DBG((char *)h->name); + NODE_DBG(" match.\n"); + + if(h->L == NULL) + return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE); + + if(c_strlen(h->name)) + { + lua_getglobal(h->L, h->name); + if (lua_type(h->L, -1) != LUA_TFUNCTION) { + NODE_DBG ("should be a function\n"); + lua_pop(h->L, 1); + return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE); + } else { + lua_pushlstring(h->L, inpkt->payload.p, inpkt->payload.len); // make sure payload.p is filled with '\0' after payload.len, or use lua_pushlstring + lua_call(h->L, 1, 1); + if (!lua_isnil(h->L, -1)){ /* get return? */ + if( lua_isstring(h->L, -1) ) // deal with the return string + { + size_t len = 0; + const char *ret = luaL_checklstring( h->L, -1, &len ); + if(len > MAX_PAYLOAD_SIZE){ + luaL_error( h->L, "return string:tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE); + } + NODE_DBG((char *)ret); + NODE_DBG("\n"); + return coap_make_response(scratch, outpkt, ret, len, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_CONTENT, COAP_CONTENTTYPE_TEXT_PLAIN); + } + } else { + return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_CONTENT, COAP_CONTENTTYPE_TEXT_PLAIN); + } + } + } + } else { + h = h->next; + } + } + }else{ + NODE_DBG("/v1/f match.\n"); + goto end; + } + } + NODE_DBG("none match.\n"); +end: + return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE); +} + +extern lua_Load gLoad; +extern os_timer_t lua_timer; +extern void dojob(lua_Load *load); +static const coap_endpoint_path_t path_command = {2, {"v1", "c"}}; +static int handle_post_command(const coap_endpoint_t *ep, coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt, uint8_t id_hi, uint8_t id_lo) +{ + if (inpkt->payload.len == 0) + return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_BAD_REQUEST, COAP_CONTENTTYPE_TEXT_PLAIN); + if (inpkt->payload.len > 0) + { + lua_Load *load = &gLoad; + if(load->line_position == 0){ + coap_buffer_to_string(load->line, load->len,&inpkt->payload); + load->line_position = c_strlen(load->line)+1; + // load->line[load->line_position-1] = '\n'; + // load->line[load->line_position] = 0; + // load->line_position++; + load->done = 1; + NODE_DBG("Get command:\n"); + NODE_DBG(load->line); // buggy here + NODE_DBG("\nResult(if any):\n"); + os_timer_disarm(&lua_timer); + os_timer_setfn(&lua_timer, (os_timer_func_t *)dojob, load); + os_timer_arm(&lua_timer, READLINE_INTERVAL, 0); // no repeat + } + return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_CONTENT, COAP_CONTENTTYPE_TEXT_PLAIN); + } +} + +static uint32_t id = 0; +static const coap_endpoint_path_t path_id = {2, {"v1", "id"}}; +static int handle_get_id(const coap_endpoint_t *ep, coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt, uint8_t id_hi, uint8_t id_lo) +{ + id = system_get_chip_id(); + return coap_make_response(scratch, outpkt, (const uint8_t *)(&id), sizeof(uint32_t), id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_CONTENT, COAP_CONTENTTYPE_TEXT_PLAIN); +} + +coap_luser_entry var_head = {NULL,NULL,NULL}; +coap_luser_entry *variable_entry = &var_head; + +coap_luser_entry func_head = {NULL,NULL,NULL}; +coap_luser_entry *function_entry = &func_head; + +const coap_endpoint_t endpoints[] = +{ + {COAP_METHOD_GET, handle_get_well_known_core, &path_well_known_core, "ct=40", NULL}, + {COAP_METHOD_GET, handle_get_variable, &path_variable, "ct=0", &var_head}, + {COAP_METHOD_POST, handle_post_function, &path_function, NULL, &func_head}, + {COAP_METHOD_POST, handle_post_command, &path_command, NULL, NULL}, + {COAP_METHOD_GET, handle_get_id, &path_id, "ct=0", NULL}, + {(coap_method_t)0, NULL, NULL, NULL, NULL} +}; + +void build_well_known_rsp(void) +{ + const coap_endpoint_t *ep = endpoints; + int i; + uint16_t len = rsplen; + + c_memset(rsp, 0, sizeof(rsp)); + + len--; // Null-terminated string + + while(NULL != ep->handler) + { + if (NULL == ep->core_attr) { + ep++; + continue; + } + if (NULL == ep->user_entry){ + if (0 < c_strlen(rsp)) { + c_strncat(rsp, ",", len); + len--; + } + + c_strncat(rsp, "<", len); + len--; + + for (i = 0; i < ep->path->count; i++) { + c_strncat(rsp, "/", len); + len--; + + c_strncat(rsp, ep->path->elems[i], len); + len -= c_strlen(ep->path->elems[i]); + } + + c_strncat(rsp, ">;", len); + len -= 2; + + c_strncat(rsp, ep->core_attr, len); + len -= c_strlen(ep->core_attr); + } else { + coap_luser_entry *h = ep->user_entry->next; // ->next: skip the first entry(head) + while(NULL != h){ + if (0 < c_strlen(rsp)) { + c_strncat(rsp, ",", len); + len--; + } + + c_strncat(rsp, "<", len); + len--; + + for (i = 0; i < ep->path->count; i++) { + c_strncat(rsp, "/", len); + len--; + + c_strncat(rsp, ep->path->elems[i], len); + len -= c_strlen(ep->path->elems[i]); + } + + c_strncat(rsp, "/", len); + len--; + + c_strncat(rsp, h->name, len); + len -= c_strlen(h->name); + + c_strncat(rsp, ">;", len); + len -= 2; + + c_strncat(rsp, ep->core_attr, len); + len -= c_strlen(ep->core_attr); + + h = h->next; + } + } + ep++; + } +} + diff --git a/app/coap/hash.c b/app/coap/hash.c new file mode 100644 index 00000000..6e6c3a24 --- /dev/null +++ b/app/coap/hash.c @@ -0,0 +1,30 @@ +#include "hash.h" +#include "c_string.h" +/* Caution: When changing this, update COAP_DEFAULT_WKC_HASHKEY + * accordingly (see int coap_hash_path()); + */ +void coap_hash(const unsigned char *s, unsigned int len, coap_key_t h) { + size_t j; + + while (len--) { + j = sizeof(coap_key_t)-1; + + while (j) { + h[j] = ((h[j] << 7) | (h[j-1] >> 1)) + h[j]; + --j; + } + + h[0] = (h[0] << 7) + h[0] + *s++; + } +} + +void coap_transaction_id(const uint32_t ip, const uint32_t port, const coap_packet_t *pkt, coap_tid_t *id) { + coap_key_t h; + c_memset(h, 0, sizeof(coap_key_t)); + + /* Compare the transport address. */ + coap_hash((const unsigned char *)&(port), sizeof(port), h); + coap_hash((const unsigned char *)&(ip), sizeof(ip), h); + coap_hash((const unsigned char *)(pkt->hdr.id), sizeof(pkt->hdr.id), h); + *id = ((h[0] << 8) | h[1]) ^ ((h[2] << 8) | h[3]); +} diff --git a/app/coap/hash.h b/app/coap/hash.h new file mode 100644 index 00000000..3980089d --- /dev/null +++ b/app/coap/hash.h @@ -0,0 +1,23 @@ +#ifndef _HASH_H +#define _HASH_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "coap.h" + +typedef unsigned char coap_key_t[4]; + +/* CoAP transaction id */ +/*typedef unsigned short coap_tid_t; */ +typedef int coap_tid_t; +#define COAP_INVALID_TID -1 + +void coap_transaction_id(const uint32_t ip, const uint32_t port, const coap_packet_t *pkt, coap_tid_t *id); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/coap/node.c b/app/coap/node.c new file mode 100644 index 00000000..5eeb7269 --- /dev/null +++ b/app/coap/node.c @@ -0,0 +1,141 @@ +#include "c_string.h" +#include "c_stdlib.h" +#include "node.h" + +static inline coap_queue_t * +coap_malloc_node(void) { + return (coap_queue_t *)c_zalloc(sizeof(coap_queue_t)); +} + +void coap_free_node(coap_queue_t *node) { + c_free(node); +} + +int coap_insert_node(coap_queue_t **queue, coap_queue_t *node) { + coap_queue_t *p, *q; + if ( !queue || !node ) + return 0; + + /* set queue head if empty */ + if ( !*queue ) { + *queue = node; + return 1; + } + + /* replace queue head if PDU's time is less than head's time */ + q = *queue; + if (node->t < q->t) { + node->next = q; + *queue = node; + q->t -= node->t; /* make q->t relative to node->t */ + return 1; + } + + /* search for right place to insert */ + do { + node->t -= q->t; /* make node-> relative to q->t */ + p = q; + q = q->next; + } while (q && q->t <= node->t); + + /* insert new item */ + if (q) { + q->t -= node->t; /* make q->t relative to node->t */ + } + node->next = q; + p->next = node; + return 1; +} + +int coap_delete_node(coap_queue_t *node) { + if ( !node ) + return 0; + + coap_delete_pdu(node->pdu); + coap_free_node(node); + + return 1; +} + +void coap_delete_all(coap_queue_t *queue) { + if ( !queue ) + return; + + coap_delete_all( queue->next ); + coap_delete_node( queue ); +} + +coap_queue_t * coap_new_node(void) { + coap_queue_t *node; + node = coap_malloc_node(); + + if ( ! node ) { + return NULL; + } + + c_memset(node, 0, sizeof(*node)); + return node; +} + +coap_queue_t * coap_peek_next( coap_queue_t *queue ) { + if ( !queue ) + return NULL; + + return queue; +} + +coap_queue_t * coap_pop_next( coap_queue_t **queue ) { // this function is called inside timeout callback only. + coap_queue_t *next; + + if ( !(*queue) ) + return NULL; + + next = *queue; + *queue = (*queue)->next; + // if (queue) { + // queue->t += next->t; + // } + next->next = NULL; + return next; +} + +int coap_remove_node( coap_queue_t **queue, const coap_tid_t id){ + coap_queue_t *p, *q, *node; + if ( !queue ) + return 0; + if ( !*queue ) // if empty + return 0; + + q = *queue; + if (q->id == id) { + node = q; + *queue = q->next; + node->next = NULL; + if(*queue){ + (*queue)->t += node->t; + } + coap_delete_node(node); + return 1; + } + + /* search for right node to remove */ + while (q && q->id != id) { + p = q; + q = q->next; + } + + /* find the node */ + if (q) { + node = q; /* save the node */ + p->next = q->next; /* remove the node */ + q = q->next; + node->next = NULL; + if (q) // add node->t to the node after. + { + q->t += node->t; + } + coap_delete_node(node); + return 1; + } + return 0; +} diff --git a/app/coap/node.h b/app/coap/node.h new file mode 100644 index 00000000..af06ee10 --- /dev/null +++ b/app/coap/node.h @@ -0,0 +1,58 @@ +#ifndef _NODE_H +#define _NODE_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "hash.h" +#include "pdu.h" + +struct coap_queue_t; +typedef uint32_t coap_tick_t; + +/* +1. queue(first)->t store when to send PDU for the next time, it's a base(absolute) time +2. queue->next->t store the delta between time and base-time. queue->next->t = timeout + now - basetime +3. node->next->t store the delta between time and previous->t. node->next->t = timeout + now - node->t - basetime +4. time to fire: 10, 15, 18, 25 + node->t: 10, 5, 3, 7 +*/ + +typedef struct coap_queue_t { + struct coap_queue_t *next; + + coap_tick_t t; /**< when to send PDU for the next time */ + unsigned char retransmit_cnt; /**< retransmission counter, will be removed when zero */ + unsigned int timeout; /**< the randomized timeout value */ + + coap_tid_t id; /**< unique transaction id */ + + // coap_packet_t *pkt; + coap_pdu_t *pdu; /**< the CoAP PDU to send */ + struct espconn *pconn; +} coap_queue_t; + +void coap_free_node(coap_queue_t *node); + +/** Adds node to given queue, ordered by node->t. */ +int coap_insert_node(coap_queue_t **queue, coap_queue_t *node); + +/** Destroys specified node. */ +int coap_delete_node(coap_queue_t *node); + +/** Removes all items from given queue and frees the allocated storage. */ +void coap_delete_all(coap_queue_t *queue); + +/** Creates a new node suitable for adding to the CoAP sendqueue. */ +coap_queue_t *coap_new_node(void); + +coap_queue_t *coap_pop_next( coap_queue_t **queue ); + +int coap_remove_node( coap_queue_t **queue, const coap_tid_t id); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/coap/pdu.c b/app/coap/pdu.c new file mode 100644 index 00000000..8313146c --- /dev/null +++ b/app/coap/pdu.c @@ -0,0 +1,63 @@ +#include "c_stdlib.h" +#include "pdu.h" + +coap_pdu_t * coap_new_pdu(void) { + coap_pdu_t *pdu = NULL; + pdu = (coap_pdu_t *)c_zalloc(sizeof(coap_pdu_t)); + if(!pdu){ + NODE_DBG("coap_new_pdu malloc error.\n"); + return NULL; + } + + pdu->scratch.p = (uint8_t *)c_zalloc(MAX_REQ_SCRATCH_SIZE); + if(!pdu->scratch.p){ + NODE_DBG("coap_new_pdu malloc error.\n"); + c_free(pdu); + return NULL; + } + pdu->scratch.len = MAX_REQ_SCRATCH_SIZE; + + pdu->pkt = (coap_packet_t *)c_zalloc(sizeof(coap_packet_t)); + if(!pdu->pkt){ + NODE_DBG("coap_new_pdu malloc error.\n"); + c_free(pdu->scratch.p); + c_free(pdu); + return NULL; + } + + pdu->msg.p = (uint8_t *)c_zalloc(MAX_REQUEST_SIZE+1); // +1 for string '\0' + if(!pdu->msg.p){ + NODE_DBG("coap_new_pdu malloc error.\n"); + c_free(pdu->pkt); + c_free(pdu->scratch.p); + c_free(pdu); + return NULL; + } + pdu->msg.len = MAX_REQUEST_SIZE; + return pdu; +} + +void coap_delete_pdu(coap_pdu_t *pdu){ + if(!pdu) + return; + + if(pdu->scratch.p){ + c_free(pdu->scratch.p); + pdu->scratch.p = NULL; + pdu->scratch.len = 0; + } + + if(pdu->pkt){ + c_free(pdu->pkt); + pdu->pkt = NULL; + } + + if(pdu->msg.p){ + c_free(pdu->msg.p); + pdu->msg.p = NULL; + pdu->msg.len = 0; + } + + c_free(pdu); + pdu = NULL; +} diff --git a/app/coap/pdu.h b/app/coap/pdu.h new file mode 100644 index 00000000..cd5154da --- /dev/null +++ b/app/coap/pdu.h @@ -0,0 +1,25 @@ +#ifndef _PDU_H +#define _PDU_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "coap.h" + +/** Header structure for CoAP PDUs */ +typedef struct { + coap_rw_buffer_t scratch; + coap_packet_t *pkt; + coap_rw_buffer_t msg; /**< the CoAP msg to send */ +} coap_pdu_t; + +coap_pdu_t *coap_new_pdu(void); + +void coap_delete_pdu(coap_pdu_t *pdu); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/coap/str.c b/app/coap/str.c new file mode 100644 index 00000000..df2f84be --- /dev/null +++ b/app/coap/str.c @@ -0,0 +1,28 @@ +/* str.c -- strings to be used in the CoAP library + * + * Copyright (C) 2010,2011 Olaf Bergmann + * + * This file is part of the CoAP library libcoap. Please see + * README for terms of use. + */ + +#include "c_stdlib.h" +#include "c_types.h" + +#include "str.h" + +str * coap_new_string(size_t size) { + str *s = (str *)c_malloc(sizeof(str) + size + 1); + if ( !s ) { + return NULL; + } + + c_memset(s, 0, sizeof(str)); + s->s = ((unsigned char *)s) + sizeof(str); + return s; +} + +void coap_delete_string(str *s) { + c_free(s); +} + diff --git a/app/coap/str.h b/app/coap/str.h new file mode 100644 index 00000000..c33045b3 --- /dev/null +++ b/app/coap/str.h @@ -0,0 +1,30 @@ +/* str.h -- strings to be used in the CoAP library + * + * Copyright (C) 2010,2011 Olaf Bergmann + * + * This file is part of the CoAP library libcoap. Please see + * README for terms of use. + */ + +#ifndef _COAP_STR_H_ +#define _COAP_STR_H_ + +#include "c_string.h" + +typedef struct { + size_t length; /* length of string */ + unsigned char *s; /* string data */ +} str; + +#define COAP_SET_STR(st,l,v) { (st)->length = (l), (st)->s = (v); } + +/** + * Returns a new string object with at least size bytes storage + * allocated. The string must be released using coap_delete_string(); + */ +str *coap_new_string(size_t size); + +/** Deletes the given string and releases any memory allocated. */ +void coap_delete_string(str *); + +#endif /* _COAP_STR_H_ */ diff --git a/app/coap/uri.c b/app/coap/uri.c new file mode 100644 index 00000000..d93cf4dc --- /dev/null +++ b/app/coap/uri.c @@ -0,0 +1,468 @@ +/* uri.c -- helper functions for URI treatment + */ + +#include "c_stdio.h" +#include "c_stdlib.h" +#include "c_string.h" +#include "c_ctype.h" + +#include "coap.h" +#include "uri.h" + +#ifndef assert +// #warning "assertions are disabled" +# define assert(x) do { \ + if(!x) NODE_ERR("uri.c assert!\n"); \ + } while (0) +#endif + + +/** + * A length-safe version of strchr(). This function returns a pointer + * to the first occurrence of @p c in @p s, or @c NULL if not found. + * + * @param s The string to search for @p c. + * @param len The length of @p s. + * @param c The character to search. + * + * @return A pointer to the first occurence of @p c, or @c NULL + * if not found. + */ +static inline unsigned char * +strnchr(unsigned char *s, size_t len, unsigned char c) { + while (len && *s++ != c) + --len; + + return len ? s : NULL; +} + +int coap_split_uri(unsigned char *str_var, size_t len, coap_uri_t *uri) { + unsigned char *p, *q; + int secure = 0, res = 0; + + if (!str_var || !uri) + return -1; + + c_memset(uri, 0, sizeof(coap_uri_t)); + uri->port = COAP_DEFAULT_PORT; + + /* search for scheme */ + p = str_var; + if (*p == '/') { + q = p; + goto path; + } + + q = (unsigned char *)COAP_DEFAULT_SCHEME; + while (len && *q && tolower(*p) == *q) { + ++p; ++q; --len; + } + + /* If q does not point to the string end marker '\0', the schema + * identifier is wrong. */ + if (*q) { + res = -1; + goto error; + } + + /* There might be an additional 's', indicating the secure version: */ + if (len && (secure = tolower(*p) == 's')) { + ++p; --len; + } + + q = (unsigned char *)"://"; + while (len && *q && tolower(*p) == *q) { + ++p; ++q; --len; + } + + if (*q) { + res = -2; + goto error; + } + + /* p points to beginning of Uri-Host */ + q = p; + if (len && *p == '[') { /* IPv6 address reference */ + ++p; + + while (len && *q != ']') { + ++q; --len; + } + + if (!len || *q != ']' || p == q) { + res = -3; + goto error; + } + + COAP_SET_STR(&uri->host, q - p, p); + ++q; --len; + } else { /* IPv4 address or FQDN */ + while (len && *q != ':' && *q != '/' && *q != '?') { + *q = tolower(*q); + ++q; + --len; + } + + if (p == q) { + res = -3; + goto error; + } + + COAP_SET_STR(&uri->host, q - p, p); + } + + /* check for Uri-Port */ + if (len && *q == ':') { + p = ++q; + --len; + + while (len && isdigit(*q)) { + ++q; + --len; + } + + if (p < q) { /* explicit port number given */ + int uri_port = 0; + + while (p < q) + uri_port = uri_port * 10 + (*p++ - '0'); + + uri->port = uri_port; + } + } + + path: /* at this point, p must point to an absolute path */ + + if (!len) + goto end; + + if (*q == '/') { + p = ++q; + --len; + + while (len && *q != '?') { + ++q; + --len; + } + + if (p < q) { + COAP_SET_STR(&uri->path, q - p, p); + p = q; + } + } + + /* Uri_Query */ + if (len && *p == '?') { + ++p; + --len; + COAP_SET_STR(&uri->query, len, p); + len = 0; + } + + end: + return len ? -1 : 0; + + error: + return res; +} + +/** + * Calculates decimal value from hexadecimal ASCII character given in + * @p c. The caller must ensure that @p c actually represents a valid + * heaxdecimal character, e.g. with isxdigit(3). + * + * @hideinitializer + */ +#define hexchar_to_dec(c) ((c) & 0x40 ? ((c) & 0x0F) + 9 : ((c) & 0x0F)) + +/** + * Decodes percent-encoded characters while copying the string @p seg + * of size @p length to @p buf. The caller of this function must + * ensure that the percent-encodings are correct (i.e. the character + * '%' is always followed by two hex digits. and that @p buf provides + * sufficient space to hold the result. This function is supposed to + * be called by make_decoded_option() only. + * + * @param seg The segment to decode and copy. + * @param length Length of @p seg. + * @param buf The result buffer. + */ +void decode_segment(const unsigned char *seg, size_t length, unsigned char *buf) { + + while (length--) { + + if (*seg == '%') { + *buf = (hexchar_to_dec(seg[1]) << 4) + hexchar_to_dec(seg[2]); + + seg += 2; length -= 2; + } else { + *buf = *seg; + } + + ++buf; ++seg; + } +} + +/** + * Runs through the given path (or query) segment and checks if + * percent-encodings are correct. This function returns @c -1 on error + * or the length of @p s when decoded. + */ +int check_segment(const unsigned char *s, size_t length) { + + size_t n = 0; + + while (length) { + if (*s == '%') { + if (length < 2 || !(isxdigit(s[1]) && isxdigit(s[2]))) + return -1; + + s += 2; + length -= 2; + } + + ++s; ++n; --length; + } + + return n; +} + +/** + * Writes a coap option from given string @p s to @p buf. @p s should + * point to a (percent-encoded) path or query segment of a coap_uri_t + * object. The created option will have type @c 0, and the length + * parameter will be set according to the size of the decoded string. + * On success, this function returns the option's size, or a value + * less than zero on error. This function must be called from + * coap_split_path_impl() only. + * + * @param s The string to decode. + * @param length The size of the percent-encoded string @p s. + * @param buf The buffer to store the new coap option. + * @param buflen The maximum size of @p buf. + * + * @return The option's size, or @c -1 on error. + * + * @bug This function does not split segments that are bigger than 270 + * bytes. + */ +int make_decoded_option(const unsigned char *s, size_t length, + unsigned char *buf, size_t buflen) { + int res; + size_t written; + + if (!buflen) { + NODE_DBG("make_decoded_option(): buflen is 0!\n"); + return -1; + } + + res = check_segment(s, length); + if (res < 0) + return -1; + + /* write option header using delta 0 and length res */ + // written = coap_opt_setheader(buf, buflen, 0, res); + written = coap_buildOptionHeader(0, res, buf, buflen); + + assert(written <= buflen); + + if (!written) /* encoding error */ + return -1; + + buf += written; /* advance past option type/length */ + buflen -= written; + + if (buflen < (size_t)res) { + NODE_DBG("buffer too small for option\n"); + return -1; + } + + decode_segment(s, length, buf); + + return written + res; +} + + +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +typedef void (*segment_handler_t)(unsigned char *, size_t, void *); + +/** + * Splits the given string into segments. You should call one of the + * macros coap_split_path() or coap_split_query() instead. + * + * @param parse_iter The iterator used for tokenizing. + * @param h A handler that is called with every token. + * @param data Opaque data that is passed to @p h when called. + * + * @return The number of characters that have been parsed from @p s. + */ +size_t coap_split_path_impl(coap_parse_iterator_t *parse_iter, + segment_handler_t h, void *data) { + unsigned char *seg; + size_t length; + + assert(parse_iter); + assert(h); + + length = parse_iter->n; + + while ( (seg = coap_parse_next(parse_iter)) ) { + + /* any valid path segment is handled here: */ + h(seg, parse_iter->segment_length, data); + } + + return length - (parse_iter->n - parse_iter->segment_length); +} + +struct pkt_scr { + coap_packet_t *pkt; + coap_rw_buffer_t *scratch; + int n; +}; + +void write_option(unsigned char *s, size_t len, void *data) { + struct pkt_scr *state = (struct pkt_scr *)data; + int res; + assert(state); + + /* skip empty segments and those that consist of only one or two dots */ + if (memcmp(s, "..", min(len,2)) == 0) + return; + + res = check_segment(s, len); + if (res < 0){ + NODE_DBG("not a valid segment\n"); + return; + } + + if (state->scratch->len < (size_t)res) { + NODE_DBG("buffer too small for option\n"); + return; + } + + decode_segment(s, len, state->scratch->p); + + if (res > 0) { + state->pkt->opts[state->pkt->numopts].buf.p = state->scratch->p; + state->pkt->opts[state->pkt->numopts].buf.len = res; + state->scratch->p += res; + state->scratch->len -= res; + state->pkt->numopts++; + state->n++; + } +} + +int coap_split_path(coap_rw_buffer_t *scratch, coap_packet_t *pkt, const unsigned char *s, size_t length) { + struct pkt_scr tmp = { pkt, scratch, 0 }; + coap_parse_iterator_t pi; + + coap_parse_iterator_init((unsigned char *)s, length, + '/', (unsigned char *)"?#", 2, &pi); + coap_split_path_impl(&pi, write_option, &tmp); + + int i; + for(i=0;iopts[pkt->numopts - i - 1].num = COAP_OPTION_URI_PATH; + } + + return tmp.n; +} + +int coap_split_query(coap_rw_buffer_t *scratch, coap_packet_t *pkt, const unsigned char *s, size_t length) { + struct pkt_scr tmp = { pkt, scratch, 0 }; + coap_parse_iterator_t pi; + + coap_parse_iterator_init((unsigned char *)s, length, + '&', (unsigned char *)"#", 1, &pi); + + coap_split_path_impl(&pi, write_option, &tmp); + + int i; + for(i=0;iopts[pkt->numopts - i - 1].num = COAP_OPTION_URI_QUERY; + } + + return tmp.n; +} + +#define URI_DATA(uriobj) ((unsigned char *)(uriobj) + sizeof(coap_uri_t)) + +coap_uri_t * coap_new_uri(const unsigned char *uri, unsigned int length) { + unsigned char *result; + + result = (unsigned char *)c_malloc(length + 1 + sizeof(coap_uri_t)); + + if (!result) + return NULL; + + c_memcpy(URI_DATA(result), uri, length); + URI_DATA(result)[length] = '\0'; /* make it zero-terminated */ + + if (coap_split_uri(URI_DATA(result), length, (coap_uri_t *)result) < 0) { + c_free(result); + return NULL; + } + return (coap_uri_t *)result; +} + +/* iterator functions */ + +coap_parse_iterator_t * coap_parse_iterator_init(unsigned char *s, size_t n, + unsigned char separator, + unsigned char *delim, size_t dlen, + coap_parse_iterator_t *pi) { + assert(pi); + assert(separator); + + pi->separator = separator; + pi->delim = delim; + pi->dlen = dlen; + pi->pos = s; + pi->n = n; + pi->segment_length = 0; + + return pi; +} + +unsigned char * coap_parse_next(coap_parse_iterator_t *pi) { + unsigned char *p; + + if (!pi) + return NULL; + + /* proceed to the next segment */ + pi->n -= pi->segment_length; + pi->pos += pi->segment_length; + pi->segment_length = 0; + + /* last segment? */ + if (!pi->n || strnchr(pi->delim, pi->dlen, *pi->pos)) { + pi->pos = NULL; + return NULL; + } + + /* skip following separator (the first segment might not have one) */ + if (*pi->pos == pi->separator) { + ++pi->pos; + --pi->n; + } + + p = pi->pos; + + while (pi->segment_length < pi->n && *p != pi->separator && + !strnchr(pi->delim, pi->dlen, *p)) { + ++p; + ++pi->segment_length; + } + + if (!pi->n) { + pi->pos = NULL; + pi->segment_length = 0; + } + + return pi->pos; +} diff --git a/app/coap/uri.h b/app/coap/uri.h new file mode 100644 index 00000000..da1ef28a --- /dev/null +++ b/app/coap/uri.h @@ -0,0 +1,166 @@ +/* uri.h -- helper functions for URI treatment + * + * Copyright (C) 2010,2011 Olaf Bergmann + * + * This file is part of the CoAP library libcoap. Please see + * README for terms of use. + */ + +#ifndef _COAP_URI_H_ +#define _COAP_URI_H_ + +#define COAP_DEFAULT_SCHEME "coap" /* the default scheme for CoAP URIs */ +#define COAP_DEFAULT_PORT 5683 + +#include "str.h" + +/** Representation of parsed URI. Components may be filled from a + * string with coap_split_uri() and can be used as input for + * option-creation functions. */ +typedef struct { + str host; /**< host part of the URI */ + unsigned short port; /**< The port in host byte order */ + str path; /**< Beginning of the first path segment. + Use coap_split_path() to create Uri-Path options */ + str query; /**< The query part if present */ +} coap_uri_t; + +/** + * Creates a new coap_uri_t object from the specified URI. Returns the new + * object or NULL on error. The memory allocated by the new coap_uri_t + * must be released using coap_free(). + * @param uri The URI path to copy. + * @para length The length of uri. + * + * @return New URI object or NULL on error. + */ +coap_uri_t *coap_new_uri(const unsigned char *uri, unsigned int length); + +/** + * @defgroup uri_parse URI Parsing Functions + * + * CoAP PDUs contain normalized URIs with their path and query split into + * multiple segments. The functions in this module help splitting strings. + * @{ + */ + +/** + * Iterator to for tokenizing a URI path or query. This structure must + * be initialized with coap_parse_iterator_init(). Call + * coap_parse_next() to walk through the tokens. + * + * @code + * unsigned char *token; + * coap_parse_iterator_t pi; + * coap_parse_iterator_init(uri.path.s, uri.path.length, '/', "?#", 2, &pi); + * + * while ((token = coap_parse_next(&pi))) { + * ... do something with token ... + * } + * @endcode + */ +typedef struct { + size_t n; /**< number of remaining characters in buffer */ + unsigned char separator; /**< segment separators */ + unsigned char *delim; /**< delimiters where to split the string */ + size_t dlen; /**< length of separator */ + unsigned char *pos; /**< current position in buffer */ + size_t segment_length; /**< length of current segment */ +} coap_parse_iterator_t; + +/** + * Initializes the given iterator @p pi. + * + * @param s The string to tokenize. + * @param n The length of @p s. + * @param separator The separator character that delimits tokens. + * @param delim A set of characters that delimit @s. + * @param dlen The length of @p delim. + * @param pi The iterator object to initialize. + * + * @return The initialized iterator object @p pi. + */ +coap_parse_iterator_t * +coap_parse_iterator_init(unsigned char *s, size_t n, + unsigned char separator, + unsigned char *delim, size_t dlen, + coap_parse_iterator_t *pi); + +/** + * Updates the iterator @p pi to point to the next token. This + * function returns a pointer to that token or @c NULL if no more + * tokens exist. The contents of @p pi will be updated. In particular, + * @c pi->segment_length specifies the length of the current token, @c + * pi->pos points to its beginning. + * + * @param pi The iterator to update. + * + * @return The next token or @c NULL if no more tokens exist. + */ +unsigned char *coap_parse_next(coap_parse_iterator_t *pi); + +/** + * Parses a given string into URI components. The identified syntactic + * components are stored in the result parameter @p uri. Optional URI + * components that are not specified will be set to { 0, 0 }, except + * for the port which is set to @c COAP_DEFAULT_PORT. This function + * returns @p 0 if parsing succeeded, a value less than zero + * otherwise. + * + * @param str_var The string to split up. + * @param len The actual length of @p str_var + * @param uri The coap_uri_t object to store the result. + * @return @c 0 on success, or < 0 on error. + * + * @note The host name part will be converted to lower case by this + * function. + */ +int +coap_split_uri(unsigned char *str_var, size_t len, coap_uri_t *uri); + +/** + * Splits the given URI path into segments. Each segment is preceded + * by an option pseudo-header with delta-value 0 and the actual length + * of the respective segment after percent-decoding. + * + * @param s The path string to split. + * @param length The actual length of @p s. + * @param buf Result buffer for parsed segments. + * @param buflen Maximum length of @p buf. Will be set to the actual number + * of bytes written into buf on success. + * + * @return The number of segments created or @c -1 on error. + */ +#if 0 +int coap_split_path(const unsigned char *s, size_t length, + unsigned char *buf, size_t *buflen); +#else +int +coap_split_path(coap_rw_buffer_t *scratch, coap_packet_t *pkt, + const unsigned char *s, size_t length); +#endif +/** + * Splits the given URI query into segments. Each segment is preceded + * by an option pseudo-header with delta-value 0 and the actual length + * of the respective query term. + * + * @param s The query string to split. + * @param length The actual length of @p s. + * @param buf Result buffer for parsed segments. + * @param buflen Maximum length of @p buf. Will be set to the actual number + * of bytes written into buf on success. + * + * @return The number of segments created or @c -1 on error. + * + * @bug This function does not reserve additional space for delta > 12. + */ +#if 0 +int coap_split_query(const unsigned char *s, size_t length, + unsigned char *buf, size_t *buflen); +#else +int coap_split_query(coap_rw_buffer_t *scratch, coap_packet_t *pkt, + const unsigned char *s, size_t length); +#endif +/** @} */ + +#endif /* _COAP_URI_H_ */ diff --git a/app/include/user_config.h b/app/include/user_config.h index f3f36494..c1a9805c 100644 --- a/app/include/user_config.h +++ b/app/include/user_config.h @@ -1,14 +1,6 @@ #ifndef __USER_CONFIG_H__ #define __USER_CONFIG_H__ -#define NODE_VERSION_MAJOR 0U -#define NODE_VERSION_MINOR 9U -#define NODE_VERSION_REVISION 5U -#define NODE_VERSION_INTERNAL 0U - -#define NODE_VERSION "NodeMCU 0.9.5" -#define BUILD_DATE "build 20150214" - // #define DEVKIT_VERSION_0_9 1 // define this only if you use NodeMCU devkit v0.9 // #define FLASH_512K @@ -25,6 +17,7 @@ #ifdef DEVELOP_VERSION #define NODE_DEBUG +#define COAP_DEBUG #endif /* DEVELOP_VERSION */ #define NODE_ERROR @@ -51,33 +44,6 @@ // #define BUILD_WOFS 1 #define BUILD_SPIFFS 1 -#define LUA_USE_MODULES - -#ifdef LUA_USE_MODULES -#define LUA_USE_MODULES_NODE -#define LUA_USE_MODULES_FILE -#define LUA_USE_MODULES_GPIO -#define LUA_USE_MODULES_WIFI -#define LUA_USE_MODULES_NET -#define LUA_USE_MODULES_PWM -#define LUA_USE_MODULES_I2C -#define LUA_USE_MODULES_SPI -#define LUA_USE_MODULES_TMR -#define LUA_USE_MODULES_ADC -#define LUA_USE_MODULES_UART -#define LUA_USE_MODULES_OW -#define LUA_USE_MODULES_BIT -#define LUA_USE_MODULES_U8G -#define LUA_USE_MODULES_MQTT -#define LUA_USE_MODULES_WS2812 // TODO: put this device specific module to device driver section. -#endif /* LUA_USE_MODULES */ - -// TODO: put device specific module to device driver section. -#ifdef LUA_USE_DEVICE_DRIVER -#define LUA_USE_DEVICE_WS2812 -#endif /* LUA_USE_DEVICE_DRIVER */ - - // #define LUA_NUMBER_INTEGRAL #define LUA_OPTRAM @@ -99,7 +65,6 @@ #define LED_LOW_COUNT_DEFAULT 0 #endif - // Configure U8glib fonts // add a U8G_FONT_TABLE_ENTRY for each font you want to compile into the image #define U8G_FONT_TABLE_ENTRY(font) diff --git a/app/include/user_modules.h b/app/include/user_modules.h new file mode 100644 index 00000000..a21cee19 --- /dev/null +++ b/app/include/user_modules.h @@ -0,0 +1,35 @@ +#ifndef __USER_MODULES_H__ +#define __USER_MODULES_H__ + +#define LUA_USE_BUILTIN_STRING // for string.xxx() +#define LUA_USE_BUILTIN_TABLE // for table.xxx() +#define LUA_USE_BUILTIN_COROUTINE // for coroutine.xxx() +#define LUA_USE_BUILTIN_MATH // for math.xxx(), partially work +// #define LUA_USE_BUILTIN_IO // for io.xxx(), partially work + +// #define LUA_USE_BUILTIN_OS // for os.xxx(), not work +// #define LUA_USE_BUILTIN_DEBUG // for debug.xxx(), not work + +#define LUA_USE_MODULES + +#ifdef LUA_USE_MODULES +#define LUA_USE_MODULES_NODE +#define LUA_USE_MODULES_FILE +#define LUA_USE_MODULES_GPIO +#define LUA_USE_MODULES_WIFI +#define LUA_USE_MODULES_NET +#define LUA_USE_MODULES_PWM +#define LUA_USE_MODULES_I2C +#define LUA_USE_MODULES_SPI +#define LUA_USE_MODULES_TMR +#define LUA_USE_MODULES_ADC +#define LUA_USE_MODULES_UART +#define LUA_USE_MODULES_OW +#define LUA_USE_MODULES_BIT +#define LUA_USE_MODULES_MQTT +// #define LUA_USE_MODULES_COAP // need about 4k more ram for now +#define LUA_USE_MODULES_U8G +#define LUA_USE_MODULES_WS2812 +#endif /* LUA_USE_MODULES */ + +#endif /* __USER_MODULES_H__ */ diff --git a/app/include/user_version.h b/app/include/user_version.h new file mode 100644 index 00000000..c1144958 --- /dev/null +++ b/app/include/user_version.h @@ -0,0 +1,12 @@ +#ifndef __USER_VERSION_H__ +#define __USER_VERSION_H__ + +#define NODE_VERSION_MAJOR 0U +#define NODE_VERSION_MINOR 9U +#define NODE_VERSION_REVISION 5U +#define NODE_VERSION_INTERNAL 0U + +#define NODE_VERSION "NodeMCU 0.9.5" +#define BUILD_DATE "build 20150306" + +#endif /* __USER_VERSION_H__ */ diff --git a/app/lua/lmathlib.c b/app/lua/lmathlib.c index d9630518..a9f553bf 100644 --- a/app/lua/lmathlib.c +++ b/app/lua/lmathlib.c @@ -324,36 +324,36 @@ const LUA_REG_TYPE math_map[] = { #endif #else {LSTRKEY("abs"), LFUNCVAL(math_abs)}, - {LSTRKEY("acos"), LFUNCVAL(math_acos)}, - {LSTRKEY("asin"), LFUNCVAL(math_asin)}, - {LSTRKEY("atan2"), LFUNCVAL(math_atan2)}, - {LSTRKEY("atan"), LFUNCVAL(math_atan)}, + // {LSTRKEY("acos"), LFUNCVAL(math_acos)}, + // {LSTRKEY("asin"), LFUNCVAL(math_asin)}, + // {LSTRKEY("atan2"), LFUNCVAL(math_atan2)}, + // {LSTRKEY("atan"), LFUNCVAL(math_atan)}, {LSTRKEY("ceil"), LFUNCVAL(math_ceil)}, - {LSTRKEY("cosh"), LFUNCVAL(math_cosh)}, - {LSTRKEY("cos"), LFUNCVAL(math_cos)}, - {LSTRKEY("deg"), LFUNCVAL(math_deg)}, - {LSTRKEY("exp"), LFUNCVAL(math_exp)}, + // {LSTRKEY("cosh"), LFUNCVAL(math_cosh)}, + // {LSTRKEY("cos"), LFUNCVAL(math_cos)}, + // {LSTRKEY("deg"), LFUNCVAL(math_deg)}, + // {LSTRKEY("exp"), LFUNCVAL(math_exp)}, {LSTRKEY("floor"), LFUNCVAL(math_floor)}, - {LSTRKEY("fmod"), LFUNCVAL(math_fmod)}, + // {LSTRKEY("fmod"), LFUNCVAL(math_fmod)}, #if LUA_OPTIMIZE_MEMORY > 0 && defined(LUA_COMPAT_MOD) {LSTRKEY("mod"), LFUNCVAL(math_fmod)}, #endif - {LSTRKEY("frexp"), LFUNCVAL(math_frexp)}, - {LSTRKEY("ldexp"), LFUNCVAL(math_ldexp)}, - {LSTRKEY("log10"), LFUNCVAL(math_log10)}, - {LSTRKEY("log"), LFUNCVAL(math_log)}, + // {LSTRKEY("frexp"), LFUNCVAL(math_frexp)}, + // {LSTRKEY("ldexp"), LFUNCVAL(math_ldexp)}, + // {LSTRKEY("log10"), LFUNCVAL(math_log10)}, + // {LSTRKEY("log"), LFUNCVAL(math_log)}, {LSTRKEY("max"), LFUNCVAL(math_max)}, {LSTRKEY("min"), LFUNCVAL(math_min)}, - {LSTRKEY("modf"), LFUNCVAL(math_modf)}, + // {LSTRKEY("modf"), LFUNCVAL(math_modf)}, {LSTRKEY("pow"), LFUNCVAL(math_pow)}, - {LSTRKEY("rad"), LFUNCVAL(math_rad)}, + // {LSTRKEY("rad"), LFUNCVAL(math_rad)}, {LSTRKEY("random"), LFUNCVAL(math_random)}, {LSTRKEY("randomseed"), LFUNCVAL(math_randomseed)}, - {LSTRKEY("sinh"), LFUNCVAL(math_sinh)}, - {LSTRKEY("sin"), LFUNCVAL(math_sin)}, + // {LSTRKEY("sinh"), LFUNCVAL(math_sinh)}, + // {LSTRKEY("sin"), LFUNCVAL(math_sin)}, {LSTRKEY("sqrt"), LFUNCVAL(math_sqrt)}, - {LSTRKEY("tanh"), LFUNCVAL(math_tanh)}, - {LSTRKEY("tan"), LFUNCVAL(math_tan)}, + // {LSTRKEY("tanh"), LFUNCVAL(math_tanh)}, + // {LSTRKEY("tan"), LFUNCVAL(math_tan)}, #if LUA_OPTIMIZE_MEMORY > 0 {LSTRKEY("pi"), LNUMVAL(PI)}, {LSTRKEY("huge"), LNUMVAL(HUGE_VAL)}, diff --git a/app/lua/lua.c b/app/lua/lua.c index 27bb3b4b..d7d14f40 100644 --- a/app/lua/lua.c +++ b/app/lua/lua.c @@ -10,6 +10,7 @@ #include "c_stdlib.h" #include "c_string.h" #include "flash_fs.h" +#include "user_version.h" #define lua_c diff --git a/app/lua/lualib.h b/app/lua/lualib.h index a475d887..9e4e7aea 100644 --- a/app/lua/lualib.h +++ b/app/lua/lualib.h @@ -24,17 +24,17 @@ LUALIB_API int (luaopen_table) (lua_State *L); #define LUA_IOLIBNAME "io" LUALIB_API int (luaopen_io) (lua_State *L); -// #define LUA_OSLIBNAME "os" -// LUALIB_API int (luaopen_os) (lua_State *L); +#define LUA_OSLIBNAME "os" +LUALIB_API int (luaopen_os) (lua_State *L); #define LUA_STRLIBNAME "string" LUALIB_API int (luaopen_string) (lua_State *L); -// #define LUA_MATHLIBNAME "math" -// LUALIB_API int (luaopen_math) (lua_State *L); +#define LUA_MATHLIBNAME "math" +LUALIB_API int (luaopen_math) (lua_State *L); -// #define LUA_DBLIBNAME "debug" -// LUALIB_API int (luaopen_debug) (lua_State *L); +#define LUA_DBLIBNAME "debug" +LUALIB_API int (luaopen_debug) (lua_State *L); #define LUA_LOADLIBNAME "package" LUALIB_API int (luaopen_package) (lua_State *L); diff --git a/app/modules/Makefile b/app/modules/Makefile index ecb8b880..7d2ae1cc 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 ../coap INCLUDES += -I ../mqtt INCLUDES += -I ../u8glib INCLUDES += -I ../lua diff --git a/app/modules/auxmods.h b/app/modules/auxmods.h index 175a1f2b..2b09448a 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_COAP "coap" +LUALIB_API int ( luaopen_coap )( lua_State *L ); + #define AUXLIB_MQTT "mqtt" LUALIB_API int ( luaopen_mqtt )( lua_State *L ); diff --git a/app/modules/coap.c b/app/modules/coap.c new file mode 100644 index 00000000..2afb9b54 --- /dev/null +++ b/app/modules/coap.c @@ -0,0 +1,646 @@ +// Module for coapwork + +//#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 "driver/uart.h" + +#include "coap.h" +#include "uri.h" +#include "node.h" +#include "coap_timer.h" +#include "coap_io.h" +#include "coap_server.h" + +coap_queue_t *gQueue = NULL; + +typedef struct lcoap_userdata +{ + lua_State *L; + struct espconn *pesp_conn; + int self_ref; +}lcoap_userdata; + +static void coap_received(void *arg, char *pdata, unsigned short len) +{ + NODE_DBG("coap_received is called.\n"); + struct espconn *pesp_conn = arg; + lcoap_userdata *cud = (lcoap_userdata *)pesp_conn->reverse; + + static uint8_t buf[MAX_MESSAGE_SIZE+1] = {0}; // +1 for string '\0' + c_memset(buf, 0, sizeof(buf)); // wipe prev data + + if (len > MAX_MESSAGE_SIZE) { + NODE_DBG("Request Entity Too Large.\n"); // NOTE: should response 4.13 to client... + return; + } else { + c_memcpy(buf, pdata, len); + } + + size_t rsplen = coap_server_respond(buf, len, MAX_MESSAGE_SIZE+1); + espconn_sent(pesp_conn, (unsigned char *)buf, rsplen); + + c_memset(buf, 0, sizeof(buf)); +} + +static void coap_sent(void *arg) +{ + NODE_DBG("coap_sent is called.\n"); +} + +// Lua: s = coap.create(function(conn)) +static int coap_create( lua_State* L, const char* mt ) +{ + struct espconn *pesp_conn = NULL; + lcoap_userdata *cud; + unsigned type; + int stack = 1; + + // create a object + cud = (lcoap_userdata *)lua_newuserdata(L, sizeof(lcoap_userdata)); + // pre-initialize it, in case of errors + cud->self_ref = LUA_NOREF; + cud->pesp_conn = NULL; + + // set its metatable + luaL_getmetatable(L, mt); + lua_setmetatable(L, -2); + + // create the espconn struct + pesp_conn = (struct espconn *)c_zalloc(sizeof(struct espconn)); + if(!pesp_conn) + return luaL_error(L, "not enough memory"); + + cud->pesp_conn = pesp_conn; + + pesp_conn->type = ESPCONN_UDP; + pesp_conn->proto.tcp = NULL; + pesp_conn->proto.udp = NULL; + + pesp_conn->proto.udp = (esp_udp *)c_zalloc(sizeof(esp_udp)); + if(!pesp_conn->proto.udp){ + c_free(pesp_conn); + cud->pesp_conn = pesp_conn = NULL; + return luaL_error(L, "not enough memory"); + } + pesp_conn->state = ESPCONN_NONE; + NODE_DBG("UDP server/client is set.\n"); + + cud->L = L; + pesp_conn->reverse = cud; + + NODE_DBG("coap_create is called.\n"); + return 1; +} + +// Lua: server:delete() +static int coap_delete( lua_State* L, const char* mt ) +{ + struct espconn *pesp_conn = NULL; + lcoap_userdata *cud; + + cud = (lcoap_userdata *)luaL_checkudata(L, 1, mt); + luaL_argcheck(L, cud, 1, "Server/Client expected"); + if(cud==NULL){ + NODE_DBG("userdata is nil.\n"); + return 0; + } + + // free (unref) callback ref + if(LUA_NOREF!=cud->self_ref){ + luaL_unref(L, LUA_REGISTRYINDEX, cud->self_ref); + cud->self_ref = LUA_NOREF; + } + + cud->L = NULL; + if(cud->pesp_conn) + { + if(cud->pesp_conn->proto.udp->remote_port || cud->pesp_conn->proto.udp->local_port) + espconn_delete(cud->pesp_conn); + c_free(cud->pesp_conn->proto.udp); + cud->pesp_conn->proto.udp = NULL; + c_free(cud->pesp_conn); + cud->pesp_conn = NULL; + } + + NODE_DBG("coap_delete is called.\n"); + return 0; +} + +// Lua: server:listen( port, ip ) +static int coap_start( lua_State* L, const char* mt ) +{ + struct espconn *pesp_conn = NULL; + lcoap_userdata *cud; + unsigned port; + size_t il; + ip_addr_t ipaddr; + + cud = (lcoap_userdata *)luaL_checkudata(L, 1, mt); + luaL_argcheck(L, cud, 1, "Server/Client expected"); + if(cud==NULL){ + NODE_DBG("userdata is nil.\n"); + return 0; + } + + pesp_conn = cud->pesp_conn; + port = luaL_checkinteger( L, 2 ); + pesp_conn->proto.udp->local_port = port; + NODE_DBG("UDP port is set: %d.\n", port); + + if( lua_isstring(L,3) ) // deal with the ip string + { + const char *ip = luaL_checklstring( L, 3, &il ); + if (ip == NULL) + { + ip = "0.0.0.0"; + } + ipaddr.addr = ipaddr_addr(ip); + c_memcpy(pesp_conn->proto.udp->local_ip, &ipaddr.addr, 4); + NODE_DBG("UDP ip is set: "); + NODE_DBG(IPSTR, IP2STR(&ipaddr.addr)); + NODE_DBG("\n"); + } + + if(LUA_NOREF==cud->self_ref){ + lua_pushvalue(L, 1); // copy to the top of stack + cud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + + espconn_regist_recvcb(pesp_conn, coap_received); + espconn_regist_sentcb(pesp_conn, coap_sent); + espconn_create(pesp_conn); + + NODE_DBG("Coap Server started on port: %d\n", port); + NODE_DBG("coap_start is called.\n"); + return 0; +} + +// Lua: server:close() +static int coap_close( lua_State* L, const char* mt ) +{ + struct espconn *pesp_conn = NULL; + lcoap_userdata *cud; + + cud = (lcoap_userdata *)luaL_checkudata(L, 1, mt); + luaL_argcheck(L, cud, 1, "Server/Client expected"); + if(cud==NULL){ + NODE_DBG("userdata is nil.\n"); + return 0; + } + + if(cud->pesp_conn) + { + if(cud->pesp_conn->proto.udp->remote_port || cud->pesp_conn->proto.udp->local_port) + espconn_delete(cud->pesp_conn); + } + + if(LUA_NOREF!=cud->self_ref){ + luaL_unref(L, LUA_REGISTRYINDEX, cud->self_ref); + cud->self_ref = LUA_NOREF; + } + + NODE_DBG("coap_close is called.\n"); + return 0; +} + +// Lua: server/client:on( "method", function(s) ) +static int coap_on( lua_State* L, const char* mt ) +{ + NODE_DBG("coap_on is called.\n"); + return 0; +} + +static void coap_response_handler(void *arg, char *pdata, unsigned short len) +{ + NODE_DBG("coap_response_handler is called.\n"); + struct espconn *pesp_conn = arg; + + coap_packet_t pkt; + static uint8_t buf[MAX_MESSAGE_SIZE+1] = {0}; // +1 for string '\0' + c_memset(buf, 0, sizeof(buf)); // wipe prev data + static int n = 0; + + int rc; + if ((len == 1460) && (1460 <= MAX_MESSAGE_SIZE)){ + c_memcpy(buf, pdata, len); // max length is 1460, another part of data is coming in next callback + n = len; + return; + } else { + if( len > MAX_MESSAGE_SIZE ) + { + NODE_DBG("Request Entity Too Large.\n"); // NOTE: should response 4.13 to client... + c_memset(buf, 0, sizeof(buf)); // wipe prev data + n = 0; + return; + } + c_memcpy(buf + n, pdata, len); + len += n; // more than 1460 + } + + if (0 != (rc = coap_parse(&pkt, buf, len))){ + NODE_DBG("Bad packet rc=%d\n", rc); + } + else + { +#ifdef COAP_DEBUG + coap_dumpPacket(&pkt); +#endif + /* check if this is a response to our original request */ + if (!check_token(&pkt)) { + /* drop if this was just some message, or send RST in case of notification */ + if (pkt.hdr.t == COAP_TYPE_CON || pkt.hdr.t == COAP_TYPE_NONCON){ + // coap_send_rst(pkt); // send RST response + // or, just ignore it. + } + goto end; + } + + if (pkt.hdr.t == COAP_TYPE_RESET) { + NODE_DBG("got RST\n"); + goto end; + } + + uint32_t ip = 0, port = 0; + coap_tid_t id = COAP_INVALID_TID; + + c_memcpy(&ip, pesp_conn->proto.udp->remote_ip, sizeof(ip)); + port = pesp_conn->proto.udp->remote_port; + + coap_transaction_id(ip, port, &pkt, &id); + + /* transaction done, remove the node from queue */ + // stop timer + coap_timer_stop(); + // remove the node + coap_remove_node(&gQueue, id); + // calculate time elapsed + coap_timer_update(&gQueue); + coap_timer_start(&gQueue); + + if (COAP_RESPONSE_CLASS(pkt.hdr.code) == 2) + { + /* There is no block option set, just read the data and we are done. */ + NODE_DBG("%d.%02d\t", (pkt.hdr.code >> 5), pkt.hdr.code & 0x1F); + NODE_DBG((char *)pkt.payload.p); + } + else if (COAP_RESPONSE_CLASS(pkt.hdr.code) >= 4) + { + NODE_DBG("%d.%02d\t", (pkt.hdr.code >> 5), pkt.hdr.code & 0x1F); + NODE_DBG((char *)pkt.payload.p); + } + } + +end: + if(!gQueue){ // if there is no node pending in the queue, disconnect from host. + if(pesp_conn->proto.udp->remote_port || pesp_conn->proto.udp->local_port) + espconn_delete(pesp_conn); + } + c_memset(buf, 0, sizeof(buf)); + n = 0; +} + +// Lua: client:request( [CON], uri, [payload] ) +static int coap_request( lua_State* L, coap_method_t m ) +{ + struct espconn *pesp_conn = NULL; + lcoap_userdata *cud; + int stack = 1; + + cud = (lcoap_userdata *)luaL_checkudata(L, stack, "coap_client"); + luaL_argcheck(L, cud, stack, "Server/Client expected"); + if(cud==NULL){ + NODE_DBG("userdata is nil.\n"); + return 0; + } + + stack++; + pesp_conn = cud->pesp_conn; + ip_addr_t ipaddr; + uint8_t host[32]; + + unsigned t; + if ( lua_isnumber(L, stack) ) + { + t = lua_tointeger(L, stack); + stack++; + if ( t != COAP_TYPE_CON && t != COAP_TYPE_NONCON ) + return luaL_error( L, "wrong arg type" ); + } else { + t = COAP_TYPE_CON; // default to CON + } + + size_t l; + const char *url = luaL_checklstring( L, stack, &l ); + stack++; + if (url == NULL) + return luaL_error( L, "wrong arg type" ); + + coap_uri_t *uri = coap_new_uri(url, l); // should call free(uri) somewhere + if (uri == NULL) + return luaL_error( L, "uri wrong format." ); + + pesp_conn->proto.udp->remote_port = uri->port; + NODE_DBG("UDP port is set: %d.\n", uri->port); + pesp_conn->proto.udp->local_port = espconn_port(); + + if(uri->host.length){ + c_memcpy(host, uri->host.s, uri->host.length); + host[uri->host.length] = '\0'; + + ipaddr.addr = ipaddr_addr(host); + NODE_DBG("Host len(%d):", uri->host.length); + NODE_DBG(host); + NODE_DBG("\n"); + + c_memcpy(pesp_conn->proto.udp->remote_ip, &ipaddr.addr, 4); + NODE_DBG("UDP ip is set: "); + NODE_DBG(IPSTR, IP2STR(&ipaddr.addr)); + NODE_DBG("\n"); + } + + coap_pdu_t *pdu = coap_new_pdu(); // should call coap_delete_pdu() somewhere + if(!pdu){ + if(uri) + c_free(uri); + return; + } + + const char *payload = NULL; + l = 0; + if( lua_isstring(L, stack) ){ + payload = luaL_checklstring( L, stack, &l ); + if (payload == NULL) + l = 0; + } + + coap_make_request(&(pdu->scratch), pdu->pkt, t, m, uri, payload, l); + +#ifdef COAP_DEBUG + coap_dumpPacket(pdu->pkt); +#endif + + int rc; + if (0 != (rc = coap_build(pdu->msg.p, &(pdu->msg.len), pdu->pkt))){ + NODE_DBG("coap_build failed rc=%d\n", rc); + } + else + { +#ifdef COAP_DEBUG + NODE_DBG("Sending: "); + coap_dump(pdu->msg.p, pdu->msg.len, true); + NODE_DBG("\n"); +#endif + espconn_regist_recvcb(pesp_conn, coap_response_handler); + sint8_t con = espconn_create(pesp_conn); + if( ESPCONN_OK != con){ + NODE_DBG("Connect to host. code:%d\n", con); + // coap_delete_pdu(pdu); + } + // else + { + coap_tid_t tid = COAP_INVALID_TID; + if (pdu->pkt->hdr.t == COAP_TYPE_CON){ + tid = coap_send_confirmed(pesp_conn, pdu); + } + else { + tid = coap_send(pesp_conn, pdu); + } + if (pdu->pkt->hdr.t != COAP_TYPE_CON || tid == COAP_INVALID_TID){ + coap_delete_pdu(pdu); + } + } + } + + if(uri) + c_free((void *)uri); + + NODE_DBG("coap_request is called.\n"); + return 0; +} + +extern coap_luser_entry *variable_entry; +extern coap_luser_entry *function_entry; +extern void build_well_known_rsp(void); +// Lua: coap:var/func( string ) +static int coap_regist( lua_State* L, const char* mt, int isvar ) +{ + size_t l; + const char *name = luaL_checklstring( L, 2, &l ); + if (name == NULL) + return luaL_error( L, "name must be set." ); + + coap_luser_entry *h = NULL; + // if(lua_isstring(L, 3)) + if(isvar) + h = variable_entry; + else + h = function_entry; + + while(NULL!=h->next){ // goto the end of the list + if(h->name!= NULL && c_strcmp(h->name, name)==0) // key exist, override it + break; + h = h->next; + } + + if(h->name==NULL || c_strcmp(h->name, name)!=0){ // not exists. make a new one. + h->next = (coap_luser_entry *)c_zalloc(sizeof(coap_luser_entry)); + h = h->next; + if(h == NULL) + return luaL_error(L, "not enough memory"); + h->next = NULL; + h->name = NULL; + h->L = NULL; + } + + h->L = L; + h->name = name; + + build_well_known_rsp(); // rebuild .well-known + + NODE_DBG("coap_regist is called.\n"); + return 0; +} + +// Lua: s = coap.createServer(function(conn)) +static int coap_createServer( lua_State* L ) +{ + const char *mt = "coap_server"; + return coap_create(L, mt); +} + +// Lua: server:delete() +static int coap_server_delete( lua_State* L ) +{ + const char *mt = "coap_server"; + return coap_delete(L, mt); +} + +// Lua: server:listen( port, ip, function(err) ) +static int coap_server_listen( lua_State* L ) +{ + const char *mt = "coap_server"; + return coap_start(L, mt); +} + +// Lua: server:close() +static int coap_server_close( lua_State* L ) +{ + const char *mt = "coap_server"; + return coap_close(L, mt); +} + +// Lua: server:on( "method", function(server) ) +static int coap_server_on( lua_State* L ) +{ + const char *mt = "coap_server"; + return coap_on(L, mt); +} + +// Lua: server:var( "name" ) +static int coap_server_var( lua_State* L ) +{ + const char *mt = "coap_server"; + return coap_regist(L, mt, 1); +} + +// Lua: server:func( "name" ) +static int coap_server_func( lua_State* L ) +{ + const char *mt = "coap_server"; + return coap_regist(L, mt, 0); +} + +// Lua: s = coap.createClient(function(conn)) +static int coap_createClient( lua_State* L ) +{ + const char *mt = "coap_client"; + return coap_create(L, mt); +} + +// Lua: client:gcdelete() +static int coap_client_gcdelete( lua_State* L ) +{ + const char *mt = "coap_client"; + return coap_delete(L, mt); +} + +// client:get( string, function(sent) ) +static int coap_client_get( lua_State* L ) +{ + return coap_request(L, COAP_METHOD_GET); +} + +// client:post( string, function(sent) ) +static int coap_client_post( lua_State* L ) +{ + return coap_request(L, COAP_METHOD_POST); +} + +// client:put( string, function(sent) ) +static int coap_client_put( lua_State* L ) +{ + return coap_request(L, COAP_METHOD_PUT); +} + +// client:delete( string, function(sent) ) +static int coap_client_delete( lua_State* L ) +{ + return coap_request(L, COAP_METHOD_DELETE); +} + +// Module function map +#define MIN_OPT_LEVEL 2 +#include "lrodefs.h" +static const LUA_REG_TYPE coap_server_map[] = +{ + { LSTRKEY( "listen" ), LFUNCVAL ( coap_server_listen ) }, + { LSTRKEY( "close" ), LFUNCVAL ( coap_server_close ) }, + { LSTRKEY( "var" ), LFUNCVAL ( coap_server_var ) }, + { LSTRKEY( "func" ), LFUNCVAL ( coap_server_func ) }, + { LSTRKEY( "__gc" ), LFUNCVAL ( coap_server_delete ) }, +#if LUA_OPTIMIZE_MEMORY > 0 + { LSTRKEY( "__index" ), LROVAL ( coap_server_map ) }, +#endif + { LNILKEY, LNILVAL } +}; + +static const LUA_REG_TYPE coap_client_map[] = +{ + { LSTRKEY( "get" ), LFUNCVAL ( coap_client_get ) }, + { LSTRKEY( "post" ), LFUNCVAL ( coap_client_post ) }, + { LSTRKEY( "put" ), LFUNCVAL ( coap_client_put ) }, + { LSTRKEY( "delete" ), LFUNCVAL ( coap_client_delete ) }, + { LSTRKEY( "__gc" ), LFUNCVAL ( coap_client_gcdelete ) }, +#if LUA_OPTIMIZE_MEMORY > 0 + { LSTRKEY( "__index" ), LROVAL ( coap_client_map ) }, +#endif + { LNILKEY, LNILVAL } +}; + +const LUA_REG_TYPE coap_map[] = +{ + { LSTRKEY( "Server" ), LFUNCVAL ( coap_createServer ) }, + { LSTRKEY( "Client" ), LFUNCVAL ( coap_createClient ) }, +#if LUA_OPTIMIZE_MEMORY > 0 + { LSTRKEY( "CON" ), LNUMVAL( COAP_TYPE_CON ) }, + { LSTRKEY( "NON" ), LNUMVAL( COAP_TYPE_NONCON ) }, + + { LSTRKEY( "__metatable" ), LROVAL( coap_map ) }, +#endif + { LNILKEY, LNILVAL } +}; + +LUALIB_API int luaopen_coap( lua_State *L ) +{ + endpoint_setup(); +#if LUA_OPTIMIZE_MEMORY > 0 + luaL_rometatable(L, "coap_server", (void *)coap_server_map); // create metatable for coap_server + luaL_rometatable(L, "coap_client", (void *)coap_client_map); // create metatable for coap_client + return 0; +#else // #if LUA_OPTIMIZE_MEMORY > 0 + int n; + luaL_register( L, AUXLIB_COAP, coap_map ); + + // Set it as its own metatable + lua_pushvalue( L, -1 ); + lua_setmetatable( L, -2 ); + + // Module constants + MOD_REG_NUMBER( L, "CON", COAP_TYPE_CON ); + MOD_REG_NUMBER( L, "NON", COAP_TYPE_NONCON ); + + n = lua_gettop(L); + + // create metatable + luaL_newmetatable(L, "coap_server"); + // metatable.__index = metatable + lua_pushliteral(L, "__index"); + lua_pushvalue(L,-2); + lua_rawset(L,-3); + // Setup the methods inside metatable + luaL_register( L, NULL, coap_server_map ); + + lua_settop(L, n); + // create metatable + luaL_newmetatable(L, "coap_client"); + // metatable.__index = metatable + lua_pushliteral(L, "__index"); + lua_pushvalue(L,-2); + lua_rawset(L,-3); + // Setup the methods inside metatable + luaL_register( L, NULL, coap_client_map ); + + return 1; +#endif // #if LUA_OPTIMIZE_MEMORY > 0 +} diff --git a/app/lua/linit.c b/app/modules/linit.c similarity index 59% rename from app/lua/linit.c rename to app/modules/linit.c index 872321ef..8bdbedfe 100644 --- a/app/lua/linit.c +++ b/app/modules/linit.c @@ -15,6 +15,8 @@ #include "lrotable.h" #include "luaconf.h" +#include "user_modules.h" + #if defined(LUA_USE_MODULES) #include "modules.h" #endif @@ -28,12 +30,26 @@ LUA_MODULES_ROM static const luaL_Reg lualibs[] = { {"", luaopen_base}, {LUA_LOADLIBNAME, luaopen_package}, - // {LUA_IOLIBNAME, luaopen_io}, - {LUA_STRLIBNAME, luaopen_string}, +#if defined(LUA_USE_BUILTIN_IO) + {LUA_IOLIBNAME, luaopen_io}, +#endif + +#if defined(LUA_USE_BUILTIN_STRING) + {LUA_STRLIBNAME, luaopen_string}, +#endif + #if LUA_OPTIMIZE_MEMORY == 0 - // {LUA_MATHLIBNAME, luaopen_math}, + #if defined(LUA_USE_BUILTIN_MATH) + {LUA_MATHLIBNAME, luaopen_math}, + #endif + + #if defined(LUA_USE_BUILTIN_TABLE) {LUA_TABLIBNAME, luaopen_table}, - // {LUA_DBLIBNAME, luaopen_debug}, + #endif + + #if defined(LUA_USE_BUILTIN_DEBUG) + {LUA_DBLIBNAME, luaopen_debug}, + #endif #endif #if defined(LUA_MODULES_ROM) #undef _ROM @@ -43,12 +59,30 @@ static const luaL_Reg lualibs[] = { {NULL, NULL} }; +#if defined(LUA_USE_BUILTIN_STRING) extern const luaR_entry strlib[]; +#endif + +#if defined(LUA_USE_BUILTIN_OS) extern const luaR_entry syslib[]; +#endif + +#if defined(LUA_USE_BUILTIN_TABLE) extern const luaR_entry tab_funcs[]; -// extern const luaR_entry dblib[]; +#endif + +#if defined(LUA_USE_BUILTIN_DEBUG) +extern const luaR_entry dblib[]; +#endif + +#if defined(LUA_USE_BUILTIN_COROUTINE) extern const luaR_entry co_funcs[]; -// extern const luaR_entry math_map[]; +#endif + +#if defined(LUA_USE_BUILTIN_MATH) +extern const luaR_entry math_map[]; +#endif + #if defined(LUA_MODULES_ROM) && LUA_OPTIMIZE_MEMORY == 2 #undef _ROM #define _ROM( name, openf, table ) extern const luaR_entry table[]; @@ -57,11 +91,30 @@ LUA_MODULES_ROM const luaR_table lua_rotable[] = { #if LUA_OPTIMIZE_MEMORY > 0 + #if defined(LUA_USE_BUILTIN_STRING) {LUA_STRLIBNAME, strlib}, + #endif + + #if defined(LUA_USE_BUILTIN_TABLE) {LUA_TABLIBNAME, tab_funcs}, - // {LUA_DBLIBNAME, dblib}, + #endif + + #if defined(LUA_USE_BUILTIN_DEBUG) + {LUA_DBLIBNAME, dblib}, + #endif + + #if defined(LUA_USE_BUILTIN_COROUTINE) {LUA_COLIBNAME, co_funcs}, - // {LUA_MATHLIBNAME, math_map}, + #endif + + #if defined(LUA_USE_BUILTIN_MATH) + {LUA_MATHLIBNAME, math_map}, + #endif + + #if defined(LUA_USE_BUILTIN_OS) + {LUA_OSLIBNAME, syslib}, + #endif + #if defined(LUA_MODULES_ROM) && LUA_OPTIMIZE_MEMORY == 2 #undef _ROM #define _ROM( name, openf, table ) { name, table }, diff --git a/app/modules/modules.h b/app/modules/modules.h index 0730fd74..3f077efd 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_COAP) +#define MODULES_COAP "coap" +#define ROM_MODULES_COAP \ + _ROM(MODULES_COAP, luaopen_coap, coap_map) +#else +#define ROM_MODULES_COAP +#endif + #if defined(LUA_USE_MODULES_MQTT) #define MODULES_MQTT "mqtt" #define ROM_MODULES_MQTT \ @@ -136,8 +144,9 @@ #define LUA_MODULES_ROM \ ROM_MODULES_GPIO \ - ROM_MODULES_PWM \ - ROM_MODULES_WIFI \ + ROM_MODULES_PWM \ + ROM_MODULES_WIFI \ + ROM_MODULES_COAP \ ROM_MODULES_MQTT \ ROM_MODULES_U8G \ ROM_MODULES_I2C \ diff --git a/app/modules/mqtt.c b/app/modules/mqtt.c index 13b182bc..018e034e 100644 --- a/app/modules/mqtt.c +++ b/app/modules/mqtt.c @@ -1102,7 +1102,7 @@ const LUA_REG_TYPE mqtt_map[] = { LNILKEY, LNILVAL } }; -LUALIB_API int ICACHE_FLASH_ATTR luaopen_mqtt( lua_State *L ) +LUALIB_API int luaopen_mqtt( lua_State *L ) { #if LUA_OPTIMIZE_MEMORY > 0 luaL_rometatable(L, "mqtt.socket", (void *)mqtt_socket_map); // create metatable for mqtt.socket diff --git a/app/modules/node.c b/app/modules/node.c index 90dd0d96..0384d798 100644 --- a/app/modules/node.c +++ b/app/modules/node.c @@ -23,6 +23,7 @@ #include "user_interface.h" #include "flash_api.h" #include "flash_fs.h" +#include "user_version.h" // Lua: restart() static int node_restart( lua_State* L ) diff --git a/examples/fragment.lua b/examples/fragment.lua index bceadaf3..c3b980dc 100644 --- a/examples/fragment.lua +++ b/examples/fragment.lua @@ -347,3 +347,19 @@ file.close() node.compile("hello.lua") dofile("hello.lua") dofile("hello.lc") + +-- use copper addon for firefox +cs=coap.Server() +cs:listen(5683) + +myvar=1 +cs:var("myvar") -- get coap://192.168.18.103:5683/v1/v/myvar will return the value of myvar: 1 + +function myfun() + print("myfun called") +end +cs:func("myfun") -- post coap://192.168.18.103:5683/v1/f/myfun will call myfun + +cc = coap.Client() +cc:get(coap.CON, "coap://192.168.18.100:5683/.well-known/core") +cc:post(coap.NON, "coap://192.168.18.100:5683/", "Hello") diff --git a/ld/eagle.app.v6.ld b/ld/eagle.app.v6.ld index d6e37116..141cab6d 100644 --- a/ld/eagle.app.v6.ld +++ b/ld/eagle.app.v6.ld @@ -5,7 +5,7 @@ MEMORY dport0_0_seg : org = 0x3FF00000, len = 0x10 dram0_0_seg : org = 0x3FFE8000, len = 0x14000 iram1_0_seg : org = 0x40100000, len = 0x8000 - irom0_0_seg : org = 0x40210000, len = 0x55000 + irom0_0_seg : org = 0x40210000, len = 0x5A000 } PHDRS diff --git a/lib/libm.a b/lib/libm.a new file mode 100644 index 00000000..bf04ff11 Binary files /dev/null and b/lib/libm.a differ diff --git a/lua_modules/hdc1000/HDC1000-example.lua b/lua_modules/hdc1000/HDC1000-example.lua new file mode 100644 index 00000000..df7dbd27 --- /dev/null +++ b/lua_modules/hdc1000/HDC1000-example.lua @@ -0,0 +1,12 @@ +HDC1000 = require("HDC1000") + +sda = 1 +scl = 2 + +HDC1000.init(sda, scl) +HDC1000.config() -- default values are used if called with no arguments. prototype is config(address, resolution, heater) + +print(string.format("Temperature: %.2f °C\nHumidity: %.2f %%", HDC1000.getTemp(), HDC1000.getHumi())) + +HDC1000 = nil +package.loaded["HDC1000"]=nil \ No newline at end of file diff --git a/lua_modules/hdc1000/HDC1000.lua b/lua_modules/hdc1000/HDC1000.lua new file mode 100644 index 00000000..80f7e499 --- /dev/null +++ b/lua_modules/hdc1000/HDC1000.lua @@ -0,0 +1,99 @@ +------------------------------------------------------- + +-- This library was written for the Texas Instruments +-- HDC1000 temperature and humidity sensor. +-- It should work for the HDC1008 too. +-- Written by Francesco Truzzi (francesco@truzzi.me) +-- Released under GNU GPL v2.0 license. + +------------------------------------------------------- + +-------------- NON-DEFAULT CONFIG VALUES -------------- +------------- config() optional arguments ------------- + +-- HDC1000_HEAT_OFF 0x00 (heater) +-- HDC1000_TEMP_11BIT 0x40 (resolution) +-- HDC1000_HUMI_11BIT 0x01 (resolution) +-- HDC1000_HUMI_8BIT 0x20 (resolution) + +------------------------------------------------------- + +local modname = ... +local M = {} +_G[modname] = M + +local id = 0 +local i2c = i2c +local delay = 20000 + +local HDC1000_ADDR = 0x40 + +local HDC1000_TEMP = 0x00 +local HDC1000_HUMI = 0x01 +local HDC1000_CONFIG = 0x02 + +local HDC1000_HEAT_ON = 0x20 +local HDC1000_TEMP_HUMI_14BIT = 0x00 + +-- reads 16bits from the sensor +local function read16() + i2c.start(id) + i2c.address(id, HDC1000_ADDR, i2c.RECEIVER) + data_temp = i2c.read(0, 2) + i2c.stop(id) + data = bit.lshift(string.byte(data_temp, 1, 1), 8) + string.byte(data_temp, 2, 2) + return data +end + +-- sets the register to read next +local function setReadRegister(register) + i2c.start(id) + i2c.address(id, HDC1000_ADDR, i2c.TRANSMITTER) + i2c.write(id, register) + i2c.stop(id) +end + +-- writes the 2 configuration bytes +local function writeConfig(config) + i2c.start(id) + i2c.address(id, HDC1000_ADDR, i2c.TRANSMITTER) + i2c.write(id, HDC1000_CONFIG, config, 0x00) + i2c.stop(id) +end + +-- returns true if battery voltage is < 2.7V, false otherwise +function M.batteryDead() + setReadRegister(HDC1000_CONFIG) + return(bit.isset(read16(), 11)) + +end + +-- initalize i2c +function M.init(sda, scl) + i2c.setup(id, sda, scl, i2c.SLOW) +end + +function M.config(addr, resolution, heater) + -- default values are set if the function is called with no arguments + HDC1000_ADDR = addr or HDC1000_ADDR + resolution = resolution or HDC1000_TEMP_HUMI_14BIT + heater = heater or HDC1000_HEAT_ON + writeConfig(bit.bor(resolution, heater)) +end + +-- outputs temperature in Celsius degrees +function M.getHumi() + setReadRegister(HDC1000_HUMI) + tmr.delay(delay) + return(read16()/65535.0*100) +end + +-- outputs humidity in %RH +function M.getTemp() + setReadRegister(HDC1000_TEMP) + tmr.delay(delay) + return(read16()/65535.0*165-40) +end + +return M + diff --git a/lua_modules/hdc1000/README.md b/lua_modules/hdc1000/README.md new file mode 100644 index 00000000..398cccee --- /dev/null +++ b/lua_modules/hdc1000/README.md @@ -0,0 +1,39 @@ +HDC1000 NodeMCU module +======================= + +Here's my NodeMCU module for the TI HDC1000 temperature and humidity sensor. It should work with the HDC1008 too but I haven't tested it. + +### Setup your sensor: +First, require it: + +`HDC1000 = require("HDC1000")` + +Then, initialize it: + +`HDC1000.init(sda, scl)` + +Configure it: + +`HDC1000.config()` + +Default options set the address to 0x40 and enable both temperature and humidity readings at 14-bit resolution, with the integrated heater on. You can change them by initializing your sensor like this: + +`HDC1000.config(address, resolution, heater);` + +"resolution" can be set to 14 bits for both temperature and humidity (0x00 - default) 11 bits for temperature (0x40), 11 bits for humidity (0x01), 8 bits for humidity (0x20) +"heater" can be set to ON (0x20 - default) or OFF (0x00) + +### Read some values +You can read temperature and humidity by using the following commands: + +`temperature = HDC1000.getTemp()` in Celsius degrees. + +`humidity = HDC1000.getHumi()` in % + +### Check your battery + +The following code returns true if the battery voltage is <2.8V, false otherwise. + +`isDead = HDC1000.batteryDead();` + +Happy making! Also, check out my Breakout Board and Arduino library for this chip: http://b.truzzi.me/hdc1000-temperature-and-humidity-sensor-breakout-with-arduino-library/.