Commit d77666c0 authored by sergio's avatar sergio Committed by Terry Ellison
Browse files

trailing spaces cleanup (#2659)

parent d7583040
......@@ -141,7 +141,7 @@ void nodemcu_init(void) {
uint32_t size_detected = flash_detect_size_byte();
uint32_t size_from_rom = flash_rom_get_size_byte();
if( size_detected != size_from_rom ) {
NODE_ERR("Self adjust flash size. 0x%x (ROM) -> 0x%x (Detected)\n",
NODE_ERR("Self adjust flash size. 0x%x (ROM) -> 0x%x (Detected)\n",
size_from_rom, size_detected);
// Fit hardware real flash size.
flash_rom_set_size_byte(size_detected);
......
......@@ -23,7 +23,7 @@ endif
# makefile at its root level - these are then overridden
# for a subtree within the makefile rooted therein
#
#DEFINES +=
#DEFINES +=
#############################################################
# Recursion Magic - Don't touch this!!
......
#
#
# This Make file is called from the core Makefile hierarchy with is a hierarchical
# make wwhich uses parent callbacks to implement inheritance. However is luac_cross
# build stands outside this and uses the host toolchain to implement a separate
# host build of the luac.cross image.
# host build of the luac.cross image.
#
.NOTPARALLEL:
summary ?= @true
CCFLAGS:= -I..
CCFLAGS:= -I..
LDFLAGS:= -L$(SDK_DIR)/lib -L$(SDK_DIR)/ld -lm -ldl -Wl,-Map=mapfile
CCFLAGS += -Wall
#DEFINES +=
#DEFINES +=
TARGET = host
......
/************************************************************************
* NodeMCU unzip wrapper code for uzlib_inflate
*
* Note that whilst it would be more straightforward to implement a
* Note that whilst it would be more straightforward to implement a
* simple in memory approach, this utility adopts the same streaming
* callback architecture as app/lua/lflash.c to enable this code to be
* tested in a pure host development environment
* tested in a pure host development environment
*/
#include <string.h>
#include <stdio.h>
......@@ -22,7 +22,7 @@
typedef uint8_t uchar;
typedef uint16_t ushort;
typedef uint32_t uint;
struct INPUT {
FILE *fin;
int len;
......@@ -49,12 +49,12 @@ struct OUTPUT {
/*
* uzlib_inflate does a stream inflate on an RFC 1951 encoded data stream.
* uzlib_inflate does a stream inflate on an RFC 1951 encoded data stream.
* It uses three application-specific CBs passed in the call to do the work:
*
* - get_byte() CB to return next byte in input stream
* - put_byte() CB to output byte to output buffer
* - recall_byte() CB to output byte to retrieve a historic byte from
* - recall_byte() CB to output byte to retrieve a historic byte from
* the output buffer.
*
* Note that put_byte() also triggers secondary CBs to do further processing.
......@@ -65,13 +65,13 @@ uint8_t get_byte (void) {
/* Read next input block */
int remaining = in->len - in->bytesRead;
int wanted = remaining >= READ_BLOCKSIZE ? READ_BLOCKSIZE : remaining;
if (fread(in->block, 1, wanted, in->fin) != wanted)
UZLIB_THROW(UZLIB_DATA_ERROR);
in->bytesRead += wanted;
in->inPtr = in->block;
in->left = wanted-1;
in->left = wanted-1;
}
return *in->inPtr++;
}
......@@ -79,10 +79,10 @@ uint8_t get_byte (void) {
void put_byte (uint8_t value) {
int offset = out->ndx % WRITE_BLOCKSIZE; /* counts from 0 */
out->block[0]->byte[offset++] = value;
out->ndx++;
if (offset == WRITE_BLOCKSIZE || out->ndx == out->len) {
if (out->fullBlkCB)
out->fullBlkCB();
......@@ -97,7 +97,7 @@ void put_byte (uint8_t value) {
uint8_t recall_byte (uint offset) {
if(offset > DICTIONARY_WINDOW || offset >= out->ndx)
UZLIB_THROW(UZLIB_DICT_ERROR);
/* ndx starts at 1. Need relative to 0 */
/* ndx starts at 1. Need relative to 0 */
uint n = out->ndx - offset;
uint pos = n % WRITE_BLOCKSIZE;
uint blockNo = out->ndx / WRITE_BLOCKSIZE - n / WRITE_BLOCKSIZE;
......@@ -110,7 +110,7 @@ int processOutRec (void) {
WRITE_BLOCKSIZE;
if (fwrite(out->block[0], 1, len, out->fout) != len)
UZLIB_THROW(UZLIB_DATA_ERROR);
out->crc = uzlib_crc32(out->block[0], len, out->crc);
out->written += len;
......@@ -118,7 +118,7 @@ int processOutRec (void) {
fclose(out->fout);
out->fullBlkCB = NULL;
}
return 1;
return 1;
}
......@@ -143,24 +143,24 @@ int main(int argc, char *argv[]) {
assert(fread((uchar*)&(out->len), 1, 4, in->fin) == 4);
in->len = ftell(in->fin);
fseek(in->fin, 0, SEEK_SET);
assert((out->fout = fopen(outFile, "wb")) != NULL);
printf ("Inflating in=%s out=%s\n", inFile, outFile);
/* Allocate the out buffers (number depends on the unpacked length) */
n = (out->len > DICTIONARY_WINDOW) ? WRITE_BLOCKS :
n = (out->len > DICTIONARY_WINDOW) ? WRITE_BLOCKS :
1 + (out->len-1) / WRITE_BLOCKSIZE;
for(i = WRITE_BLOCKS - n + 1; i <= WRITE_BLOCKS; i++)
assert((out->block[i % WRITE_BLOCKS] = uz_malloc(sizeof(outBlock))) != NULL);
out->breakNdx = (out->len < WRITE_BLOCKSIZE) ? out->len : WRITE_BLOCKSIZE;
out->fullBlkCB = processOutRec;
out->crc = ~0;
/* Call inflate to do the business */
res = uzlib_inflate (get_byte, put_byte, recall_byte, in->len, &crc, &cxt_not_used);
if (res > 0 && crc != ~out->crc)
res = UZLIB_CHKSUM_ERROR;
......
......@@ -23,11 +23,11 @@ int main (int argc, char **argv) {
(iLen = ftell(fin)) <= 0 || fseek(fin, 0, SEEK_SET))
return 1;
if ((fout = fopen(out, "wb")) == NULL ||
(iBuf = (uint8_t *) uz_malloc(iLen)) == NULL ||
fread(iBuf, 1, iLen, fin) != iLen)
(iBuf = (uint8_t *) uz_malloc(iLen)) == NULL ||
fread(iBuf, 1, iLen, fin) != iLen)
return 1;
if (uzlib_compress (&oBuf, &oLen, iBuf, iLen) == UZLIB_OK &&
if (uzlib_compress (&oBuf, &oLen, iBuf, iLen) == UZLIB_OK &&
oLen == fwrite(oBuf, oLen, 1, fout))
status = UZLIB_OK;
uz_free(iBuf);
......
......@@ -292,7 +292,7 @@ void outBytes(void *bytes, int nBytes) {
/* flush this first, if necessary */
oBuf->nBits = oBuf->bits = 0;
for (i = 0; i < nBytes; i++) {
DBG_PRINT("%02x-", *((uchar*)bytes+i));
DBG_PRINT("%02x-", *((uchar*)bytes+i));
oBuf->buffer[oBuf->len++] = *((uchar*)bytes+i);
}
}
......@@ -511,8 +511,8 @@ void uzlibCompressBlock(const uchar *src, uint srcLen) {
}
}
}
if (lastOffset) { /* flush cached match if any */
if (lastOffset) { /* flush cached match if any */
copy(lastOffset, lastLen);
DBG_PRINT("dic: %6x %6x %6x\n", i, lastLen, lastOffset);
i += lastLen - 1;
......
......@@ -110,7 +110,7 @@ struct uzlib_data {
int bFinal;
uint curLen;
uint checksum;
};
};
/*
* Note on changes to layout, naming, etc. This module combines extracts
......@@ -164,13 +164,13 @@ static int getbit (UZLIB_DATA *d) {
/* read a num bit value from a stream and add base */
static uint read_bits (UZLIB_DATA *d, int num, int base) {
/* This is an optimised version which doesn't call getbit num times */
if (!num)
if (!num)
return base;
uint i, n = (((uint)-1)<<num);
uint i, n = (((uint)-1)<<num);
for (i = d->bitcount; i < num; i +=8)
d->tag |= ((uint)d->get_byte()) << i;
n = d->tag & ~n;
d->tag >>= num;
d->bitcount = i - num;
......@@ -596,7 +596,7 @@ int uzlib_inflate (
if (res == UZLIB_DONE) {
d->checksum = get_le_uint32(d);
(void) get_le_uint32(d); /* already got length so ignore */
(void) get_le_uint32(d); /* already got length so ignore */
}
UZLIB_THROW(res);
......
......@@ -24,7 +24,7 @@ STD_CFLAGS=-std=gnu11 -Wimplicit
# makefile at its root level - these are then overridden
# for a subtree within the makefile rooted therein
#
#DEFINES +=
#DEFINES +=
#############################################################
# Recursion Magic - Don't touch this!!
......
......@@ -82,7 +82,7 @@ static char *cryptoSha1(char *data, unsigned int len) {
SHA1_CTX ctx;
SHA1Init(&ctx);
SHA1Update(&ctx, data, len);
uint8_t *digest = (uint8_t *) c_zalloc(20);
SHA1Final(digest, &ctx);
return (char *) digest; // Requires free
......@@ -188,7 +188,7 @@ static void ws_closeSentCallback(void *arg) {
static void ws_sendFrame(struct espconn *conn, int opCode, const char *data, unsigned short len) {
NODE_DBG("ws_sendFrame %d %d\n", opCode, len);
ws_info *ws = (ws_info *) conn->reverse;
if (ws->connectionState == 4) {
NODE_DBG("already in closing state\n");
return;
......@@ -243,7 +243,7 @@ static void ws_sendFrame(struct espconn *conn, int opCode, const char *data, uns
// Apply mask to encode payload
int i;
for (i = 0; i < len; i++) {
b[bufOffset + i] ^= b[bufOffset - 4 + i % 4];
b[bufOffset + i] ^= b[bufOffset - 4 + i % 4];
}
bufOffset += len;
......@@ -819,7 +819,7 @@ void ws_connect(ws_info *ws, const char *url) {
conn->proto.tcp = (esp_tcp *) c_zalloc(sizeof(esp_tcp));
conn->proto.tcp->local_port = espconn_port();
conn->proto.tcp->remote_port = ws->port;
conn->reverse = ws;
ws->conn = conn;
......
......@@ -78,7 +78,7 @@ the firmware to lock onto that baud rate (between 1200 and 230400).
### Integer build
By default a build will be generated supporting floating-point variables.
To reduce memory size an integer build can be created. You can change this
To reduce memory size an integer build can be created. You can change this
either by uncommenting `LUA_NUMBER_INTEGRAL` in `app/include/user_config.h`:
```c
......
Whilst the Lua Virtual Machine (LVM) can compile Lua source dynamically and this can prove
very flexible during development, you will use less RAM resources if you precompile
very flexible during development, you will use less RAM resources if you precompile
your sources before execution.
## Compiling Lua directly on your ESP8266
......@@ -9,7 +9,7 @@ your sources before execution.
- [`node.compile()`](modules/node/#nodecompile) wraps this 'load and dump to file' operation into a single atomic library call.
The issue with both of these approaches is that compilation is RAM-intensive and hence
you will find that you will need to break your application into a lot of small and
you will find that you will need to break your application into a lot of small and
compilable modules in order to avoid hitting RAM constraints. This can be mitigated
by doing all compiles immediately after a [node.restart()`](modules/node/#noderestart).
......@@ -18,16 +18,16 @@ by doing all compiles immediately after a [node.restart()`](modules/node/#nodere
If you install `lua` on your development PC or Laptop then you can use the standard Lua
compiler to syntax check any Lua source before downloading it to the ESP8266 module. However,
the NodeMCU compiler output uses different data types (e.g. it supports ROMtables) so the
compiled output from standard `luac` cannot run on the ESP8266.
compiled output from standard `luac` cannot run on the ESP8266.
Compiling source on one platform for use on another (e.g. Intel 64-bit Windows to ESP8266) is
Compiling source on one platform for use on another (e.g. Intel 64-bit Windows to ESP8266) is
known as _cross-compilation_ and the NodeMCU firmware build now automatically generates
a `luac.cross` image as standard in the firmware root directory; this can be used to
compile and to syntax-check Lua source on the Development machine for execution under
compile and to syntax-check Lua source on the Development machine for execution under
NodeMCU Lua on the ESP8266.
`luac.cross` will translate Lua source files into binary files that can be later loaded
and executed by the LVM. Such binary files, which normally have the `.lc` (lua code)
and executed by the LVM. Such binary files, which normally have the `.lc` (lua code)
extension are loaded directly by the LVM without the RAM overhead of compilation.
Each `luac.cross` execution produces a single output file containing the bytecodes
......@@ -37,12 +37,12 @@ even Lua binary files) on the command line. You can use '-' to indicate the
standard input as a source file and '--' to signal the end of options (that is, all
remaining arguments will be treated as files even if they start with '-').
`luac.cross` supports the standard `luac` options `-l`, `-o`, `-p`, `-s` and `-v`,
`luac.cross` supports the standard `luac` options `-l`, `-o`, `-p`, `-s` and `-v`,
as well as the `-h` option which produces the current help overview.
NodeMCU also implements some major extensions to support the use of the
[Lua Flash Store (LFS)](lfs.md)), in that it can produce an LFS image file which
is loaded as an overlay into the firmware in flash memory; the LVM can access and
NodeMCU also implements some major extensions to support the use of the
[Lua Flash Store (LFS)](lfs.md)), in that it can produce an LFS image file which
is loaded as an overlay into the firmware in flash memory; the LVM can access and
execute this code directly from flash without needing to store code in RAM. This
mode is enabled by specifying the `-f`option.
......@@ -50,9 +50,9 @@ mode is enabled by specifying the `-f`option.
- **Compact relocatable**. This is selected by the `-f` option. Here the compiler compresses the compiled binary so that image is small for downloading over Wifi/WAN (e.g. a full 64Kb LFS image is compressed down to a 22Kb file.) The LVM processes such image in two passes with the integrity of the image validated on the first, and the LFS itself gets updated on the second. The LVM also checks that the image will fit in the allocated LFS region before loading, but you can also use the `-m` option to throw a compile error if the image is too large, for example `-m 0x10000` will raise an error if the image will not load into a 64Kb regions.
- **Absolute**. This is selected by the `-a <baseAddr>` option. Here the compiler fixes all addresses relative to the base address specified. This allows an LFS absolute image to be loaded directly into the ESP flash using a tool such as `esptool.py`.
- **Absolute**. This is selected by the `-a <baseAddr>` option. Here the compiler fixes all addresses relative to the base address specified. This allows an LFS absolute image to be loaded directly into the ESP flash using a tool such as `esptool.py`.
These two modes target two separate use cases: the compact relocatable format
These two modes target two separate use cases: the compact relocatable format
facilitates simple OTA updates to an LFS based Lua application; the absolute format
facilitates factory installation of LFS based applications.
......@@ -65,4 +65,4 @@ just the `luac.cross` image. You must first ensure that the following options i
//#define LUA_FLASH_STORE 0x10000 // uncomment if you LFS support
```
Developers have successfully built this on Linux (including docker builds), MacOS, Win10/WSL and WinX/Cygwin.
Developers have successfully built this on Linux (including docker builds), MacOS, Win10/WSL and WinX/Cygwin.
......@@ -5,7 +5,7 @@
## How does the non-OS SDK structure execution
Details of the execution model for the **non-OS SDK** is not well documented by
Details of the execution model for the **non-OS SDK** is not well documented by
Espressif. This section summarises the project's understanding of how this execution
model works based on the Espressif-supplied examples and SDK documentation, plus
various posts on the Espressif BBS and other forums, and an examination of the
......@@ -21,7 +21,7 @@ which are also used by the SDK. In this model, execution units are either:
critical and should complete in no more than 50 µSec.
ISR code and data constants should be run out of RAM or ROM, for two reasons:
if an ISR interrupts a flash I/O operation (which must disable the Flash
if an ISR interrupts a flash I/O operation (which must disable the Flash
instruction cache) and a cache miss occurs, then the ISR will trigger a
fatal exception; secondly, the
execution time for Flash memory (that is located in the `irom0` load section)
......@@ -32,7 +32,7 @@ which are also used by the SDK. In this model, execution units are either:
guidelines. (Note that any time critical code within normal execution and that
is bracketed by interrupt lock / unlock guards should also follow this 50
µSec guideline.)<br/><br/>
- **TASKS**. A task is a normal execution unit running at a non-interrupt priority.
Tasks can be executed from Flash memory. An executing task can be interrupted
by one or more ISRs being delivered, but it won't be preempted by another
......@@ -93,5 +93,5 @@ normal LLT to execute a `task_post_YYY(XXX_callback_handle,param)` where YYY is
one of `low`, `medium`, `high`. The callback will then be executed when the SDK
delivers the task.
_Note_: `task_post_YYY` can fail with a false return if the task Q is full.
_Note_: `task_post_YYY` can fail with a false return if the task Q is full.
......@@ -93,7 +93,7 @@ Espressif refers to this area as "System Param" and it resides in the last four
The default init data is provided as part of the SDK in the file `esp_init_data_default.bin`. NodeMCU will automatically flash this file to the right place on first boot if the sector appears to be empty.
If you need to customize init data then first download the [Espressif SDK 2.2.0](https://github.com/espressif/ESP8266_NONOS_SDK/archive/v2.2.0.zip) and extract `esp_init_data_default.bin`. Then flash that file just like you'd flash the firmware. The correct address for the init data depends on the capacity of the flash chip.
If you need to customize init data then first download the [Espressif SDK 2.2.0](https://github.com/espressif/ESP8266_NONOS_SDK/archive/v2.2.0.zip) and extract `esp_init_data_default.bin`. Then flash that file just like you'd flash the firmware. The correct address for the init data depends on the capacity of the flash chip.
- `0x7c000` for 512 kB, modules like most ESP-01, -03, -07 etc.
- `0xfc000` for 1 MB, modules like ESP8285, PSF-A85, some ESP-01, -03 etc.
......
......@@ -6,8 +6,8 @@ The basic process to get started with NodeMCU consists of the following three st
1. [Flash the firmware](flash.md) to the chip
1. [Upload code](upload.md) to the device.
You will typically do steps 1 and 2 only once, but then repeat step 3 as you develop your application. If your application outgrows the limited on-chip RAM then you can use the [Lua Flash Store](lfs.md) (LFS) to move your Lua code into flash memory, freeing a lot more RAM for variable data. This is why it is a good idea to enable LFS for step 1 if you are developing a larger application. As documented below there is a different approach to uploading Lua code.
You will typically do steps 1 and 2 only once, but then repeat step 3 as you develop your application. If your application outgrows the limited on-chip RAM then you can use the [Lua Flash Store](lfs.md) (LFS) to move your Lua code into flash memory, freeing a lot more RAM for variable data. This is why it is a good idea to enable LFS for step 1 if you are developing a larger application. As documented below there is a different approach to uploading Lua code.
!!! caution
There's more than one way to skin a cat. For each of the tasks you have a number of choices with regards to tooling. The colored boxes represent an opinionated path to start your journey - the quickest way to success so to speak. Feel free to follow the links above to get more detailed information.
......@@ -48,7 +48,7 @@ You will typically do steps 1 and 2 only once, but then repeat step 3 as you dev
<td><a href="#esptoolpy">esptool.py</a></td>
<td><a href="#esptoolpy">esptool.py</a></td>
<td class="select"><a href="#esptoolpy">esptool.py</a></td>
</tr>
</tr>
<tr>
<th rowspan="2">Upload code</th>
<td class="select"><a href="#esplorer">ESPlorer (Java)</a></td>
......@@ -59,7 +59,7 @@ You will typically do steps 1 and 2 only once, but then repeat step 3 as you dev
<td><a href="#nodemcu-tool">NodeMCU-Tool (Node.js)</a></td>
<td class="select"><a href="#nodemcu-tool">NodeMCU-Tool (Node.js)</a></td>
<td class="select"><a href="#nodemcu-tool">NodeMCU-Tool (Node.js)</a></td>
</tr>
</tr>
<t>
<th colspan="4">LFS tasks below</th>
</tr>
......@@ -111,7 +111,7 @@ You will typically do steps 1 and 2 only once, but then repeat step 3 as you dev
<td class="select"><a href="#upload-lfs-image">generic</a></td>
<td class="select"><a href="#upload-lfs-image">generic</a></td>
<td class="select"><a href="#upload-lfs-image">generic</a></td>
</tr>
</tr>
</table>
**How to read this**
......@@ -131,7 +131,7 @@ Our intention is to introduce you to programming in Lua on the ESP8266 as quickl
## Cloud Builder
The cloud builder at [https://nodemcu-build.com](https://nodemcu-build.com) allows to pick NodeMCU branch, modules and a few other configuration options (e.g. SSL yes/no). After the build is completed you will receive an email with two links to download your custom firmware:
The cloud builder at [https://nodemcu-build.com](https://nodemcu-build.com) allows to pick NodeMCU branch, modules and a few other configuration options (e.g. SSL yes/no). After the build is completed you will receive an email with two links to download your custom firmware:
- one for NodeMCU with floating support
- one for NodeMCU *without* floating support i.e. an integer-only binary
......@@ -248,7 +248,7 @@ Windows 10 users can install and use the Windows Subsystem for Linux (WSL). Alte
TBD
1. `$ cd app/lua/luac_cross`
2. `$ make`
2. `$ make`
### Linux
......@@ -264,9 +264,9 @@ TBD
### Select Lua files to be run from LFS
The easiest approach is to maintain all the Lua files for your project in a single directory on your host. (These files will be compiled by `luac.cross` to build the LFS image in next step.)
For example to run the Telnet and FTP servers from LFS, put the following files in your project directory:
* [lua_examples/lfs/_init.lua](https://github.com/nodemcu/nodemcu-firmware/tree/dev/lua_examples/lfs/_init.lua). LFS helper routines and functions.
* [lua_examples/lfs/dummy_strings.lua](https://github.com/nodemcu/nodemcu-firmware/tree/dev/lua_examples/lfs/dummy_strings.lua). Moving common strings into LFS.
* [lua_examples/telnet/telnet.lua](https://github.com/nodemcu/nodemcu-firmware/tree/dev/lua_examples/telnet/telnet.lua). A simple **telnet** server.
......@@ -313,7 +313,7 @@ You might also want to add a simple one-line script file to your `~/bin` directo
## Upload LFS image
The compiled LFS image file (e.g. `lfs.img`) is uploaded as a regular file to the device file system (SPIFFS). You do this just like with Lua files with e.g. [ESPlorer](#esplorer) or [NodeMCU-Tool](#nodemcu-tool). There is also a new example, [HTTP_OTA.lua](https://github.com/nodemcu/nodemcu-firmware/tree/dev/lua_examples/lfs/HTTP_OTA.lua), in `lua_examples` that can retrieve images from a standard web service.
Once the LFS image file is on SPIFFS, you can execute the [node.flashreload()](../modules/node/#nodeflashreload) command and the loader will then load it into flash and immediately restart the ESP module with the new LFS loaded, if the image file is valid. However, the call will return with an error _if_ the file is found to be invalid, so your reflash code should include logic to handle such an error return.
### Edit your `init.lua` file
......@@ -323,6 +323,6 @@ Once the LFS image file is on SPIFFS, you can execute the [node.flashreload()](.
- Individual functions can be executed directly, e.g. `LFS.myfunc(a,b)`
- LFS is now in the require path, so `require 'myModule'` works as expected.
Do a protected call of this `_init` code: `pcall(node.flashindex("_init"))` and check the error status. See [Programming Techniques and Approachs](lfs.md#programming-techniques-and-approachs) in the LFS whitepaper for a more detailed description.
Do a protected call of this `_init` code: `pcall(node.flashindex("_init"))` and check the error status. See [Programming Techniques and Approachs](lfs.md#programming-techniques-and-approachs) in the LFS whitepaper for a more detailed description.
[↑ back to matrix](#task-os-selector)
# Hardware FAQ
This content is now maintained at [http://www.esp8266.com/wiki/doku.php?id=nodemcu-unofficial-faq](http://www.esp8266.com/wiki/doku.php?id=nodemcu-unofficial-faq).
......@@ -29,7 +29,7 @@ wifi.sta.config("SSID", "password")
```lua
-- register event callbacks for WiFi events
wifi.sta.eventMonReg(wifi.STA_CONNECTING, function(previous_state)
if(previous_state==wifi.STA_GOTIP) then
if(previous_state==wifi.STA_GOTIP) then
print("Station lost connection with access point. Attempting to reconnect...")
else
print("STATION_CONNECTING")
......
......@@ -55,7 +55,7 @@ The `stripdebug([level[, function]])` call is processed as follows:
- If both arguments are omitted then the function returns the current default strip level.
- If the function parameter is omitted, then the level is used as the default setting for future compiles. The level must be 1-3 corresponding to the above debug optimization settings. Hence if `stripdebug(3)` is included in **init.lua**, then all debug information will be stripped out of subsequently compiled functions.
- If the function parameter is omitted, then the level is used as the default setting for future compiles. The level must be 1-3 corresponding to the above debug optimization settings. Hence if `stripdebug(3)` is included in **init.lua**, then all debug information will be stripped out of subsequently compiled functions.
- The function parameter if present is parsed in the same way as the function argument in `setfenv()` (except that the integer 0 level is not permitted, and this function tree corresponding to this scope is walked to implement this debug optimization level.
......@@ -92,7 +92,7 @@ In use there is little noticeable difference other than the code size during dev
### How to enable LCD
Enabling LCD is simple: all you need is a patched version and define `LUA_OPTIMIZE_DEBUG` at the default level that you want in `app/include/user_config.h` and do a normal make.
Enabling LCD is simple: all you need is a patched version and define `LUA_OPTIMIZE_DEBUG` at the default level that you want in `app/include/user_config.h` and do a normal make.
Without this define enabled, the unpatched version is generated.
......
......@@ -25,7 +25,7 @@ However, most Lua developers seem to prefer the convenience of our [Cloud Build
Variable | Option
---------|------------
LFS size | (none, 32, 64, 96 or 128Kb) The default is none. The default is none, in which case LFS is disabled. Selecting a numeric value enables LFS with the LFS region sized at this value.
SPIFFS base | If you have a 4Mb flash module then I suggest you choose the 1024Kb option as this will preserve the SPIFFS even if you reflash with a larger firmware image; otherwise leave this at the default 0.
SPIFFS base | If you have a 4Mb flash module then I suggest you choose the 1024Kb option as this will preserve the SPIFFS even if you reflash with a larger firmware image; otherwise leave this at the default 0.
SPIFFS size | (default or various multiples of 64Kb) Choose the size that you need. Larger FS require more time to format on first boot.
You must choose an explicit (non-default) LFS size to enable the use of LFS. Most developers find it more useful to work with a fixed SPIFFS size matched to their application requirements.
......@@ -44,7 +44,7 @@ Most Lua developers seem to start with the [ESPlorer](https://github.com/4refr0n
- If you use a fixed SPIFFS image (I find 128Kb is enough for most of my applications) and are developing on a UART-attached ESP module, then you can also recompile any LC files and LFS image, then rebuild a SPIFFS file system image before loading it onto the ESP using `esptool.py`; if you script this you will find that this cycle takes less than a minute. You can either embed the LFS.img in the SPIFFS. You can also use the `luac.cross -a` option to build an absolute address format image that you can directly flash into the LFS region within the firmware.
- If you only need to update the Lua components, then you can work over-the-air (OTA). For example see my
- If you only need to update the Lua components, then you can work over-the-air (OTA). For example see my
[HTTP_OTA.lua](https://github.com/nodemcu/nodemcu-firmware/tree/dev/lua_examples/lfs/HTTP_OTA.lua), which pulls a new LFS image from a webservice and reloads it into the LFS region. This only takes seconds, so I often use this in preference to UART-attached loading.
- Another option would be to include the FTP and Telnet modules in the base LFS image and to use telnet and FTP to update your system. (Given that a 64Kb LFS can store thousands of lines of Lua, doing this isn't much of an issue.)
......@@ -93,7 +93,7 @@ You can then create a file, say `LFS_dummy_strings.lua`, and insert these `local
A useful starting point may be found in [lua_examples/lfs/dummy_strings.lua](https://github.com/nodemcu/nodemcu-firmware/tree/dev/lua_examples/lfs/dummy_strings.lua); this saves about 4Kb of RAM by moving a lot of common compiler and Lua VM strings into ROM.
Another good use of this technique is when you have resources such as CSS, HTML and JS fragments that you want to output over the internet. Instead of having lots of small resource files, you can just use string assignments in an LFS module and this will keep these constants in LFS instead.
## Technical issues
......@@ -114,7 +114,7 @@ Any RO resources that are relocated to a flash address space:
- Must not be collected. Also RW references to RO resources must be robustly handled by the LGC.
- Cannot reference to any volatile RW data elements (though RW resources can refer to RO resources).
All strings in Lua are [interned](https://en.wikipedia.org/wiki/String_interning), so that only one copy of any string is kept in memory, and most string manipulation uses the address of this single copy as a unique reference. This uniqueness and the LGC of strings is facilitated by using a global string table that is hooked into the Lua global state. Within standard Lua VM, any new string is first resolved against RAM string table, so that only the string-misses are added to the string table.
All strings in Lua are [interned](https://en.wikipedia.org/wiki/String_interning), so that only one copy of any string is kept in memory, and most string manipulation uses the address of this single copy as a unique reference. This uniqueness and the LGC of strings is facilitated by using a global string table that is hooked into the Lua global state. Within standard Lua VM, any new string is first resolved against RAM string table, so that only the string-misses are added to the string table.
The LFS patch adds a second RO string table in flash and this contains all strings used in the LFS Protos. Maintaining integrity across the two string tables is simple and low-cost, with LFS resolution process extended across both the RAM and ROM string tables. Hence any strings already in the ROM string table already have a unique string reference avoiding the need to add an additional entry in the RAM table. This both significantly reduces the size of the RAM string table, and removes a lot of strings from the LCG scanning.
......@@ -163,7 +163,7 @@ The deep cross-copy of the compiled `Proto` hierarchy is also complicated becaus
This patch moves the `luac.cross` build into the overall application make hierarchy and so it is now simply a part of the NodeMCU make. The old Lua script has been removed from the `tools` directory, together with the need to have Lua pre-installed on the host.
The LFS image is by default position independent, so is independent of the actual NodeMCU target image. You just have to copy it to the target file system and execute a `flashreload` and this copies the image from SPIFSS to the correct flash location, relocating all address to the correct base. (See `app/lua/lflash.c` for the details.) This process is fast.
The LFS image is by default position independent, so is independent of the actual NodeMCU target image. You just have to copy it to the target file system and execute a `flashreload` and this copies the image from SPIFSS to the correct flash location, relocating all address to the correct base. (See `app/lua/lflash.c` for the details.) This process is fast.
A `luac.cross -a` option also allows absolute address images to be built for direct flashing the LFS store onto the module during provisioning.
......@@ -171,14 +171,14 @@ A `luac.cross -a` option also allows absolute address images to be built for dir
The LGC applies to what the Lua VM classifies as collectable objects (strings, tables, functions, userdata, threads -- known collectively as `GCObjects`). A simple two "colour" LGC was used in previous Lua versions, but Lua 5.1 introduced the Dijkstra's 3-colour (*white*, *grey*, *black*) variant that enabled the LGC to operate in an incremental mode. This permits smaller LGC steps interspersed by LGC pause, and is very useful for larger scale Lua implementations. Whilst this is probably not really needed for IoT devices, NodeMCU follows this standard Lua 5.1 implementation, albeit with the `elua` EGC changes.
In fact, two *white* flavours are used to support incremental working (so this 3-colour algorithm really uses 4). All newly allocated collectable objects are marked as the current *white*, and a link in `GCObject` header enables scanning through all such Lua objects. Collectable objects can be referenced directly or indirectly via one of the Lua application's *roots*: the global environment, the Lua registry and the stack.
In fact, two *white* flavours are used to support incremental working (so this 3-colour algorithm really uses 4). All newly allocated collectable objects are marked as the current *white*, and a link in `GCObject` header enables scanning through all such Lua objects. Collectable objects can be referenced directly or indirectly via one of the Lua application's *roots*: the global environment, the Lua registry and the stack.
The standard LGC algorithm is quite complex and assumes that all GCObjects are RW so that a flag byte within each object can be updated during the mark and sweep processing. LFS introduces GCObjects that are stored in RO memory and are therefore truly RO.
The standard LGC algorithm is quite complex and assumes that all GCObjects are RW so that a flag byte within each object can be updated during the mark and sweep processing. LFS introduces GCObjects that are stored in RO memory and are therefore truly RO.
The LFS patch therefore modifies the LGC processing to avoid such updates to GCObjects in RO memory, whilst still maintaining overall object integrity, as any attempt to update their content during LGC will result in the firmware crashing with a memory exception; the remainder of this section provides further detail on how this was achieved. The LGC operates two broad phases: **mark** and **sweep**
- The **mark** phase walks collectable objects by a recursive walk starting at at the LGC roots. (This is referred to as _traverse_.) Any object that is visited in this walk has its colour flipped from *white* to *grey* to denote that it is in use, and it is relinked into a grey list. The grey list is iteratively processed, removing one grey object at a time. Such objects can reference other objects (e.g. a table has many keys and values which can also be collectable objects), so each one is then also traversed and all objects reachable from it are marked, as above. After an object has been traversed, it's turned from grey to black. The LGC will walks all RW collectable objects, traversing the dependents of each in turn. As RW objects can now refer to RO ones, the traverse routines has additional tests to skip trying to mark any RO LFS references.
- The white flavour is flipped just before entering the **sweep** phase. This phase then loops over all collectable objects. Any objects found with previous white are no longer in use, and so can be freed. The 'current' white are kept; this prevents any new objects created during a paused sweep from being accidentally collected before being marked, but this means that it takes two sweeps to free all unused objects. There are other subtleties introduced in this 3-colour algorithm such as barriers and back-tracking to maintain integrity of the LGC, and these also needed extra rules to handle RO GCObjects correclty, but detailed explanation of these is really outside the scope of this paper.
- The white flavour is flipped just before entering the **sweep** phase. This phase then loops over all collectable objects. Any objects found with previous white are no longer in use, and so can be freed. The 'current' white are kept; this prevents any new objects created during a paused sweep from being accidentally collected before being marked, but this means that it takes two sweeps to free all unused objects. There are other subtleties introduced in this 3-colour algorithm such as barriers and back-tracking to maintain integrity of the LGC, and these also needed extra rules to handle RO GCObjects correclty, but detailed explanation of these is really outside the scope of this paper.
As well as standard collectable GCOobjets:
......@@ -194,7 +194,7 @@ As far as the LGC algorithm is concerned, encountering any _flash_ object in a s
### General comments
- **Reboot implementation**. Whilst the application initiated LFS reload might seem an overhead, it typically only adds a few seconds per reboot.
- **Reboot implementation**. Whilst the application initiated LFS reload might seem an overhead, it typically only adds a few seconds per reboot.
- **LGC reduction**. Since the cost of LGC is directly related to the size of the LGC sweep lists, moving RO resources into LFS memory removes them from the LGC scope and therefore reduces LGC runtime accordingly.
......
......@@ -9,7 +9,7 @@ This FAQ does not aim to help you to learn to program or even how to program in
## What has changed since the first version of this FAQ?
The [NodeMCU company](https://www.nodemcu.com/index_en.html) was set up by [Zeroday](https://github.com/nodemcu) to develop and to market a set of Lua firmware-based development boards which employ the Espressif ESP8266 SoC. The initial development of the firmware was done by Zeroday and a colleague, Vowstar, in-house with the firmware being first open-sourced on Github in late 2014. In mid-2015, Zeroday decided to open the firmware development to a wider group of community developers, so the core group of developers now comprises 6 community developers (including this author), and we are also supported by another dozen or so active contributors, and two NodeMCU originators.
This larger active team has allowed us to address most of the outstanding issues present at the first version of this FAQ. These include:
- For some time the project was locked into an old SDK version, but we now regularly rebaseline to the current SDK version.
......@@ -36,7 +36,7 @@ The NodeMCU firmware implements Lua 5.1 over the Espressif SDK for its ESP8266 S
- The [Lua User's Wiki](http://lua-users.org/wiki/) gives useful example source and relevant discussion. In particular, its [Lua Learning Lua](http://lua-users.org/wiki/LearningLua) section is a good place to start learning Lua.
- The best book to learn Lua is *Programming in Lua- by Roberto Ierusalimschy, one of the creators of Lua. It's first edition is available free [online](http://www.lua.org/pil/contents.html) . The second edition was aimed at Lua 5.1, but is out of print. The third edition is still in print and available in paperback. It contains a lot more material and clearly identifies Lua 5.1 vs Lua 5.2 differences. **This third edition is widely available for purchase and probably the best value for money**. References of the format [PiL **n.m**] refer to section **n.m** in this edition.
- The Espressif ESP8266 architecture is closed source, but the Espressif SDK itself is continually being updated so the best way to get the documentation for this is to [google Espressif IoT SDK Programming Guide](https://www.google.co.uk/search?q=Espressif+IoT+SDK+Programming+Guide) or to look at the Espressif [downloads forum](https://bbs.espressif.com/viewforum.php?f=27).
### How is NodeMCU Lua different to standard Lua?
Whilst the Lua standard distribution includes a stand-alone Lua interpreter, Lua itself is primarily an *extension language* that makes no assumptions about a "main" program: Lua works embedded in a host application to provide a powerful, lightweight scripting language for use within the application. This host application can then invoke functions to execute a piece of Lua code, can write and read Lua variables, and can register C functions to be called by Lua code. Through the use of C functions, Lua can be augmented to cope with a wide range of different domains, thus creating customized programming languages sharing a syntactical framework.
......@@ -72,13 +72,13 @@ The firmware has a wide range of libraries available to support common hardware
There are also further tailoring options available, for example you can choose to have a firmware build which uses 32-bit integer arithmetic instead of floating point. Our integer builds have a smaller Flash footprint and execute faster, but working in integer also has a number of pitfalls, so our general recommendation is to use floating point builds.
Unlike Arduino or ESP8266 development, where each application change requires the flashing of a new copy of the firmware, in the case of Lua the firmware is normally flashed once, and all application development is done by updating files on the SPIFFS file system. In this respect, Lua development on the ESP8266 is far more like developing applications on a more traditional PC. The firmware will only be reflashed if the developer wants to add or update one or more of the hardware-related libraries.
Unlike Arduino or ESP8266 development, where each application change requires the flashing of a new copy of the firmware, in the case of Lua the firmware is normally flashed once, and all application development is done by updating files on the SPIFFS file system. In this respect, Lua development on the ESP8266 is far more like developing applications on a more traditional PC. The firmware will only be reflashed if the developer wants to add or update one or more of the hardware-related libraries.
Those developers who are used to dealing in MB or GB of RAM and file systems can easily run out of memory resources, but with care and using some of the techniques discussed below can go a long way to mitigate this.
The ESP8266 runs the SDK over the native hardware, so there is no underlying operating system to capture errors and to provide graceful failure modes. Hence system or application errors can easily "PANIC" the system causing it to reboot. Error handling has been kept simple to save on the limited code space, and this exacerbates this tendency. Running out of a system resource such as RAM will invariably cause a messy failure and system reboot.
Note that in the 3 years since the firmware was first developed, Espressif has developed and released a new RTOS alternative to the non-OS SDK, and and the latest version of the SDK API reference recommends using RTOS. Unfortunately, the richer RTOS has a significantly larger RAM footprint. Whilst our port to the ESP-32 (with its significantly larger RAM) uses the [ESP-IDF](https://github.com/espressif/esp-idf) which is based on RTOS, the ESP8266 RTOS versions don't have enough free RAM for a RTOS-based NodeMCU firmware build to have sufficient free RAM to write usable applications.
Note that in the 3 years since the firmware was first developed, Espressif has developed and released a new RTOS alternative to the non-OS SDK, and and the latest version of the SDK API reference recommends using RTOS. Unfortunately, the richer RTOS has a significantly larger RAM footprint. Whilst our port to the ESP-32 (with its significantly larger RAM) uses the [ESP-IDF](https://github.com/espressif/esp-idf) which is based on RTOS, the ESP8266 RTOS versions don't have enough free RAM for a RTOS-based NodeMCU firmware build to have sufficient free RAM to write usable applications.
There is currently no `debug` library support. So you have to use 1980s-style "binary-chop" to locate errors and use print statement diagnostics though the system's UART interface. (This omission was largely because of the Flash memory footprint of this library, but there is no reason in principle why we couldn't make this library available in the near future as a custom build option).
......@@ -86,7 +86,7 @@ The LTR implementation means that you can't extend standard libraries as easily
There are standard libraries to provide access to the various hardware options supported by the hardware: WiFi, GPIO, One-wire, I²C, SPI, ADC, PWM, UART, etc.
The runtime system runs in interactive-mode. In this mode it first executes any `init.lua` script. It then "listens" to the serial port for input Lua chunks, and executes them once syntactically complete.
The runtime system runs in interactive-mode. In this mode it first executes any `init.lua` script. It then "listens" to the serial port for input Lua chunks, and executes them once syntactically complete.
There is no batch support, although automated embedded processing is normally achieved by setting up the necessary event triggers in the [`init.lua`](../upload/#initlua) script.
......@@ -97,7 +97,7 @@ Non-Lua processing (e.g. network functions) will usually only take place once th
```lua
node.restart(); for i = 1, 20 do print("not quite yet -- ",i); end
```
You, therefore, **have to** implement ESP8266 Lua applications using an event driven approach. You have to understand which SDK API requests schedule asynchronous processing, and which define event actions through Lua callbacks. Yes, such an event-driven approach makes it difficult to develop procedurally structured applications, but it is well suited to developing the sorts of application that you will typically want to implement on an IoT device.
### So how does the SDK event / tasking system work in Lua?
......@@ -116,11 +116,11 @@ In essence, the NodeMCU firmware is a C application which exploits the ability o
- By default, the Lua runtime also 'listens' to UART 0, the serial port, in interactive mode and will execute any Lua commands input through this serial port. Using the serial port in this way is the most common method of developing and debugging Lua applications on the ESP8266/
- The Lua libraries provide a set of functions for declaring application functions (written in Lua) as callbacks (which are stored in the [Lua registry](#so-how-is-the-lua-registry-used-and-why-is-this-important)) to associate application tasks with specific hardware and timer events. These are also non-preemptive at an applications level. The Lua libraries work in consort with the SDK to queue pending events and invoke any registered Lua callback routines, which then run to completion uninterrupted. For example the Lua [`mytimer:alarm(interval, repeat, callback)`](modules/tmr/#tmralarm) calls a function in the `tmr` library which registers a C function for this alarm using the SDK, and when this C alarm callback function is called it then in turn invokes the Lua callback.
- Excessively long-running Lua functions (or Lua code chunks executed at the interactive prompt through UART 0) can cause other system functions and services to timeout, or to allocate scarce RAM resources to buffer queued data, which can then trigger either the watchdog timer or memory exhaustion, both of which will ultimately cause the system to reboot.
- Just like their C counterparts, Lua tasks initiated by timer, network, GPIO and other callbacks run non pre-emptively to completion before the next task can run, and this includes SDK tasks. Printing to the default serial port is done by the Lua runtime libraries, but SDK services including even a reboot request are run as individual tasks. This is why in the previous example printout out twenty copies of "not quite yet --" before completing and return control the SDK which then allows the reboot to occur.
- Just like their C counterparts, Lua tasks initiated by timer, network, GPIO and other callbacks run non pre-emptively to completion before the next task can run, and this includes SDK tasks. Printing to the default serial port is done by the Lua runtime libraries, but SDK services including even a reboot request are run as individual tasks. This is why in the previous example printout out twenty copies of "not quite yet --" before completing and return control the SDK which then allows the reboot to occur.
This event-driven approach is very different to a conventional procedural applications written in Lua, and different from how you develop C sketches and applications for the Arduino architectures. _There is little point in constructing poll loops in your NodeMCU Lua code since almost always the event that you are polling will not be delivered by the SDK until after your Lua code returns control to the SDK._ The most robust and efficient approach to coding ESP8266 Lua applications is to embrace this event model paradigm, and to decompose your application into atomic tasks that are threaded by events which themselves initiate callback functions. Each event task is established by a callback in an API call in an earlier task.
This event-driven approach is very different to a conventional procedural applications written in Lua, and different from how you develop C sketches and applications for the Arduino architectures. _There is little point in constructing poll loops in your NodeMCU Lua code since almost always the event that you are polling will not be delivered by the SDK until after your Lua code returns control to the SDK._ The most robust and efficient approach to coding ESP8266 Lua applications is to embrace this event model paradigm, and to decompose your application into atomic tasks that are threaded by events which themselves initiate callback functions. Each event task is established by a callback in an API call in an earlier task.
Understanding how the system executes your code can help you structure it better and improve both performance and memory usage.
Understanding how the system executes your code can help you structure it better and improve both performance and memory usage.
- _If you are not using timers and other callback, then you are using the wrong approach._
......@@ -135,7 +135,7 @@ SDK Callbacks include:
| Lua Module | Functions which define or remove callbacks |
|------------|--------------------------------------------|
| tmr | `register([id,] interval, mode, function())` |
| node | `task.post([task_priority], function)`, `output(function(str), serial_debug)` |
| node | `task.post([task_priority], function)`, `output(function(str), serial_debug)` |
| wifi | `startsmart(chan, function())`, `sta.getap(function(table))` |
| net.server | `sk:listen(port,[ip],function(socket))` |
| net | `sk:on(event, function(socket, [, data]))`, `sk:send(string, function(sent))`, `sk:dns(domain, function(socket,ip))` |
......@@ -147,7 +147,7 @@ For a comprehensive list refer to the module documentation on this site.
### So what are the different ways of declaring variables and how is NodeMCU different here?
The following is all standard Lua and is explained in detail in PiL etc., but it is worth summarising here because understanding this is of particular importance in the NodeMCU environment.
The following is all standard Lua and is explained in detail in PiL etc., but it is worth summarising here because understanding this is of particular importance in the NodeMCU environment.
All variables in Lua can be classed as globals, locals or upvalues. But by default any variable that is referenced and not previously declared as `local` is **global** and this variable will persist in the global table until it is explicitly deleted. If you want to see what global variables are in scope then try
......@@ -159,7 +159,7 @@ Local variables are 'lexically scoped', and you may declare any variables as loc
Lua variable can be assigned two broad types of data: **values** such as numbers, booleans, and strings and **references** such as functions, tables and userdata. You can see the difference here when you assign the contents of a variable `a` to `b`. In the case of a value then it is simply copied into `b`. In the case of a reference, both `a` and `b` now refer to the *same object*, and no copying of content takes place. This process of referencing can have some counter-intuitive consequences. For example, in the following code by the time it exists, the variable `tmr2func` is out of scope. However a reference to the function has now been stored in the Lua registry by the alarm API call, so it and any upvalues that it uses will persist until it is eventually entirely dereferenced (e.g. by `tmr2:unregister()`).
```Lua
```Lua
do
local tmr2func = function() ds.convert_T(true); tmr1:start() end
tmr2:alarm(300000, tmr.ALARM_AUTO, tmr2func)
......@@ -168,7 +168,7 @@ end
You need to understand the difference between when a function is compiled, when it is bound as a closure and when it is invoked at runtime. The closure is normally bound once pretty much immediately after compile, but this isn't necessarily the case. Consider the following example from my MCP23008 module below.
```Lua
```Lua
-- Bind the read and write functions for commonly accessed registers
for reg, regAddr in pairs {
IODOR = 0x00,
......@@ -183,7 +183,7 @@ for reg, regAddr in pairs {
return read(MCP23008addr, regAddr)
end
end
```
```
This loop is compiled once when the module is required. The opcode vectors for the read and write functions are created during the compile, along with a header which defines how many upvalues and locals are used by each function. However, these two functions are then bound _four_ times as different functions (e.g. `mcp23008.writeIODOR()`) and each closure inherits its own copies of the upvalues it uses so the `regAddr` for this function is `0x00`). The upvalue list is created when the closure is created and through some Lua magic, even if the outer routine that initially declared them is no longer in scope and has been GCed (Garbage Collected), the Lua RTS ensures that any upvalue will still persist whilst the closure persists.
......@@ -191,30 +191,30 @@ On the other hand the storage for any locals is allocated each time the routine
The Lua runtime uses hashed key access internally to retrieve keyed data from a table. On the other hand locals and upvalues are stored as a contiguous vector and are accessed directly by an index, which is a lot faster. In NodeMCU Lua accesses to Firmware-based tables is particularly slow, which is why you will often see statements like the following at the beginning of modules. *Using locals and upvalues this way is both a lot faster at runtime and generates less bytecode instructions for their access.*
```Lua
```Lua
local i2c = i2c
local i2c_start, i2c_stop, i2c_address, i2c_read, i2c_write, i2c_TRANSMITTER, i2c_RECEIVER =
i2c.start, i2c.stop, i2c.address, i2c.read, i2c.write, i2c.TRANSMITTER, i2c.RECEIVER
```
```
### So how is context passed between Lua event tasks?
It is important to understand that a single Lua function is associated with / bound to any event callback task. This function is executed from within the relevant NodeMCU library C code using a `lua_call()`. Even system initialisation which executes the `dofile("init.lua")` is really a special case of this. Each function can invoke other functions and so on, but it must ultimately return control to the C library code which then returns control the SDK, terminating the task.
It is important to understand that a single Lua function is associated with / bound to any event callback task. This function is executed from within the relevant NodeMCU library C code using a `lua_call()`. Even system initialisation which executes the `dofile("init.lua")` is really a special case of this. Each function can invoke other functions and so on, but it must ultimately return control to the C library code which then returns control the SDK, terminating the task.
By their very nature Lua `local` variables only exist within the context of an executing Lua function, and so locals are unreferenced on exit and any local data (unless also a reference type such as a function, table, or user data which is also referenced elsewhere) can therefore be garbage collected between these `lua_call()` actions.
By their very nature Lua `local` variables only exist within the context of an executing Lua function, and so locals are unreferenced on exit and any local data (unless also a reference type such as a function, table, or user data which is also referenced elsewhere) can therefore be garbage collected between these `lua_call()` actions.
So context can only be passed between event routines by one of the following mechanisms:
- **Globals** are by nature globally accessible. Any global will persist until explicitly dereferenced by assigning `nil` to it. Globals can be readily enumerated, e.g. by a `for k,v in pairs(_G) do`, so their use is transparent.
- The **File system** is a special case of persistent global, so there is no reason in principle why it and the files it contains can't be used to pass context. However the ESP8266 file system uses flash memory and even with the SPIFFS file system still has a limited write cycle lifetime, so it is best to avoid using the file system to store frequently changing content except as a mechanism of last resort.
- The **Lua Registry**. This is a normally hidden table used by the library modules to store callback functions and other Lua data types. The GC treats the registry as in scope and hence any content referenced in the registry will not be garbage collected.
- The **Lua Registry**. This is a normally hidden table used by the library modules to store callback functions and other Lua data types. The GC treats the registry as in scope and hence any content referenced in the registry will not be garbage collected.
- **Upvalues**. These are a standard feature of Lua as described above that is fully implemented in NodeMCU. When a function is declared within an outer function, all of the local variables within the outer scope are available to the inner function. Ierusalimschy's paper, [Closures in Lua](http://www.cs.tufts.edu/~nr/cs257/archive/roberto-ierusalimschy/closures-draft.pdf), gives a lot more detail for those that want to dig deeper.
### So how is the Lua Registry used and why is this important?
All Lua callbacks are called by C wrapper functions within the NodeMCU libraries that are themselves callbacks that have been activated by the SDK as a result of a given event. Such C wrapper functions themselves frequently need to store state for passing between calls or to other wrapper C functions. The Lua registry is a special Lua table which is used for this purpose, except that it is hidden from direct Lua access, but using a standard Lua table for this store enables standard garbage collection algorithms to operate on its content. Any content that needs to be saved is created with a unique key. The upvalues for functions that are global or referenced in the Lua Registry will persist between event routines, and hence any upvalues used by them will also persist and can be used for passing context.
If you are running out of memory, then you might not be correctly clearing down Registry entries. One example is as above where you are setting up timers but not unregistering them. Another occurs in the following code fragment. The `on()` function passes the socket to the connection callback as it's first argument `sck`. This is local variable in the callback function, and it also references the same socket as the upvalue `srv`. So functionally `srv` and `sck` are interchangeable. So why pass it as an argument? Normally garbage collecting a socket will automatically unregister any of its callbacks, but if you use a socket as an upvalue in the callback, the socket is now referenced through the Register, and now it won't be GCed because it is referenced. Catch-22 and a programming error, not a bug.
If you are running out of memory, then you might not be correctly clearing down Registry entries. One example is as above where you are setting up timers but not unregistering them. Another occurs in the following code fragment. The `on()` function passes the socket to the connection callback as it's first argument `sck`. This is local variable in the callback function, and it also references the same socket as the upvalue `srv`. So functionally `srv` and `sck` are interchangeable. So why pass it as an argument? Normally garbage collecting a socket will automatically unregister any of its callbacks, but if you use a socket as an upvalue in the callback, the socket is now referenced through the Register, and now it won't be GCed because it is referenced. Catch-22 and a programming error, not a bug.
Example of wrong upvalue usage in the callback:
......@@ -231,15 +231,15 @@ One way to check the registry is to use the construct `for k,v in pairs(debug.ge
### How do I track globals
- See the Unofficial Lua FAQ: [Detecting Undefined Variables](http://lua-users.org/wiki/DetectingUndefinedVariables).
- My approach is to avoid using them unless I have a _very_ good reason to justify this. I track them statically by running a `luac -p -l XXX.lua | grep GLOBAL` filter on any new modules and replace any accidental globals by local or upvalued local declarations.
- My approach is to avoid using them unless I have a _very_ good reason to justify this. I track them statically by running a `luac -p -l XXX.lua | grep GLOBAL` filter on any new modules and replace any accidental globals by local or upvalued local declarations.
- On NodeMCU, _G's metatable is _G, so you can create any globals that you need and then 'close the barn door' by assigning
`_G.__newindex=function(g,k,v) error ("attempting to set global "..k.." to "..v) end` and any attempt to create new globals with now throw an error and give you a traceback of where this has happened.
### Why is it importance to understand how upvalues are implemented when programming for the ESP8266?
The use of upvalues is a core Lua feature. This is explained in detail in PiL. Any Lua routines defined within an outer scope my use them. This can include routines directly or indirectly referenced in the globals table, **_G**, or in the Lua Registry.
The use of upvalues is a core Lua feature. This is explained in detail in PiL. Any Lua routines defined within an outer scope my use them. This can include routines directly or indirectly referenced in the globals table, **_G**, or in the Lua Registry.
The number of upvalues associated with a given routine is calculated during compile and a stack vector is allocated for them when the closure is bound to hold these references. Each upvalues is classed as open or closed. All upvalues are initially open which means that the upvalue references back to the outer function's register set. However, upvalues must be able to outlive the scope of the outer routine where they are declared as a local variable. The runtime VM does this by adding extra checks when executing a function return to scan any defined closures within its scope for back references and allocate memory to hold the upvalue and points the upvalue's reference to this. This is known as a closed upvalue.
The number of upvalues associated with a given routine is calculated during compile and a stack vector is allocated for them when the closure is bound to hold these references. Each upvalues is classed as open or closed. All upvalues are initially open which means that the upvalue references back to the outer function's register set. However, upvalues must be able to outlive the scope of the outer routine where they are declared as a local variable. The runtime VM does this by adding extra checks when executing a function return to scan any defined closures within its scope for back references and allocate memory to hold the upvalue and points the upvalue's reference to this. This is known as a closed upvalue.
This processing is a mature part of the Lua 5.x runtime system, and for normal Lua applications development this "behind-the-scenes" magic ensures that upvalues just work as any programmer might expect. Sufficient garbage collector metadata is also stored so that these hidden values will be garbage collected correctly *when properly dereferenced*.
......@@ -271,9 +271,9 @@ Building an application on the ESP8266 is a bit like threading pearls onto a nec
### When and why should I avoid using tmr.delay()?
If you are used coding in a procedural paradigm then it is understandable that you consider using [`tmr.delay()`](modules/tmr.md#tmrdelay) to time sequence your application. However as discussed in the previous section, with NodeMCU Lua you are coding in an event-driven paradigm.
If you are used coding in a procedural paradigm then it is understandable that you consider using [`tmr.delay()`](modules/tmr.md#tmrdelay) to time sequence your application. However as discussed in the previous section, with NodeMCU Lua you are coding in an event-driven paradigm.
If you look at the `app/modules/tmr.c` code for this function, then you will see that it executes a low level `ets_delay_us(delay)`. This function isn't part of the NodeMCU code or the SDK; it's actually part of the xtensa-lx106 boot ROM, and is a simple timing loop which polls against the internal CPU clock. `tmr.delay()` is really intended to be used where you need to have more precise timing control on an external hardware I/O (e.g. lifting a GPIO pin high for 20 μSec). It does this with interrupts enabled, because so there is no guarantee that the delay will be as requested, and the Lua RTS itself may inject operations such as GC, so if you do this level of precise control then you should encode your application as a C library.
If you look at the `app/modules/tmr.c` code for this function, then you will see that it executes a low level `ets_delay_us(delay)`. This function isn't part of the NodeMCU code or the SDK; it's actually part of the xtensa-lx106 boot ROM, and is a simple timing loop which polls against the internal CPU clock. `tmr.delay()` is really intended to be used where you need to have more precise timing control on an external hardware I/O (e.g. lifting a GPIO pin high for 20 μSec). It does this with interrupts enabled, because so there is no guarantee that the delay will be as requested, and the Lua RTS itself may inject operations such as GC, so if you do this level of precise control then you should encode your application as a C library.
It will achieve no functional purpose in pretty much every other usecase, as any other system code-based activity will be blocked from execution; at worst it will break your application and create hard-to-diagnose timeout errors. We therefore deprecate its general use.
......@@ -301,7 +301,7 @@ You can also build `luac.cross` on your development host if you have Lua locally
### How do I minimise the footprint of an application?
Perhaps the simplest aspect of reducing the footprint of an application is to get its scope correct. The ESP8266 is an IoT device and not a general purpose system. It is typically used to attach real-world monitors, controls, etc. to an intranet and is therefore designed to implement functions that have limited scope. We commonly come across developers who are trying to treat the ESP8266 as a general purpose device and can't understand why their application can't run.
Perhaps the simplest aspect of reducing the footprint of an application is to get its scope correct. The ESP8266 is an IoT device and not a general purpose system. It is typically used to attach real-world monitors, controls, etc. to an intranet and is therefore designed to implement functions that have limited scope. We commonly come across developers who are trying to treat the ESP8266 as a general purpose device and can't understand why their application can't run.
The simplest and safest way to use IoT devices is to control them through a dedicated general purpose system on the same network. This could be a low cost system such as a [RaspberryPi (RPi)](https://www.raspberrypi.org/) server, running your custom code or an open source home automation (HA) application. Such systems have orders of magnitude more capacity than the ESP8266, for example the RPi has 2GB RAM and its SD card can be up to 32GB in capacity, and it can support the full range of USB-attached disk drives and other devices. It also runs a fully featured Linux OS, and has a rich selection of applications pre configured for it. There are plenty of alternative systems available in this under $50 price range, as well as proprietary HA systems which can cost 10-50 times more.
......@@ -315,7 +315,7 @@ _If you are trying to implement a user-interface or HTTP webserver in your ESP82
- However if you do this then you will also find it extremely difficult to debug or maintain your application.
- A good compromise is to use a tool such as [LuaSrcDiet](http://luaforge.net/projects/luasrcdiet/), which you can use to compact production code for downloading to the ESP8266:
- Keep a master repository of your code on your PC or a cloud-based versioning repository such as [GitHub](https://github.com/)
- Lay it out and comment it for ease of maintenance and debugging
- Lay it out and comment it for ease of maintenance and debugging
- Use a package such as [Esplorer](https://github.com/4refr0nt/ESPlorer) to download modules that you are debugging and to test them.
- Once the code is tested and stable, then compress it using LuaSrcDiet before downloading to the ESP8266. Doing this will reduce the code footprint on the SPIFFS by 2-3x. Also note that LuaSrcDiet has a mode which achieves perhaps 95% of the possible code compaction but which still preserves line numbering. This means that any line number-based error messages will still be usable.
- Standard Lua compiled code includes a lot of debug information which almost doubles its RAM size. [node.stripdebug()](modules/node.md#nodestripdebug) can be used to change this default setting either to increase the debug information for a given module or to remove line number information to save a little more space. Using `node.compile()` to pre-compile any production code will remove all compiled code including error line info and so is not recommended except for stable production code where line numbers are not needed.
......@@ -350,14 +350,14 @@ end
return M
```
This approach ensures that the module can be fully dereferenced on completion. OK, in this case, this also means that the module has to be reloaded on each TCP connection to port 80; however, loading a compiled module from SPIFFS only takes a few mSec, so surely this is an acceptable overhead if it enables you to break down your application into RAM-sized chunks. Note that `require()` will automatically search for `connector.lc` followed by `connector.lua`, so the code will work for both source and compiled variants.
This approach ensures that the module can be fully dereferenced on completion. OK, in this case, this also means that the module has to be reloaded on each TCP connection to port 80; however, loading a compiled module from SPIFFS only takes a few mSec, so surely this is an acceptable overhead if it enables you to break down your application into RAM-sized chunks. Note that `require()` will automatically search for `connector.lc` followed by `connector.lua`, so the code will work for both source and compiled variants.
- Whilst the general practice is for a module to return a table, [PiL 15.1] suggests that it is sometimes appropriate to return a single function instead as this avoids the memory overhead of an additional table. This pattern would look as follows:
```lua
local s = net.createServer(net.TCP)
s:listen(80, function(c) require("connector")(c) end)
```
```lua
local module = _ -- this is a situation where using an upvalue is essential!
return function(csocket)
......@@ -421,7 +421,7 @@ Functions have fixed overheads, so in general the more that you group your appli
### What other resources are available?
Install `lua` and `luac` on your development PC. This is freely available for Windows, Mac and Linux distributions, but we strongly suggest that you use Lua 5.1 to maintain source compatibility with ESP8266 code. This will allow you not only to unit test some modules on your PC in a rich development environment, but you can also use `luac` to generate a bytecode listing of your code and to validate new code syntactically before downloading to the ESP8266. This will also allow you to develop server-side applications and embedded applications in a common language.
Install `lua` and `luac` on your development PC. This is freely available for Windows, Mac and Linux distributions, but we strongly suggest that you use Lua 5.1 to maintain source compatibility with ESP8266 code. This will allow you not only to unit test some modules on your PC in a rich development environment, but you can also use `luac` to generate a bytecode listing of your code and to validate new code syntactically before downloading to the ESP8266. This will also allow you to develop server-side applications and embedded applications in a common language.
## Firmware and Lua app development
......
......@@ -8,7 +8,7 @@ This Lua module implementation provides a basic FTP server for the ESP8266. It h
It provides a limited subset of FTP commands that enable such clients to transfer files to and from the ESP's file system. Only one server can be started at any one time, but this server can support multiple connected sessions (some FTP clients use multiple sessions and so require this feature).
!!! warning
This module is too big to load by standard `require` function or compile on ESP8266 using `node.compile()`. The only option to load and use it is to use [LFS](../lfs.md).
This module is too big to load by standard `require` function or compile on ESP8266 using `node.compile()`. The only option to load and use it is to use [LFS](../lfs.md).
### Limitations
- FTP over SSH or TLS is not currently supported so transfer is unencrypted.
......@@ -18,7 +18,7 @@ It provides a limited subset of FTP commands that enable such clients to transfe
- Only PASV mode is supported as the `net` module does not allow static allocation of outbound sockets.
### Notes
The coding style adopted here is more similar to best practice for normal (PC) module implementations, as using LFS permits a bias towards clarity of coding over brevity. It includes extra logic to handle some of the edge case issues more robustly. It also uses a standard forward reference coding pattern to allow the code to be laid out in main routine, subroutine order.
The coding style adopted here is more similar to best practice for normal (PC) module implementations, as using LFS permits a bias towards clarity of coding over brevity. It includes extra logic to handle some of the edge case issues more robustly. It also uses a standard forward reference coding pattern to allow the code to be laid out in main routine, subroutine order.
Most FTP clients are capable of higher transfer rates than the ESP SPIFFS write throughput, so the server uses TCP flow control to limit upload rates to the ESP.
......@@ -27,7 +27,7 @@ The following FTP commands are supported:
- with no parameter: CDUP, NOOP, PASV, PWD, QUIT, SYST
- with one parameter: CWD, DELE, MODE, PASS, PORT, RNFR, RNTO, SIZE, TYPE, USER
- xfer commands: LIST, NLST, RETR, STOR
This implementation is by [Terry Ellison](https://github.com/TerryE), but I wish to acknowledge the inspiration and hard work by [Neronix](https://github.com/NeiroNx) that made this possible.
## createServer()
......@@ -49,7 +49,7 @@ Create the FTP server on the standard ports 20 and 21. The global variable `FTP
require("ftpserver").createServer('user', 'password')
```
## open()
## open()
Wrapper to createServer() which also connects to the WiFi channel.
#### Syntax
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment