Commit 4911d2db authored by Arnim Läuger's avatar Arnim Läuger
Browse files

Merge pull request #1336 from nodemcu/dev

1.5.1 master drop
parents c8037568 2e109686
...@@ -15,6 +15,8 @@ ifndef PDIR ...@@ -15,6 +15,8 @@ ifndef PDIR
GEN_LIBS = ucglib.a GEN_LIBS = ucglib.a
endif endif
STD_CFLAGS=-std=gnu11 -Wimplicit
############################################################# #############################################################
# Configuration i.e. compile options etc. # Configuration i.e. compile options etc.
# Target specific stuff (defines etc.) goes in here! # Target specific stuff (defines etc.) goes in here!
......
#include "ets_sys.h"
#include "osapi.h"
#include "os_type.h"
#include "lwip/err.h"
#include "lwip/ip_addr.h"
#include "lwip/mem.h"
#include "lwip/app/espconn.h"
#include "upgrade.h"
#include "upgrade_lib.c"
#define UPGRADE_DEBUG
#ifdef UPGRADE_DEBUG
#define UPGRADE_DBG os_printf
#else
#define UPGRADE_DBG
#endif
LOCAL struct espconn *upgrade_conn;
LOCAL uint8 *pbuf;
LOCAL os_timer_t upgrade_10s;
LOCAL os_timer_t upgrade_timer;
LOCAL uint32 totallength = 0;
LOCAL uint32 sumlength = 0;
/******************************************************************************
* FunctionName : upgrade_disconcb
* Description : The connection has been disconnected successfully.
* Parameters : arg -- Additional argument to pass to the callback function
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
upgrade_disconcb(void *arg)
{
struct espconn *pespconn = arg;
if (pespconn == NULL) {
return;
}
os_free(pespconn->proto.tcp);
pespconn->proto.tcp = NULL;
os_free(pespconn);
pespconn = NULL;
upgrade_conn = NULL;
}
/******************************************************************************
* FunctionName : upgrade_datasent
* Description : Data has been sent successfully,This means that more data can
* be sent.
* Parameters : arg -- Additional argument to pass to the callback function
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
upgrade_datasent(void *arg)
{
struct espconn *pespconn = arg;
if (pespconn ->state == ESPCONN_CONNECT) {
}
}
/******************************************************************************
* FunctionName : upgrade_deinit
* Description : disconnect the connection with the host
* Parameters : bin -- server number
* Returns : none
*******************************************************************************/
void ICACHE_FLASH_ATTR
LOCAL upgrade_deinit(void)
{
if (system_upgrade_flag_check() != UPGRADE_FLAG_START) {
system_upgrade_deinit();
}
}
/******************************************************************************
* FunctionName : upgrade_10s_cb
* Description : Processing the client when connected with host time out
* Parameters : pespconn -- A point to the host
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR upgrade_10s_cb(struct espconn *pespconn)
{
if (pespconn == NULL) {
return;
}
system_upgrade_deinit();
os_free(pespconn->proto.tcp);
pespconn->proto.tcp = NULL;
os_free(pespconn);
pespconn = NULL;
upgrade_conn = NULL;
}
/******************************************************************************
* FunctionName : user_upgrade_check
* Description : Processing the received data from the server
* Parameters : arg -- Additional argument to pass to the callback function
* pusrdata -- The received data (or NULL when the connection has been closed!)
* length -- The length of received data
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
upgrade_check(struct upgrade_server_info *server)
{
UPGRADE_DBG("upgrade_check\n");
if (system_upgrade_flag_check() != UPGRADE_FLAG_FINISH) {
totallength = 0;
sumlength = 0;
os_timer_disarm(&upgrade_timer);
system_upgrade_flag_set(UPGRADE_FLAG_IDLE);
upgrade_deinit();
server->upgrade_flag = false;
if (server->check_cb != NULL) {
server->check_cb(server);
}
} else {
os_timer_disarm(&upgrade_timer);
upgrade_deinit();
server->upgrade_flag = true;
if (server->check_cb != NULL) {
server->check_cb(server);
}
}
#ifdef UPGRADE_SSL_ENABLE
espconn_secure_disconnect(upgrade_conn);
#else
espconn_disconnect(upgrade_conn);
#endif
}
/******************************************************************************
* FunctionName : upgrade_download
* Description : Processing the upgrade data from the host
* Parameters : bin -- server number
* pusrdata -- The upgrade data (or NULL when the connection has been closed!)
* length -- The length of upgrade data
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
upgrade_download(void *arg, char *pusrdata, unsigned short length)
{
char *ptr = NULL;
char *ptmp2 = NULL;
char lengthbuffer[32];
if (totallength == 0 && (ptr = (char *)os_strstr(pusrdata, "\r\n\r\n")) != NULL &&
(ptr = (char *)os_strstr(pusrdata, "Content-Length")) != NULL) {
ptr = (char *)os_strstr(pusrdata, "\r\n\r\n");
length -= ptr - pusrdata;
length -= 4;
totallength += length;
UPGRADE_DBG("upgrade file download start.\n");
system_upgrade(ptr + 4, length);
ptr = (char *)os_strstr(pusrdata, "Content-Length: ");
if (ptr != NULL) {
ptr += 16;
ptmp2 = (char *)os_strstr(ptr, "\r\n");
if (ptmp2 != NULL) {
os_memset(lengthbuffer, 0, sizeof(lengthbuffer));
os_memcpy(lengthbuffer, ptr, ptmp2 - ptr);
sumlength = atoi(lengthbuffer);
} else {
UPGRADE_DBG("sumlength failed\n");
}
} else {
UPGRADE_DBG("Content-Length: failed\n");
}
} else {
totallength += length;
os_printf("totallen = %d\n",totallength);
system_upgrade(pusrdata, length);
}
if (totallength == sumlength) {
UPGRADE_DBG("upgrade file download finished.\n");
system_upgrade_flag_set(UPGRADE_FLAG_FINISH);
totallength = 0;
sumlength = 0;
upgrade_check(upgrade_conn->reverse);
os_timer_disarm(&upgrade_10s);
os_timer_setfn(&upgrade_10s, (os_timer_func_t *)upgrade_deinit, NULL);
os_timer_arm(&upgrade_10s, 10, 0);
} else {
if (upgrade_conn->state != ESPCONN_READ) {
totallength = 0;
sumlength = 0;
os_timer_disarm(&upgrade_10s);
os_timer_setfn(&upgrade_10s, (os_timer_func_t *)upgrade_check, upgrade_conn->reverse);
os_timer_arm(&upgrade_10s, 10, 0);
}
}
}
/******************************************************************************
* FunctionName : upgrade_connect
* Description : client connected with a host successfully
* Parameters : arg -- Additional argument to pass to the callback function
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
upgrade_connect_cb(void *arg)
{
struct espconn *pespconn = arg;
UPGRADE_DBG("upgrade_connect_cb\n");
os_timer_disarm(&upgrade_10s);
espconn_regist_disconcb(pespconn, upgrade_disconcb);
espconn_regist_sentcb(pespconn, upgrade_datasent);
if (pbuf != NULL) {
UPGRADE_DBG("%s\n", pbuf);
#ifdef UPGRADE_SSL_ENABLE
espconn_secure_sent(pespconn, pbuf, os_strlen(pbuf));
#else
espconn_sent(pespconn, pbuf, os_strlen(pbuf));
#endif
}
}
/******************************************************************************
* FunctionName : upgrade_connection
* Description : connect with a server
* Parameters : bin -- server number
* url -- the url whitch upgrade files saved
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
upgrade_connect(struct upgrade_server_info *server)
{
UPGRADE_DBG("upgrade_connect\n");
pbuf = server->url;
espconn_regist_connectcb(upgrade_conn, upgrade_connect_cb);
espconn_regist_recvcb(upgrade_conn, upgrade_download);
system_upgrade_init();
system_upgrade_flag_set(UPGRADE_FLAG_START);
#ifdef UPGRADE_SSL_ENABLE
espconn_secure_connect(upgrade_conn);
#else
espconn_connect(upgrade_conn);
#endif
os_timer_disarm(&upgrade_10s);
os_timer_setfn(&upgrade_10s, (os_timer_func_t *)upgrade_10s_cb, upgrade_conn);
os_timer_arm(&upgrade_10s, 10000, 0);
}
/******************************************************************************
* FunctionName : user_upgrade_init
* Description : parameter initialize as a client
* Parameters : server -- A point to a server parmer which connected
* Returns : none
*******************************************************************************/
bool ICACHE_FLASH_ATTR
#ifdef UPGRADE_SSL_ENABLE
system_upgrade_start_ssl(struct upgrade_server_info *server)
#else
system_upgrade_start(struct upgrade_server_info *server)
#endif
{
if (system_upgrade_flag_check() == UPGRADE_FLAG_START) {
return false;
}
if (server == NULL) {
UPGRADE_DBG("server is NULL\n");
return false;
}
if (upgrade_conn == NULL) {
upgrade_conn = (struct espconn *)os_zalloc(sizeof(struct espconn));
}
if (upgrade_conn != NULL) {
upgrade_conn->proto.tcp = NULL;
upgrade_conn->type = ESPCONN_TCP;
upgrade_conn->state = ESPCONN_NONE;
upgrade_conn->reverse = server;
if (upgrade_conn->proto.tcp == NULL) {
upgrade_conn->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp));
}
if (upgrade_conn->proto.tcp != NULL) {
upgrade_conn->proto.tcp->local_port = espconn_port();
upgrade_conn->proto.tcp->remote_port = server->port;
os_memcpy(upgrade_conn->proto.tcp->remote_ip, server->ip, 4);
UPGRADE_DBG("%s\n", __func__);
upgrade_connect(server);
if (server->check_cb != NULL) {
os_timer_disarm(&upgrade_timer);
os_timer_setfn(&upgrade_timer, (os_timer_func_t *)upgrade_check, server);
os_timer_arm(&upgrade_timer, server->check_times, 0);
}
}
}
return true;
}
#include "ets_sys.h"
#include "spi_flash.h"
//#include "net80211/ieee80211_var.h"
#include "lwip/mem.h"
#include "upgrade.h"
struct upgrade_param {
uint32 fw_bin_addr;
uint8 fw_bin_sec;
uint8 fw_bin_sec_num;
uint8 fw_bin_sec_earse;
uint8 extra;
uint8 save[4];
uint8 *buffer;
};
LOCAL struct upgrade_param *upgrade;
extern SpiFlashChip *flashchip;
/******************************************************************************
* FunctionName : system_upgrade_internal
* Description : a
* Parameters :
* Returns :
*******************************************************************************/
LOCAL bool ICACHE_FLASH_ATTR
system_upgrade_internal(struct upgrade_param *upgrade, uint8 *data, uint16 len)
{
bool ret = false;
if(data == NULL || len == 0)
{
return true;
}
upgrade->buffer = (uint8 *)os_zalloc(len + upgrade->extra);
os_memcpy(upgrade->buffer, upgrade->save, upgrade->extra);
os_memcpy(upgrade->buffer + upgrade->extra, data, len);
len += upgrade->extra;
upgrade->extra = len & 0x03;
len -= upgrade->extra;
os_memcpy(upgrade->save, upgrade->buffer + len, upgrade->extra);
do {
if (upgrade->fw_bin_addr + len >= (upgrade->fw_bin_sec + upgrade->fw_bin_sec_num) * SPI_FLASH_SEC_SIZE) {
break;
}
if (len > SPI_FLASH_SEC_SIZE) {
} else {
// os_printf("%x %x\n",upgrade->fw_bin_sec_earse,upgrade->fw_bin_addr);
/* earse sector, just earse when first enter this zone */
if (upgrade->fw_bin_sec_earse != (upgrade->fw_bin_addr + len) >> 12) {
upgrade->fw_bin_sec_earse = (upgrade->fw_bin_addr + len) >> 12;
spi_flash_erase_sector(upgrade->fw_bin_sec_earse);
// os_printf("%x\n",upgrade->fw_bin_sec_earse);
}
}
if (spi_flash_write(upgrade->fw_bin_addr, (uint32 *)upgrade->buffer, len) != SPI_FLASH_RESULT_OK) {
break;
}
ret = true;
upgrade->fw_bin_addr += len;
} while (0);
os_free(upgrade->buffer);
upgrade->buffer = NULL;
return ret;
}
/******************************************************************************
* FunctionName : system_upgrade
* Description : a
* Parameters :
* Returns :
*******************************************************************************/
bool ICACHE_FLASH_ATTR
system_upgrade(uint8 *data, uint16 len)
{
bool ret;
ret = system_upgrade_internal(upgrade, data, len);
return ret;
}
/******************************************************************************
* FunctionName : system_upgrade_init
* Description : a
* Parameters :
* Returns :
*******************************************************************************/
void ICACHE_FLASH_ATTR
system_upgrade_init(void)
{
uint32 user_bin2_start;
uint8 flash_buf[4];
uint8 high_half;
spi_flash_read(0, (uint32 *)flash_buf, 4);
high_half = (flash_buf[3] & 0xF0) >> 4;
if (upgrade == NULL) {
upgrade = (struct upgrade_param *)os_zalloc(sizeof(struct upgrade_param));
}
system_upgrade_flag_set(UPGRADE_FLAG_IDLE);
if (high_half == 2 || high_half == 3 || high_half == 4) {
user_bin2_start = 129; // 128 + 1
upgrade->fw_bin_sec_num = 123; // 128 - 1 - 4
} else {
user_bin2_start = 65; // 64 + 1
upgrade->fw_bin_sec_num = 59; // 64 - 1 - 4
}
upgrade->fw_bin_sec = (system_upgrade_userbin_check() == USER_BIN1) ? user_bin2_start : 1;
upgrade->fw_bin_addr = upgrade->fw_bin_sec * SPI_FLASH_SEC_SIZE;
}
/******************************************************************************
* FunctionName : system_upgrade_deinit
* Description : a
* Parameters :
* Returns :
*******************************************************************************/
void ICACHE_FLASH_ATTR
system_upgrade_deinit(void)
{
os_free(upgrade);
upgrade = NULL;
}
...@@ -15,6 +15,7 @@ ifndef PDIR ...@@ -15,6 +15,7 @@ ifndef PDIR
GEN_LIBS = libuser.a GEN_LIBS = libuser.a
endif endif
STD_CFLAGS=-std=gnu11 -Wimplicit
############################################################# #############################################################
# Configuration i.e. compile options etc. # Configuration i.e. compile options etc.
...@@ -43,7 +44,6 @@ INCLUDES += -I ../../include/ets ...@@ -43,7 +44,6 @@ INCLUDES += -I ../../include/ets
INCLUDES += -I ../libc INCLUDES += -I ../libc
INCLUDES += -I ../platform INCLUDES += -I ../platform
INCLUDES += -I ../lua INCLUDES += -I ../lua
INCLUDES += -I ../wofs
INCLUDES += -I ../spiffs INCLUDES += -I ../spiffs
PDIR := ../$(PDIR) PDIR := ../$(PDIR)
sinclude $(PDIR)Makefile sinclude $(PDIR)Makefile
......
...@@ -38,6 +38,8 @@ ...@@ -38,6 +38,8 @@
#define L16UI_MATCH 0x001002u #define L16UI_MATCH 0x001002u
#define L16SI_MATCH 0x009002u #define L16SI_MATCH 0x009002u
static exception_handler_fn load_store_handler;
void load_non_32_wide_handler (struct exception_frame *ef, uint32_t cause) void load_non_32_wide_handler (struct exception_frame *ef, uint32_t cause)
{ {
...@@ -70,9 +72,13 @@ void load_non_32_wide_handler (struct exception_frame *ef, uint32_t cause) ...@@ -70,9 +72,13 @@ void load_non_32_wide_handler (struct exception_frame *ef, uint32_t cause)
else else
{ {
die: die:
/* Turns out we couldn't fix this, trigger a system break instead /* Turns out we couldn't fix this, so try and chain to the handler
* that was set by the SDK. If none then trigger a system break instead
* and hang if the break doesn't get handled. This is effectively * and hang if the break doesn't get handled. This is effectively
* what would happen if the default handler was installed. */ * what would happen if the default handler was installed. */
if (load_store_handler) {
load_store_handler(ef, cause);
}
asm ("break 1, 1"); asm ("break 1, 1");
while (1) {} while (1) {}
} }
...@@ -102,11 +108,14 @@ die: ...@@ -102,11 +108,14 @@ die:
* of whether there's a proper handler installed for EXCCAUSE_LOAD_STORE_ERROR, * of whether there's a proper handler installed for EXCCAUSE_LOAD_STORE_ERROR,
* which of course breaks everything if we allow that to go through. As such, * which of course breaks everything if we allow that to go through. As such,
* we use the linker to wrap that call and stop the SDK from shooting itself in * we use the linker to wrap that call and stop the SDK from shooting itself in
* its proverbial foot. * its proverbial foot. We do save the EXCCAUSE_LOAD_STORE_ERROR handler so that
* we can chain to it above.
*/ */
exception_handler_fn TEXT_SECTION_ATTR exception_handler_fn TEXT_SECTION_ATTR
__wrap__xtos_set_exception_handler (uint32_t cause, exception_handler_fn fn) __wrap__xtos_set_exception_handler (uint32_t cause, exception_handler_fn fn)
{ {
if (cause != EXCCAUSE_LOAD_STORE_ERROR) if (cause != EXCCAUSE_LOAD_STORE_ERROR)
__real__xtos_set_exception_handler (cause, fn); __real__xtos_set_exception_handler (cause, fn);
else
load_store_handler = fn;
} }
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
#include <xtensa/corebits.h> #include <xtensa/corebits.h>
void load_non_32_wide_handler (struct exception_frame *ef, uint32_t cause) TEXT_SECTION_ATTR; void load_non_32_wide_handler (struct exception_frame *ef, uint32_t cause) TEXT_SECTION_ATTR;
void __real__xtos_set_exception_handler (uint32_t cause, exception_handler_fn fn);
...@@ -12,15 +12,15 @@ ...@@ -12,15 +12,15 @@
#include "platform.h" #include "platform.h"
#include "c_string.h" #include "c_string.h"
#include "c_stdlib.h" #include "c_stdlib.h"
#include "c_stdio.h"
#include "flash_fs.h" #include "flash_fs.h"
#include "flash_api.h"
#include "user_interface.h" #include "user_interface.h"
#include "user_exceptions.h" #include "user_exceptions.h"
#include "user_modules.h" #include "user_modules.h"
#include "ets_sys.h" #include "ets_sys.h"
#include "driver/uart.h" #include "driver/uart.h"
#include "task/task.h"
#include "mem.h" #include "mem.h"
#ifdef LUA_USE_MODULES_RTCTIME #ifdef LUA_USE_MODULES_RTCTIME
...@@ -33,13 +33,6 @@ ...@@ -33,13 +33,6 @@
static os_event_t *taskQueue; static os_event_t *taskQueue;
/* Important: no_init_data CAN NOT be left as zero initialised, as that
* initialisation will happen after user_start_trampoline, but before
* the user_init, thus clobbering our state!
*/
static uint8_t no_init_data = 0xff;
/* Note: the trampoline *must* be explicitly put into the .text segment, since /* Note: the trampoline *must* be explicitly put into the .text segment, since
* by the time it is invoked the irom has not yet been mapped. This naturally * by the time it is invoked the irom has not yet been mapped. This naturally
* also goes for anything the trampoline itself calls. * also goes for anything the trampoline itself calls.
...@@ -55,76 +48,30 @@ void TEXT_SECTION_ATTR user_start_trampoline (void) ...@@ -55,76 +48,30 @@ void TEXT_SECTION_ATTR user_start_trampoline (void)
rtctime_early_startup (); rtctime_early_startup ();
#endif #endif
/* Minimal early detection of missing esp_init_data.
* If it is missing, the SDK will write its own and thus we'd end up
* using that unless the flash size field is incorrect. This then leads
* to different esp_init_data being used depending on whether the user
* flashed with the right flash size or not (and the better option would
* be to flash with an *incorrect* flash size, counter-intuitively).
* To avoid that mess, we read out the flash size and do a test for
* esp_init_data based on that size. If it's missing, flag for later.
* If the flash size was incorrect, we'll end up fixing it all up
* anyway, so this ends up solving the conundrum. Only remaining issue
* is lack of spare code bytes in iram, so this is deliberately quite
* terse and not as readable as one might like.
*/
SPIFlashInfo sfi;
SPIRead (0, &sfi, sizeof (sfi)); // Cache read not enabled yet, safe to use
if (sfi.size < 2) // Compensate for out-of-order 4mbit vs 2mbit values
sfi.size ^= 1;
uint32_t flash_end_addr = (256 * 1024) << sfi.size;
uint32_t init_data_hdr = 0xffffffff;
SPIRead (flash_end_addr - 4 * SPI_FLASH_SEC_SIZE, &init_data_hdr, sizeof (init_data_hdr));
no_init_data = (init_data_hdr == 0xffffffff);
call_user_start (); call_user_start ();
} }
// +================== New task interface ==================+
/* To avoid accidentally losing the fix for the TCP port randomization static void start_lua(task_param_t param, uint8 priority) {
* during an LWIP upgrade, we've implemented most it outside the LWIP
* source itself. This enables us to test for the presence of the fix
* /at link time/ and error out if it's been lost.
* The fix itself consists of putting the function-static 'port' variable
* into its own section, and get the linker to provide an alias for it.
* From there we can then manually randomize it at boot.
*/
static inline void tcp_random_port_init (void)
{
extern uint16_t _tcp_new_port_port; // provided by the linker script
_tcp_new_port_port += xthal_get_ccount () % 4096;
}
void task_lua(os_event_t *e){
char* lua_argv[] = { (char *)"lua", (char *)"-i", NULL }; char* lua_argv[] = { (char *)"lua", (char *)"-i", NULL };
NODE_DBG("Task task_lua started.\n"); NODE_DBG("Task task_lua started.\n");
switch(e->sig){
case SIG_LUA:
NODE_DBG("SIG_LUA received.\n");
lua_main( 2, lua_argv ); lua_main( 2, lua_argv );
break;
case SIG_UARTINPUT:
lua_handle_input (false);
break;
case LUA_PROCESS_LINE_SIG:
lua_handle_input (true);
break;
default:
break;
}
} }
void task_init(void){ static void handle_input(task_param_t flag, uint8 priority) {
taskQueue = (os_event_t *)os_malloc(sizeof(os_event_t) * TASK_QUEUE_LEN); // c_printf("HANDLE_INPUT: %u %u\n", flag, priority); REMOVE
system_os_task(task_lua, USER_TASK_PRIO_0, taskQueue, TASK_QUEUE_LEN); lua_handle_input (flag);
} }
// extern void test_spiffs(); static task_handle_t input_sig;
// extern int test_romfs();
// extern uint16_t flash_get_sec_num(); task_handle_t user_get_input_sig(void) {
return input_sig;
}
bool user_process_input(bool force) {
return task_post_low(input_sig, force);
}
void nodemcu_init(void) void nodemcu_init(void)
{ {
...@@ -142,8 +89,6 @@ void nodemcu_init(void) ...@@ -142,8 +89,6 @@ void nodemcu_init(void)
NODE_ERR("Self adjust flash size.\n"); NODE_ERR("Self adjust flash size.\n");
// Fit hardware real flash size. // Fit hardware real flash size.
flash_rom_set_size_byte(flash_safe_get_size_byte()); flash_rom_set_size_byte(flash_safe_get_size_byte());
// Write out init data at real location.
no_init_data = true;
if( !fs_format() ) if( !fs_format() )
{ {
...@@ -154,51 +99,33 @@ void nodemcu_init(void) ...@@ -154,51 +99,33 @@ void nodemcu_init(void)
NODE_ERR( "format done.\n" ); NODE_ERR( "format done.\n" );
} }
fs_unmount(); // mounted by format. fs_unmount(); // mounted by format.
}
#endif // defined(FLASH_SAFE_API)
if (no_init_data) // Reboot to get SDK to use (or write) init data at new location
{
NODE_ERR("Restore init data.\n");
// Flash init data at FLASHSIZE - 0x04000 Byte.
flash_init_data_default();
// Flash blank data at FLASHSIZE - 0x02000 Byte.
flash_init_data_blank();
// Reboot to make the new data come into effect
system_restart (); system_restart ();
}
#if defined( BUILD_WOFS )
romfs_init();
// if( !wofs_format() ) // Don't post the start_lua task, we're about to reboot...
// { return;
// NODE_ERR( "\ni*** ERROR ***: unable to erase the flash. WOFS might be compromised.\n" ); }
// NODE_ERR( "It is advised to re-flash the NodeWifi image.\n" ); #endif // defined(FLASH_SAFE_API)
// }
// else
// NODE_ERR( "format done.\n" );
// test_romfs(); #if defined ( BUILD_SPIFFS )
#elif defined ( BUILD_SPIFFS )
fs_mount(); fs_mount();
// test_spiffs(); // test_spiffs();
#endif #endif
// endpoint_setup(); // endpoint_setup();
// char* lua_argv[] = { (char *)"lua", (char *)"-e", (char *)"print(collectgarbage'count');ttt={};for i=1,100 do table.insert(ttt,i*2 -1);print(i);end for k, v in pairs(ttt) do print('<'..k..' '..v..'>') end print(collectgarbage'count');", NULL }; task_post_low(task_get_id(start_lua),'s');
// lua_main( 3, lua_argv ); }
// char* lua_argv[] = { (char *)"lua", (char *)"-i", NULL };
// lua_main( 2, lua_argv );
// char* lua_argv[] = { (char *)"lua", (char *)"-e", (char *)"pwm.setup(0,100,50) pwm.start(0) pwm.stop(0)", NULL };
// lua_main( 3, lua_argv );
// NODE_DBG("Flash sec num: 0x%x\n", flash_get_sec_num());
tcp_random_port_init (); #ifdef LUA_USE_MODULES_WIFI
#include "../modules/wifi_common.h"
task_init(); void user_rf_pre_init(void)
system_os_post(LUA_TASK_PRIO,SIG_LUA,'s'); {
//set WiFi hostname before RF initialization (adds ~440 us to boot time)
wifi_change_default_host_name();
} }
#endif
/****************************************************************************** /******************************************************************************
* FunctionName : user_init * FunctionName : user_init
...@@ -211,18 +138,15 @@ void user_init(void) ...@@ -211,18 +138,15 @@ void user_init(void)
#ifdef LUA_USE_MODULES_RTCTIME #ifdef LUA_USE_MODULES_RTCTIME
rtctime_late_startup (); rtctime_late_startup ();
#endif #endif
// NODE_DBG("SDK version:%s\n", system_get_sdk_version());
// system_print_meminfo();
// os_printf("Heap size::%d.\n",system_get_free_heap_size());
// os_delay_us(50*1000); // delay 50ms before init uart
UartBautRate br = BIT_RATE_DEFAULT; UartBautRate br = BIT_RATE_DEFAULT;
uart_init (br, br, USER_TASK_PRIO_0, SIG_UARTINPUT); input_sig = task_get_id(handle_input);
uart_init (br, br, input_sig);
#ifndef NODE_DEBUG #ifndef NODE_DEBUG
system_set_os_print(0); system_set_os_print(0);
#endif #endif
system_init_done_cb(nodemcu_init); system_init_done_cb(nodemcu_init);
} }
// Generated by mkfs.lua
// DO NOT MODIFY
#ifndef __ROMFILES_H__
#define __ROMFILES_H__
const unsigned char romfiles_fs[] =
{
0xFF
};
#endif
// Filesystem implementation
#include "romfs.h"
#include "c_string.h"
// #include "c_errno.h"
#include "romfiles.h"
#include "c_stdio.h"
// #include "c_stdlib.h"
#include "c_fcntl.h"
#include "platform.h"
#if defined( BUILD_ROMFS ) || defined( BUILD_WOFS )
#define TOTAL_MAX_FDS 8
// DO NOT CHANGE THE ROMFS ALIGNMENT.
// UNLESS YOU _LIKE_ TO WATCH THE WORLD BURN.
#define ROMFS_ALIGN 4
#define fsmin( x , y ) ( ( x ) < ( y ) ? ( x ) : ( y ) )
static FD fd_table[ TOTAL_MAX_FDS ];
static int romfs_num_fd;
#define WOFS_END_MARKER_CHAR 0xFF
#define WOFS_DEL_FIELD_SIZE ( ROMFS_ALIGN )
#define WOFS_FILE_DELETED 0xAA
// Length of the 'file size' field for both ROMFS/WOFS
#define ROMFS_SIZE_LEN 4
static int romfs_find_empty_fd(void)
{
int i;
for( i = 0; i < TOTAL_MAX_FDS; i ++ )
if( fd_table[ i ].baseaddr == 0xFFFFFFFF &&
fd_table[ i ].offset == 0xFFFFFFFF &&
fd_table[ i ].size == 0xFFFFFFFF )
return i;
return -1;
}
static void romfs_close_fd( int fd )
{
if(fd<0 || fd>=TOTAL_MAX_FDS)
return;
c_memset( fd_table + fd, 0xFF, sizeof( FD ) );
fd_table[ fd ].flags = 0;
}
// Helper function: read a byte from the FS
static uint8_t romfsh_read8( uint32_t addr, const FSDATA *pfs )
{
uint8_t temp;
if( pfs->flags & ROMFS_FS_FLAG_DIRECT )
return pfs->pbase[ addr ];
pfs->readf( &temp, addr, 1, pfs );
return temp;
}
// Helper function: return 1 if PFS reffers to a WOFS, 0 otherwise
static int romfsh_is_wofs( const FSDATA* pfs )
{
return ( pfs->flags & ROMFS_FS_FLAG_WO ) != 0;
}
// Find the next file, returning FS_FILE_OK or FS_FILE_NOT_FOUND if there no file left.
static uint8_t romfs_next_file( uint32_t *start, char* fname, size_t len, size_t *act_len, FSDATA *pfs )
{
uint32_t i, j, n;
uint32_t fsize;
int is_deleted;
// Look for the file
i = *start;
*act_len = 0;
if( (i >= INTERNAL_FLASH_SIZE) || (romfsh_read8( i, pfs ) == WOFS_END_MARKER_CHAR )) // end of file system
{
*start = (i >= INTERNAL_FLASH_SIZE)?(INTERNAL_FLASH_SIZE-1):i;
return FS_FILE_NOT_FOUND;
}
// Read file name
len = len>MAX_FNAME_LENGTH?MAX_FNAME_LENGTH:len;
for( j = 0; j < len; j ++ )
{
fname[ j ] = romfsh_read8( i + j, pfs );
if( fname[ j ] == 0 )
break;
}
n = j; // save the file name length to n
// ' i + j' now points at the '0' byte
j = i + j + 1;
// Round to a multiple of ROMFS_ALIGN
j = ( j + ROMFS_ALIGN - 1 ) & ~( ROMFS_ALIGN - 1 );
// WOFS has an additional WOFS_DEL_FIELD_SIZE bytes before the size as an indication for "file deleted"
if( romfsh_is_wofs( pfs ) )
{
is_deleted = romfsh_read8( j, pfs ) == WOFS_FILE_DELETED;
j += WOFS_DEL_FIELD_SIZE;
}
else
is_deleted = 0;
// And read the size
fsize = romfsh_read8( j, pfs ) + ( romfsh_read8( j + 1, pfs ) << 8 );
fsize += ( romfsh_read8( j + 2, pfs ) << 16 ) + ( romfsh_read8( j + 3, pfs ) << 24 );
j += ROMFS_SIZE_LEN;
if( !is_deleted )
{
// Found the valid file
*act_len = n;
}
// Move to next file
i = j + fsize;
// On WOFS, all file names must begin at a multiple of ROMFS_ALIGN
if( romfsh_is_wofs( pfs ) )
i = ( i + ROMFS_ALIGN - 1 ) & ~( ROMFS_ALIGN - 1 );
*start = i; // modify the start address
return FS_FILE_OK;
}
// Open the given file, returning one of FS_FILE_NOT_FOUND, FS_FILE_ALREADY_OPENED
// or FS_FILE_OK
static uint8_t romfs_open_file( const char* fname, FD* pfd, FSDATA *pfs, uint32_t *plast, uint32_t *pnameaddr )
{
uint32_t i, j, n;
char fsname[ MAX_FNAME_LENGTH + 1 ];
uint32_t fsize;
int is_deleted;
// Look for the file
i = 0;
while( 1 )
{
if( i >= INTERNAL_FLASH_SIZE ){
*plast = INTERNAL_FLASH_SIZE - 1; // point to last one
return FS_FILE_NOT_FOUND;
}
if( romfsh_read8( i, pfs ) == WOFS_END_MARKER_CHAR )
{
*plast = i;
return FS_FILE_NOT_FOUND;
}
// Read file name
n = i;
for( j = 0; j < MAX_FNAME_LENGTH; j ++ )
{
fsname[ j ] = romfsh_read8( i + j, pfs );
if( fsname[ j ] == 0 )
break;
}
// ' i + j' now points at the '0' byte
j = i + j + 1;
// Round to a multiple of ROMFS_ALIGN
j = ( j + ROMFS_ALIGN - 1 ) & ~( ROMFS_ALIGN - 1 );
// WOFS has an additional WOFS_DEL_FIELD_SIZE bytes before the size as an indication for "file deleted"
if( romfsh_is_wofs( pfs ) )
{
is_deleted = romfsh_read8( j, pfs ) == WOFS_FILE_DELETED;
j += WOFS_DEL_FIELD_SIZE;
}
else
is_deleted = 0;
// And read the size
fsize = romfsh_read8( j, pfs ) + ( romfsh_read8( j + 1, pfs ) << 8 );
fsize += ( romfsh_read8( j + 2, pfs ) << 16 ) + ( romfsh_read8( j + 3, pfs ) << 24 );
j += ROMFS_SIZE_LEN;
if( !c_strncasecmp( fname, fsname, MAX_FNAME_LENGTH ) && !is_deleted )
{
// Found the file
pfd->baseaddr = j;
pfd->offset = 0;
pfd->size = fsize;
if( pnameaddr )
*pnameaddr = n;
return FS_FILE_OK;
}
// Move to next file
i = j + fsize;
// On WOFS, all file names must begin at a multiple of ROMFS_ALIGN
if( romfsh_is_wofs( pfs ) )
i = ( i + ROMFS_ALIGN - 1 ) & ~( ROMFS_ALIGN - 1 );
}
*plast = 0;
return FS_FILE_NOT_FOUND;
}
static int romfs_open( const char *path, int flags, int mode, void *pdata )
{
FD tempfs;
int i;
FSDATA *pfsdata = ( FSDATA* )pdata;
int must_create = 0;
int exists;
uint8_t lflags = ROMFS_FILE_FLAG_READ;
uint32_t firstfree, nameaddr;
if( romfs_num_fd == TOTAL_MAX_FDS )
{
return -1;
}
// Does the file exist?
exists = romfs_open_file( path, &tempfs, pfsdata, &firstfree, &nameaddr ) == FS_FILE_OK;
// Now interpret "flags" to set file flags and to check if we should create the file
if( flags & O_CREAT )
{
// If O_CREAT is specified with O_EXCL and the file already exists, return with error
if( ( flags & O_EXCL ) && exists )
{
return -1;
}
// Otherwise create the file if it does not exist
must_create = !exists;
}
if( ( flags & O_TRUNC ) && ( flags & ( O_WRONLY | O_RDWR ) ) && exists )
{
// The file exists, but it must be truncated
// In the case of WOFS, this effectively means "create a new file"
must_create = 1;
}
// ROMFS can't create files
if( must_create && ( ( pfsdata->flags & ROMFS_FS_FLAG_WO ) == 0 ) )
{
return -1;
}
// Decode access mode
if( flags & O_WRONLY )
lflags = ROMFS_FILE_FLAG_WRITE;
else if( flags & O_RDWR )
lflags = ROMFS_FILE_FLAG_READ | ROMFS_FILE_FLAG_WRITE;
if( flags & O_APPEND )
lflags |= ROMFS_FILE_FLAG_APPEND;
// If a write access is requested when the file must NOT be created, this
// is an error
if( ( lflags & ( ROMFS_FILE_FLAG_WRITE | ROMFS_FILE_FLAG_APPEND ) ) && !must_create )
{
return -1;
}
if( ( lflags & ( ROMFS_FILE_FLAG_WRITE | ROMFS_FILE_FLAG_APPEND ) ) && romfs_fs_is_flag_set( pfsdata, ROMFS_FS_FLAG_WRITING ) )
{
// At most one file can be opened in write mode at any given time on WOFS
return -1;
}
// Do we need to create the file ?
if( must_create )
{
if( exists )
{
// Invalidate the file first by changing WOFS_DEL_FIELD_SIZE bytes before
// the file length to WOFS_FILE_DELETED
uint8_t tempb[] = { WOFS_FILE_DELETED, 0xFF, 0xFF, 0xFF };
pfsdata->writef( tempb, tempfs.baseaddr - ROMFS_SIZE_LEN - WOFS_DEL_FIELD_SIZE, WOFS_DEL_FIELD_SIZE, pfsdata );
}
// Find the last available position by asking romfs_open_file to look for a file
// with an invalid name
romfs_open_file( "\1", &tempfs, pfsdata, &firstfree, NULL );
// Is there enough space on the FS for another file?
if( pfsdata->max_size - firstfree + 1 < c_strlen( path ) + 1 + WOFS_MIN_NEEDED_SIZE + WOFS_DEL_FIELD_SIZE )
{
return -1;
}
// Make sure we can get a file descriptor before writing
if( ( i = romfs_find_empty_fd() ) < 0 )
{
return -1;
}
// Write the name of the file
pfsdata->writef( path, firstfree, c_strlen( path ) + 1, pfsdata );
firstfree += c_strlen( path ) + 1; // skip over the name
// Align to a multiple of ROMFS_ALIGN
firstfree = ( firstfree + ROMFS_ALIGN - 1 ) & ~( ROMFS_ALIGN - 1 );
firstfree += ROMFS_SIZE_LEN + WOFS_DEL_FIELD_SIZE; // skip over the size and the deleted flags area
tempfs.baseaddr = firstfree;
tempfs.offset = tempfs.size = 0;
// Set the "writing" flag on the FS to indicate that there is a file opened in write mode
romfs_fs_set_flag( pfsdata, ROMFS_FS_FLAG_WRITING );
}
else // File must exist (and was found in the previous 'romfs_open_file' call)
{
if( !exists )
{
return -1;
}
if( ( i = romfs_find_empty_fd() ) < 0 )
{
return -1;
}
}
// Copy the descriptor information
tempfs.flags = lflags;
c_memcpy( fd_table + i, &tempfs, sizeof( FD ) );
romfs_num_fd ++;
return i;
}
static int romfs_close( int fd, void *pdata )
{
if(fd<0 || fd>=TOTAL_MAX_FDS)
return 0;
FD* pfd = fd_table + fd;
FSDATA *pfsdata = ( FSDATA* )pdata;
uint8_t temp[ ROMFS_SIZE_LEN ];
if( pfd->flags & ( ROMFS_FILE_FLAG_WRITE | ROMFS_FILE_FLAG_APPEND ) )
{
// Write back the size
temp[ 0 ] = pfd->size & 0xFF;
temp[ 1 ] = ( pfd->size >> 8 ) & 0xFF;
temp[ 2 ] = ( pfd->size >> 16 ) & 0xFF;
temp[ 3 ] = ( pfd->size >> 24 ) & 0xFF;
pfsdata->writef( temp, pfd->baseaddr - ROMFS_SIZE_LEN, ROMFS_SIZE_LEN, pfsdata );
// Clear the "writing" flag on the FS instance to allow other files to be opened
// in write mode
romfs_fs_clear_flag( pfsdata, ROMFS_FS_FLAG_WRITING );
}
romfs_close_fd( fd );
romfs_num_fd --;
return 0;
}
static _ssize_t romfs_write( int fd, const void* ptr, size_t len, void *pdata )
{
if(fd<0 || fd>=TOTAL_MAX_FDS)
return -1;
if(len == 0)
return 0;
FD* pfd = fd_table + fd;
FSDATA *pfsdata = ( FSDATA* )pdata;
if( ( pfd->flags & ( ROMFS_FILE_FLAG_WRITE | ROMFS_FILE_FLAG_APPEND ) ) == 0 )
{
return -1;
}
// Append mode: set the file pointer to the end
if( pfd->flags & ROMFS_FILE_FLAG_APPEND )
pfd->offset = pfd->size;
// Only write at the end of the file!
if( pfd->offset != pfd->size )
return 0;
// Check if we have enough space left on the device. Always keep 1 byte for the final 0xFF
// and ROMFS_ALIGN - 1 bytes for aligning the contents of the file data in the worst case
// scenario (so ROMFS_ALIGN bytes in total)
if( pfd->baseaddr + pfd->size + len > pfsdata->max_size - ROMFS_ALIGN )
len = pfsdata->max_size - ( pfd->baseaddr + pfd->size ) - ROMFS_ALIGN;
pfsdata->writef( ptr, pfd->offset + pfd->baseaddr, len, pfsdata );
pfd->offset += len;
pfd->size += len;
return len;
}
static _ssize_t romfs_read( int fd, void* ptr, size_t len, void *pdata )
{
if(fd<0 || fd>=TOTAL_MAX_FDS)
return -1;
if(len == 0)
return 0;
FD* pfd = fd_table + fd;
long actlen = fsmin( len, pfd->size - pfd->offset );
FSDATA *pfsdata = ( FSDATA* )pdata;
if( ( pfd->flags & ROMFS_FILE_FLAG_READ ) == 0 )
{
return -1;
}
if( pfsdata->flags & ROMFS_FS_FLAG_DIRECT )
c_memcpy( ptr, pfsdata->pbase + pfd->offset + pfd->baseaddr, actlen );
else
actlen = pfsdata->readf( ptr, pfd->offset + pfd->baseaddr, actlen, pfsdata );
pfd->offset += actlen;
return actlen;
}
// lseek
static int romfs_lseek( int fd, int off, int whence, void *pdata )
{
if(fd<0 || fd>=TOTAL_MAX_FDS)
return -1;
FD* pfd = fd_table + fd;
uint32_t newpos = 0;
switch( whence )
{
case SEEK_SET:
newpos = off;
break;
case SEEK_CUR:
newpos = pfd->offset + off;
break;
case SEEK_END:
newpos = pfd->size + off;
break;
default:
return -1;
}
if( newpos > pfd->size )
return -1;
pfd->offset = newpos;
return newpos;
}
// ****************************************************************************
// WOFS functions and instance descriptor for real hardware
#if defined( BUILD_WOFS )
static uint32_t sim_wofs_write( const void *from, uint32_t toaddr, uint32_t size, const void *pdata )
{
const FSDATA *pfsdata = ( const FSDATA* )pdata;
if(toaddr>=INTERNAL_FLASH_SIZE)
{
NODE_ERR("ERROR in flash op: wrong addr.\n");
return 0;
}
toaddr += ( uint32_t )pfsdata->pbase;
return platform_flash_write( from, toaddr, size );
}
static uint32_t sim_wofs_read( void *to, uint32_t fromaddr, uint32_t size, const void *pdata )
{
const FSDATA *pfsdata = ( const FSDATA* )pdata;
if(fromaddr>=INTERNAL_FLASH_SIZE)
{
NODE_ERR("ERROR in flash op: wrong addr.\n");
return 0;
}
fromaddr += ( uint32_t )pfsdata->pbase;
return platform_flash_read( to, fromaddr, size );
}
// This must NOT be a const!
static FSDATA wofs_fsdata =
{
NULL,
ROMFS_FS_FLAG_WO,
sim_wofs_read,
sim_wofs_write,
0
};
// WOFS formatting function
// Returns 1 if OK, 0 for error
int wofs_format( void )
{
uint32_t sect_first, sect_last;
FD tempfd;
platform_flash_get_first_free_block_address( &sect_first );
// Get the first free address in WOFS. We use this address to compute the last block that we need to
// erase, instead of simply erasing everything from sect_first to the last Flash page.
romfs_open_file( "\1", &tempfd, &wofs_fsdata, &sect_last, NULL );
sect_last = platform_flash_get_sector_of_address( sect_last + ( uint32_t )wofs_fsdata.pbase );
while( sect_first <= sect_last )
if( platform_flash_erase_sector( sect_first ++ ) == PLATFORM_ERR )
return 0;
return 1;
}
int wofs_open(const char *_name, int flags){
return romfs_open( _name, flags, 0, &wofs_fsdata );
}
int wofs_close( int fd ){
return romfs_close( fd, &wofs_fsdata );
}
size_t wofs_write( int fd, const void* ptr, size_t len ){
return romfs_write( fd, ptr, len, &wofs_fsdata );
}
size_t wofs_read( int fd, void* ptr, size_t len){
return romfs_read( fd, ptr, len, &wofs_fsdata );
}
int wofs_lseek( int fd, int off, int whence ){
return romfs_lseek( fd, off, whence, &wofs_fsdata );
}
int wofs_eof( int fd ){
if(fd<0 || fd>=TOTAL_MAX_FDS)
return -1;
FD* pfd = fd_table + fd;
// NODE_DBG("off:%d, sz:%d\n",pfd->offset, pfd->size);
return pfd->offset == pfd->size;
}
int wofs_getc( int fd ){
char c = EOF;
if(!wofs_eof(fd)){
romfs_read( fd, &c, 1, &wofs_fsdata );
}
// NODE_DBG("c: %d\n", c);
return (int)c;
}
int wofs_ungetc( int c, int fd ){
return romfs_lseek( fd, -1, SEEK_CUR, &wofs_fsdata );
}
// Find the next file, returning FS_FILE_OK or FS_FILE_NOT_FOUND if there no file left.
uint8_t wofs_next( uint32_t *start, char* fname, size_t len, size_t *act_len ){
return romfs_next_file( start, fname, len, act_len, &wofs_fsdata );
}
#endif // #ifdef BUILD_WOFS
// Initialize both ROMFS and WOFS as needed
int romfs_init( void )
{
unsigned i;
for( i = 0; i < TOTAL_MAX_FDS; i ++ )
{
c_memset( fd_table + i, 0xFF, sizeof( FD ) );
fd_table[ i ].flags = 0;
}
#if defined( BUILD_WOFS )
// Get the start address and size of WOFS and register it
wofs_fsdata.pbase = ( uint8_t* )platform_flash_get_first_free_block_address( NULL );
wofs_fsdata.max_size = INTERNAL_FLASH_SIZE - ( ( uint32_t )wofs_fsdata.pbase );
NODE_DBG("wofs.pbase:%x,max:%x\n",wofs_fsdata.pbase,wofs_fsdata.max_size);
#endif // ifdef BUILD_WOFS
return 0;
}
#else // #if defined( BUILD_ROMFS ) || defined( BUILD_WOFS )
int romfs_init( void )
{
}
#endif // #if defined( BUILD_ROMFS ) || defined( BUILD_WOFS )
int test_romfs()
{
int fd;
int i, size;
fd = wofs_open("init.lua",O_RDONLY);
NODE_DBG("open file fd:%d\n", fd);
char r[128];
NODE_DBG("read from file:\n");
c_memset(r,0,128);
size = wofs_read(fd,r,128);
r[size]=0;
NODE_DBG(r);
NODE_DBG("\n");
wofs_close(fd);
fd = wofs_open("testm.lua",O_RDONLY);
NODE_DBG("open file fd:%d\n", fd);
NODE_DBG("read from file:\n");
c_memset(r,0,128);
size = wofs_read(fd,r,128);
r[size]=0;
NODE_DBG(r);
NODE_DBG("\n");
wofs_close(fd);
return 0;
}
// Read-only ROM filesystem
#ifndef __ROMFS_H__
#define __ROMFS_H__
#include "c_types.h"
#include "c_fcntl.h"
/*******************************************************************************
The Read-Only "filesystem" resides in a contiguous zone of memory, with the
following structure (repeated for each file):
Filename: ASCIIZ, max length is DM_MAX_FNAME_LENGTH, first byte is 0xFF if last file
File size: (4 bytes), aligned to ROMFS_ALIGN bytes
File data: (file size bytes)
The WOFS (Write Once File System) uses much of the ROMFS functions, thuss it is
also implemented in romfs.c. It resides in a contiguous zone of memory, with a
structure that is quite similar with ROMFS' structure (repeated for each file):
Filename: ASCIIZ, max length is DM_MAX_FNAME_LENGTH, first byte is 0xFF if last file.
WOFS filenames always begin at an address which is a multiple of ROMFS_ALIGN.
File deleted flag: (WOFS_DEL_FIELD_SIZE bytes), aligned to ROMFS_ALIGN bytes
File size: (4 bytes), aligned to ROMFS_ALIGN bytes
File data: (file size bytes)
*******************************************************************************/
// GLOBAL maximum file length (on ALL supported filesystem)
#define MAX_FNAME_LENGTH 30
enum
{
FS_FILE_NOT_FOUND,
FS_FILE_OK
};
// ROMFS/WOFS functions
typedef uint32_t ( *p_fs_read )( void *to, uint32_t fromaddr, uint32_t size, const void *pdata );
typedef uint32_t ( *p_fs_write )( const void *from, uint32_t toaddr, uint32_t size, const void *pdata );
// File flags
#define ROMFS_FILE_FLAG_READ 0x01
#define ROMFS_FILE_FLAG_WRITE 0x02
#define ROMFS_FILE_FLAG_APPEND 0x04
// A small "FILE" structure
typedef struct
{
uint32_t baseaddr;
uint32_t offset;
uint32_t size;
uint8_t flags;
} FD;
// WOFS constants
// The miminum size we need in order to create another file
// This size will be added to the size of the filename when creating a new file
// to ensure that there's enough space left on the device
// This comes from the size of the file length field (4) + the maximum number of
// bytes needed to align this field (3) + a single 0xFF byte which marks the end
// of the filesystem (1) + the maximum number of bytes needed to align the contents
// of a file (3)
#define WOFS_MIN_NEEDED_SIZE 11
// Filesystem flags
#define ROMFS_FS_FLAG_DIRECT 0x01 // direct mode (the file is mapped in a memory area directly accesible by the CPU)
#define ROMFS_FS_FLAG_WO 0x02 // this FS is actually a WO (Write-Once) FS
#define ROMFS_FS_FLAG_WRITING 0x04 // for WO only: there is already a file opened in write mode
// File system descriptor
typedef struct
{
uint8_t *pbase; // pointer to FS base in memory (only for ROMFS_FS_FLAG_DIRECT)
uint8_t flags; // flags (see above)
p_fs_read readf; // pointer to read function (for non-direct mode FS)
p_fs_write writef; // pointer to write function (only for ROMFS_FS_FLAG_WO)
uint32_t max_size; // maximum size of the FS (in bytes)
} FSDATA;
#define romfs_fs_set_flag( p, f ) p->flags |= ( f )
#define romfs_fs_clear_flag( p, f ) p->flags &= ( uint8_t )~( f )
#define romfs_fs_is_flag_set( p, f ) ( ( p->flags & ( f ) ) != 0 )
#if defined( BUILD_WOFS )
int wofs_format( void );
int wofs_open(const char *name, int flags);
int wofs_close( int fd );
size_t wofs_write( int fd, const void* ptr, size_t len );
size_t wofs_read( int fd, void* ptr, size_t len);
int wofs_lseek( int fd, int off, int whence );
int wofs_eof( int fd );
int wofs_getc( int fd );
int wofs_ungetc( int c, int fd );
uint8_t wofs_next( uint32_t *start, char* fname, size_t len, size_t *act_len ); // for list file name
#endif
// FS functions
int romfs_init( void );
#endif
blockquote {
padding: 0 15px;
color: #777;
border-left: 4px solid #ddd;
}
.rst-content blockquote {
margin: 0;
}
/*shifts the nested subnav label to the left to align it with the regular nav item labels*/
ul.subnav ul.subnav span {
padding-left: 1.3em;
}
body {
font-size: 100%;
}
p {
line-height: 20px;
margin-bottom: 16px;
}
h1, h2 {
border-bottom: 1px solid #eee;
line-height: 1.2;
margin-top: 1.2em;
margin-bottom: 16px;
}
h3, h4, h5, h6 {
margin: 1em 0 0.7em 0;
}
code {
font-size: 85%;
margin-right: 3px;
}
table.docutils td code {
font-size: 100%;
}
.wy-plain-list-disc, .rst-content .section ul, .rst-content .toctree-wrapper ul, article ul {
line-height: 20px;
margin-bottom: 16px;
}
\ No newline at end of file
# NodeMCU Dokumentation
NodeMCU ist eine [eLua](http://www.eluaproject.net/)-basierende firmware für den [ESP8266 WiFi SOC von Espressif](http://espressif.com/en/products/esp8266/). Dies ist ein Partnerprojekt für die beliebten [NodeMCU dev kits](https://github.com/nodemcu/nodemcu-devkit-v1.0) - open source NodeMCU boards mit ESP8266-12E chips.
Diese firmware nutzt das Espressif NON-OS SDK, das Dateisystem basiert auf [spiffs](https://github.com/pellepl/spiffs).
There are essentially three ways to build your NodeMCU firmware: cloud build service, Docker image, dedicated Linux environment (possibly VM).
**Building manually**
Note that the *default configuration in the C header files* (`user_config.h`, `user_modules.h`) is designed to run on all ESP modules including the 512 KB modules like ESP-01 and only includes general purpose interface modules which require at most two GPIO pins.
## Cloud Build Service
NodeMCU "application developers" just need a ready-made firmware. There's a [cloud build service](http://nodemcu-build.com/) with a nice UI and configuration options for them.
## Docker Image
Occasional NodeMCU firmware hackers don't need full control over the complete tool chain. They might not want to setup a Linux VM with the build environment. Docker to the rescue. Give [Docker NodeMCU build](https://hub.docker.com/r/marcelstoer/nodemcu-build/) a try.
## Linux Build Environment
NodeMCU firmware developers commit or contribute to the project on GitHub and might want to build their own full fledged build environment with the complete tool chain. There is a [post in the esp8266.com Wiki](http://www.esp8266.com/wiki/doku.php?id=toolchain#how_to_setup_a_vm_to_host_your_toolchain) that describes this.
\ No newline at end of file
# Extension Developer FAQ
**# # # Work in Progress # # #**
## How does the non-OS SDK structure execution
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
BootROM code.
The ESP8266 boot ROM contains a set of primitive tasking and dispatch functions
which are also used by the SDK. In this model, execution units are either:
- **INTERRUPT SERVICE ROUTINES (ISRs)** which are declared and controlled
through the `ets_isr_attach()` and other `ets_isr_*` and `ets_intr_*`
functions. ISRs can be defined on a range of priorities, where a higher
priority ISR is able to interrupt a lower priority one. ISRs are time
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
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)
is indeterminate: whilst cache-hits can run at full memory bandwidth, any
cache-misses require the code to be read from Flash; and even though
H/W-based, this is at roughly 26x slower than memory bandwidth (for DIO
flash); this will cause ISR execution to fall outside the require time
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
queued task. The Espressif guideline is that no individual task should run for
more than 15 mSec, before returning control to the SDK.
The ROM will queue up to 32 pending tasks at priorities 0..31 and will
execute the highest priority queued task next (or wait on interrupt if none
is runnable). The SDK tasking system is layered on this ROM dispatcher and
it reserves 29 of these task priorities for its own use, including the
implementation of the various SDK timer, WiFi and other callback mechanisms
such as the software WDT.
Three of these task priorities are allocated for and exposed directly at an
application level. The application can declare a single task handler for each
level, and associate a task queue with the level. Tasks can be posted to this
queue. (The post will fail is the queue is full). Tasks are then delivered
FIFO within task priority.
How the three user task priorities USER0 .. USER2 are positioned relative to
the SDK task priorities is undocumented, but some SDK tasks definitely run at
a lower priority than USER0. As a result if you always have a USER task queued
for execution, then you can starve SDK housekeeping tasks and you will start
to get WiFi and other failures. Espressif therefore recommends that you don't
stay computable for more than 500 mSec to avoid such timeouts.
Note that the 50µS, 15mSec and 500mSec limits are guidelines
and not hard constraints -- that is if you break them (slightly) then your code
may (usually) work, but you can get very difficult to diagnose and intermittent
failures. Also running ISRs from Flash may work until there is a collision with
SPIFFS I/O which will then a cause CPU exception.
Also note that the SDK API function `system_os_post()`, and the `task_post_*()`
macros which generate this can be safely called from an ISR.
The Lua runtime is NOT reentrant, and hence any code which calls any Lua API
must run within a task context. Any such task is what we call a _Lua-Land Task_
(or **LLT**). _ISRs must not access the Lua API or Lua resources._ LLTs can be
executed as SDK API callbacks or OS tasks. They can also, of course, call the
Lua execution system to execute Lua code (e.g. `luaL_dofile()` and related
calls).
Also since the application has no control over the relative time ordering of
tasks and SDK API callbacks, LLTs can't make any assumptions about whether a
task and any posted successors will run consecutively.
This API is designed to complement the Lua library model, so that a library can
declare one or more task handlers and that both ISPs and LLTs can then post a
message for delivery to a task handler. Each task handler has a unique message
associated with it, and may bind a single uint32 parameter. How this parameter
is interpreted is left to the task poster and task handler to coordinate.
The interface is exposed through `#include "task/task.h"` and involves two API
calls. Any task handlers are declared, typically in the module_init function by
assigning `task_get_id(some_task_callback)` to a (typically globally) accessible
handle variable, say `XXX_callback_handle`. This can then be used in an ISR or
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.
Adafruit provides a really nice [firmware flashing tutorial](https://learn.adafruit.com/building-and-running-micropython-on-the-esp8266/flash-firmware). Below you'll find just the basics for the two popular tools esptool and NodeMCU Flasher.
!!! note "Note:"
Keep in mind that the ESP8266 needs to be [put into flash mode](#putting-device-into-flash-mode) before you can flash a new firmware!
## esptool
> A cute Python utility to communicate with the ROM bootloader in Espressif ESP8266. It is intended to be a simple, platform independent, open source replacement for XTCOM.
Source: [https://github.com/themadinventor/esptool](https://github.com/themadinventor/esptool)
Supported platforms: OS X, Linux, Windows, anything that runs Python
**Running esptool.py**
Run the following command to flash an *aggregated* binary as is produced for example by the [cloud build service](build.md#cloud-build-service) or the [Docker image](build.md#docker-image).
`esptool.py --port <USB-port-with-ESP8266> write_flash -fm <mode> -fs <size> 0x00000 <nodemcu-firmware>.bin`
- `mode` is `qio` for 512&nbsp;kByte modules and `dio` for 4&nbsp;MByte modules (`qio` might work as well, YMMV).
- `size` is given in bits. Specify `4m` for 512&nbsp;kByte and `32m` for 4&nbsp;MByte.
Check the [esptool flash modes documentation](https://github.com/themadinventor/esptool#flash-modes) for details and other options.
## NodeMCU Flasher
> A firmware Flash tool for NodeMCU...We are working on next version and will use QT framework. It will be cross platform and open-source.
Source: [https://github.com/nodemcu/nodemcu-flasher](https://github.com/nodemcu/nodemcu-flasher)
Supported platforms: Windows
## Putting Device Into Flash Mode
To enable ESP8266 firmware flashing GPIO0 pin must be pulled low before the device is reset. Conversely, for a normal boot, GPIO0 must be pulled high or floating.
If you have a [NodeMCU dev kit](https://github.com/nodemcu/nodemcu-devkit-v1.0) then you don't need to do anything, as the USB connection can pull GPIO0 low by asserting DTR and reset your board by asserting RTS.
If you have an ESP-01 or other device without built-in USB, you will need to enable flashing yourself by pulling GPIO0 low or pressing a "flash" switch.
## Which Files To Flash
If you build your firmware with the [cloud builder or the Docker image](build.md), or any other method that produces a *combined binary*, then you can flash that file directly to address 0x00000.
Otherwise, if you built your own firmware from source code:
- `bin/0x00000.bin` to 0x00000
- `bin/0x10000.bin` to 0x10000
Also, in some special circumstances, you may need to flash `blank.bin` or `esp_init_data_default.bin` to various addresses on the flash (depending on flash size and type), see [below](#upgrading-from-sdk-09x-firmware).
If upgrading from [SPIFFS](https://github.com/pellepl/spiffs) version 0.3.2 to 0.3.3 or later, or after flashing any new firmware (particularly one with a much different size), you may need to run [`file.format()`](modules/file.md#fileformat) to re-format your flash filesystem. You will know if you need to do this if your flash files disappeared, or if they exist but seem empty, or if data cannot be written to new files.
## Upgrading from SDK 0.9.x Firmware
If you flash a recent NodeMCU firmware for the first time, it's advisable that you get all accompanying files right. A typical case that often fails is when a module is upgraded from a 0.9.x firmware to the latest version built from the [NodeMCU build service](http://nodemcu-build.com). It might look like the brand new firmware is broken, but the reason for the missing Lua prompt is related to the big jump in SDK versions: Espressif changed the `esp_init_data_default.bin` for their devices along the way with the [SDK 1.4.0 release](http://bbs.espressif.com/viewtopic.php?f=46&t=1124). So things break when a NodeMCU firmware with SDK 1.4.0 or above is flashed to a module which contains old init data from a previous SDK.
Download a recent SDK release, e.g. [esp_iot_sdk_v1.4.0_15_09_18.zip](http://bbs.espressif.com/download/file.php?id=838) or later and extract `esp_init_data_default.bin` from there. *Use this file together with the new firmware during flashing*.
**esptool**
For [esptool](https://github.com/themadinventor/esptool) you specify another file to download at the command line.
```
esptool.py write_flash <flash options> 0x00000 <nodemcu-firmware>.bin 0x7c000 esp_init_data_default.bin
```
!!! note "Note:"
The address for `esp_init_data_default.bin` depends on the size of your module's flash. ESP-01, -03, -07 etc. with 512 kByte flash require `0x7c000`. Init data goes to `0x3fc000` on an ESP-12E with 4 MByte flash.
**NodeMCU Flasher**
The [NodeMCU Flasher](https://github.com/nodemcu/nodemcu-flasher) will download init data using a special path:
```
INTERNAL://DEFAULT
```
Replace the provided (old) `esp_init_data_default.bin` with the one extracted above and use the flasher like you're used to.
**References**
* [2A-ESP8266__IOT_SDK_User_Manual__EN_v1.5.pdf, Chapter 6](http://bbs.espressif.com/viewtopic.php?f=51&t=1024)
* [SPI Flash ROM Layout (without OTA upgrades)](https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map#spi-flash-rom-layout-without-ota-upgrades)
# Hardware FAQ
## What is this FAQ for?
This FAQ addresses hardware-specific issues relating to the NodeMcu firmware on
NoceMCU Inc Devkits and other ESP-8266 modules.
## Hardware Specifics
## Why file writes fail all the time on DEVKIT V1.0?
NodeMCU DEVKIT V1.0 uses ESP12-E-DIO(ESP-12-D) module. This module runs the
Flash memory in [Dual IO SPI](#whats-the-different-between-dio-and-qio-mode)
(DIO) mode. This firmware will not be correctly loaded if you use old flashtool
versions, and the filesystem will not work if you used a pre 0.9.6 firmware
version (<0.9.5) or old. The easiest way to resolve this problem s update all
the firmware and flash tool to current version.
- Use the latest [esptool.py](https://github.com/themadinventor/esptool) with
DIO support and command option to flash firmware, or
- Use the latest [NodeMCU flasher](https://github.com/NodeMCU/NodeMCU-flasher)
with default option. (You must select the `restore to default` option in advanced
menu tab), or
- Use the latest Espressif's flash tool -- see [this Espressif forum
topic](http://bbs.espressif.com/viewtopic.php?f=5&t=433) (without auto download
support). Use DIO mode and 32M flash size option, and flash latest firmware to
0x00000. Before flashing firmware, remember to hold FLASH button, and press RST
button once. Note that the new NodeMCU our firmware download tool, when
released, will be capable of flashing firmware automatically without any button
presses.
## What's the different between DIO and QIO mode?
Whether DIO or QIO modes are available depends on the physical connection
between the ESP8266 CPU and its onboard flash chip. QIO connects to the flash
using 5 data pins as compared to DIO's 3. This frees up an extra 2 IO pins for
GPIO use, but this also halves the read/write data-rate to Flash compared to
QIO modules.
## How to use DEVKIT V0.9 on Mac OS X?
<TODO>
### How does DEVKIT use DTR and RTS enter download mode?
<TODO>
# NodeMCU Documentation
NodeMCU is an [eLua](http://www.eluaproject.net/) based firmware for the [ESP8266 WiFi SOC from Espressif](http://espressif.com/en/products/esp8266/). The firmware is based on the Espressif NON-OS SDK and uses a file system based on [spiffs](https://github.com/pellepl/spiffs). The code repository consists of 98.1% C-code that glues the thin Lua veneer to the SDK.
The NodeMCU *firmware* is a companion project to the popular [NodeMCU dev kits](https://github.com/nodemcu/nodemcu-devkit-v1.0), ready-made open source development boards with ESP8266-12E chips.
## Programming Model
The NodeMCU programming model is similar to that of [Node.js](https://en.wikipedia.org/wiki/Node.js), only in Lua. It is asynchronous and event-driven. Many functions, therefore, have parameters for callback functions. To give you an idea what a NodeMCU program looks like study the short snippets below. For more extensive examples have a look at the `/lua_examples` folder in the repository on GitHub.
```lua
-- a simple HTTP server
srv = net.createServer(net.TCP)
srv:listen(80, function(conn)
conn:on("receive", function(conn, payload)
print(payload)
conn:send("<h1> Hello, NodeMCU.</h1>")
end)
conn:on("sent", function(conn) conn:close() end)
end)
```
```lua
-- connect to WiFi access point
wifi.setmode(wifi.STATION)
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
print("Station lost connection with access point. Attempting to reconnect...")
else
print("STATION_CONNECTING")
end
end)
```
```lua
-- manipulate hardware like with Arduino
pin = 1
gpio.mode(pin, gpio.OUTPUT)
gpio.write(pin, gpio.HIGH)
print(gpio.read(pin))
```
## Getting Started
1. [Build the firmeware](build.md) with the modules you need.
1. [Flash the firmware](flash.md) to the chip.
1. [Upload code](upload.md) to the firmware.
# FAQ
**# # # Work in Progress # # #**
*This was started by [Terry Ellison](https://github.com/TerryE) as an unofficial FAQ in mid 2015. It never became officially official and it is in need of an overhaul, see [#937](https://github.com/nodemcu/nodemcu-firmware/issues/937). Yet, it is still very valuable and is, therefore, included here.*
## What is this FAQ for?
This FAQ does not aim to help you to learn to program or even how to program in Lua. There are plenty of resources on the Internet for this, some of which are listed in [Where to start](#where-to-start). What this FAQ does is to answer some of the common questions that a competent Lua developer would ask in learning how to develop Lua applications for the ESP8266 based boards running the [NodeMcu](http://NodeMCU.com/index_en.html) firmware.
## Lua Language
### Where to start
The NodeMCU firmware implements Lua 5.1 over the Espressif SDK for its ESP8266 SoC and the IoT modules based on this.
* The official lua.org **[Lua Language specification](http://www.lua.org/manual/5.1/manual.html)** gives a terse but complete language specification.
* Its [FAQ](http://www.lua.org/faq.html) provides information on Lua availability and licensing issues.
* The **[unofficial Lua FAQ](http://www.luafaq.org/)** provides a lot of useful Q and A content, and is extremely useful for those learning Lua as a second language.
* 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/Learning) 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](http://bbs.espressif.com/viewforum.php?f=5) .
* The **[NodeMCU documentation](http://www.NodeMCU.com/docs/)** is available online. However, please remember that the development team are based in China, and English is a second language, so the documentation needs expanding and be could improved with technical proofing.
* As with all Open Source projects the source for the NodeMCU firmware is openly available on the [GitHub NodeMCU-firmware](https://github.com/NodeMCU/NodeMCU-firmware) repository.
### How is NodeMCU Lua different to standard Lua?
Whilst the Lua standard distribution includes a host 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, light-weight 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.
The ESP8266 was designed and is fabricated in China by [Espressif Systems](http://espressif.com/new-sdk-release/). Espressif have also developed and released a companion software development kit (SDK) to enable developers to build practical IoT applications for the ESP8266. The SDK is made freely available to developers in the form of binary libraries and SDK documentation. However this is in a *closed format*, with no developer access to the source files, so ESP8266 applications *must* rely solely on the SDK API (and the somewhat Spartan SDK API documentation).
The NodeMCU Lua firmware is an ESP8266 application and must therefore be layered over the ESP8266 SDK. However, the hooks and features of Lua enable it to be seamlessly integrated without loosing any of the standard Lua language features. The firmware has replaced some standard Lua modules that don't align well with the SDK structure with ESP8266-specific versions. For example, the standard `io` and `os` libraries don't work, but have been largely replaced by the NodeMCU `node` and `file` libraries. The `debug` and `math` libraries have also been omitted to reduce the runtime footprint.
NodeMCU Lua is based on [eLua](http://www.eluaproject.net/overview), a fully featured implementation of Lua 5.1 that has been optimized for embedded system development and execution to provide a scripting framework that can be used to deliver useful applications within the limited RAM and Flash memory resources of embedded processors such as the ESP8266. One of the main changes introduced in the eLua fork is to use read-only tables and constants wherever practical for library modules. On a typical build this approach reduces the RAM footprint by some 20-25KB and this makes a Lua implementation for the ESP8266 feasible. This technique is called LTR and this is documented in detail in an eLua technical paper: [Lua Tiny RAM](http://www.eluaproject.net/doc/master/en_arch_ltr.html).
The mains impacts of the ESP8266 SDK and together with its hardware resource limitations are not in the Lua language implementation itself, but in how *application programmers must approach developing and structuring their applications*. As discussed in detail below, the SDK is non-preemptive and event driven. Tasks can be associated with given events by using the SDK API to registering callback functions to the corresponding events. Events are queued internally within the SDK, and it then calls the associated tasks one at a time, with each task returning control to the SDK on completion. *The SDK states that if any tasks run for more than 10 mSec, then services such as Wifi can fail.*
The NodeMCU libraries act as C wrappers around registered Lua callback functions to enable these to be used as SDK tasks. ***You must therefore use an Event-driven programming style in writing your ESP8266 Lua programs***. Most programmers are used to writing in a procedural style where there is a clear single flow of execution, and the program interfaces to operating system services by a set of synchronous API calls to do network I/O, etc. Whilst the logic of each individual task is procedural, this is not how you code up ESP8266 applications.
## ESP8266 Specifics
### How is coding for the ESP8266 the same as standard Lua?
* This is a fully featured Lua 5.1 implementation so all standard Lua language constructs and data types work.
* The main standard Lua libraries -- `core`, `coroutine`, `string` and `table` are implemented.
### How is coding for the ESP8266 different to standard Lua?
* The ESP8266 use onchip RAM and offchip Flash memory connected using a dedicated SPI interface. Both of these are *very* limited (when compared to systems than most application programmer use). The SDK and the Lua firmware already use the majority of this resource: the later build versions keep adding useful functionality, and unfortunately at an increased RAM and Flash cost, so depending on the build version and the number of modules installed the runtime can have as little as 17KB RAM and 40KB Flash available at an application level. This Flash memory is formatted an made available as a **SPI Flash File System (SPIFFS)** through the `file` library.
* However, if you choose to use a custom build, for example one which uses integer arithmetic instead of floating point, and which omits libraries that aren't needed for your application, then this can help a lot doubling these available resources. (See Marcel Stör's excellent [custom build tool](http://frightanic.com/NodeMCU-custom-build/) that he discusses in [this forum topic](http://www.esp8266.com/viewtopic.php?f=23&t=3001)). Even so, those developers who are used to dealing in MB or GB of RAM and file systems can easily run out of these resources. Some of the techniques discussed below can go a long way to mitigate this issue.
* Current versions of the ESP8266 run the SDK over the native hardware so there is no underlying operating system to capture errors and to provide graceful failure modes, so 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.
* 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 systems 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 an custom build option).
* The LTR implementation means that you can't easily extend standard libraries as you can in normal Lua, so for example an attempt to define `function table.pack()` will cause a runtime error because you can't write to the global `table`. (Yes, there are standard sand-boxing techniques to achieve the same effect by using metatable based inheritance, but if you try to use this type of approach within a real application, then you will find that you run out of RAM before you implement anything useful.)
* 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. There is no `luac` or batch support, although automated embedded processing is normally achieved by setting up the necessary event triggers in the `init.lua` script.
* The various libraries (`net`, `tmr`, `wifi`, etc.) use the SDK callback mechanism to bind Lua processing to individual events (for example a timer alarm firing). Developers should make full use of these events to keep Lua execution sequences short. *If any individual task takes too long to execute then other queued tasks can time-out and bad things start to happen.*
* Non-Lua processing (e.g. network functions) will usually only take place once the current Lua chunk has completed execution. So any network calls should be viewed at an asynchronous request. A common coding mistake is to assume that they are synchronous, that is if two `socket:send()` are on consecutive lines in a Lua programme, then the first has completed by the time the second is executed. This is wrong. Each `socket:send()` request simply queues the send operation for dispatch. Neither will start to process until the Lua code has return to is calling C function. Stacking up such requests in a single Lua task function burns scarce RAM and can trigger a PANIC. This true for timer, network, and other callbacks. It is even the case for actions such as requesting a system restart, as can be seen by the following example:
```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?
* The SDK employs an event-driven and task-oriented architecture for programming at an applications level.
* The SDK uses a startup hook `void user_init(void)`, defined by convention in the C module `user_main.c`, which it invokes on boot. The `user_init()` function can be used to do any initialisation required and to call the necessary timer alarms or other SDK API calls to bind and callback routines to implement the tasks needed in response to any system events.
* The API provides a set of functions for declaring application functions (written in C) as callbacks to associate application tasks with specific hardware and timer events. These are non-preemptive at an applications level.
* Whilst the SDK provides a number of interrupt driven device drivers, the hardware architecture severely limits the memory available for these drivers, so writing new device drivers is not a viable options for most developers
* The SDK interfaces internally with hardware and device drivers to queue pending events.
* The registered callback routines are invoked sequentially with the associated C task running to completion uninterrupted.
* In the case of Lua, these C tasks are typically functions within the Lua runtime library code and these typically act as C wrappers around the corresponding developer-provided Lua callback functions. An example here is the Lua `tmr.alarm(id, interval, repeat, callback)` function. The calls a function in the `tmr` library which registers a C function for this alarm using the SDK, and when this C function is called it then invokes the Lua callback.
The NodeMCU firmware simply mirrors this structure at a Lua scripting level:
* A startup module `init.lua` is invoked on boot. This function module can be used to do any initialisation required and to call the necessary timer alarms or libary calls to bind and callback routines to implement the tasks needed in response to any system events.
* 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 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.
* Excessively long-running Lua functions can therefore cause other system functions and services to timeout, or allocate memory 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.
* 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.
This event-driven approach is very different to a conventional procedural implementation of Lua.
Consider a simple telnet example given in `examples/fragment.lua`:
```lua
s=net.createServer(net.TCP)
s:listen(23,function(c)
con_std = c
function s_output(str)
if(con_std~=nil) then
con_std:send(str)
end
end
node.output(s_output, 0)
c:on("receive",function(c,l) node.input(l) end)
c:on("disconnection",function(c)
con_std = nil
node.output(nil)
end)
end)
```
This example defines five Lua functions:
| Function | Defined in | Parameters | Callback? |
|-----------|------------|------------|-----------|
| Main | Outer module | ... (Not used) | |
| Connection listener | Main | c (connection socket) | |
| s_output | Connection listener | str | Yes |
| On Receive| Connection listener | c, l (socket, input) | Yes |
| On Disconnect | Connection listener | c (socket) | Yes |
`s`, `con_std` and `s_output` are global, and no [upvalues](#why-is-it-importance-to-understand-how-upvalues-are-implemented-when-programming-for-the-esp8266) are used. There is no "correct" order to define these in, but we could reorder this code for clarity (though doing this adds a few extra globals) and define these functions separately one another. However, let us consider how this is executed:
* The outer module is compiled including the four internal functions.
* `Main` is then assigning the created `net.createServer()` to the global `s`. The `connection listener` closure is created and bound to a temporary variable which is then passed to the `socket.listen()` as an argument. The routine then exits returning control to the firmware.
* When another computer connects to port 23, the listener handler retrieves the reference to then connection listener and calls it with the socket parameter. This function then binds the s_output closure to the global `s_output`, and registers this function with the `node.output` hook. Likewise the `on receive` and `on disconnection` are bound to temporary variables which are passed to the respective on handlers. We now have four Lua function registered in the Lua runtime libraries associated with four events. This routine then exits returning control to the firmware.
* When a record is received, the on receive handler within the net library retrieves the reference to the `on receive` Lua function and calls it passing it the record. This routine then passes this to the `node.input()` and exits returning control to the firmware.
* The `node.input` handler polls on an 80 mSec timer alarm. If a compete Lua chunk is available (either via the serial port or node input function), then it executes it and any output is then passed to the `note.output` handler. which calls `s_output` function. Any pending sends are then processed.
* This cycle repeats until the other computer disconnects, and `net` library disconnection handler then calls the Lua `on disconnect` handler. This Lua routine dereferences the connected socket and closes the `node.output` hook and exits returning control to the disconnect handler which garbage collects any associated sockets and registered on handlers.
Whilst this is all going on, The SDK can (and often will) schedule other event tasks in between these Lua executions (e.g. to do the actual TCP stack processing). The longest individual Lua execution in this example is only 18 bytecode instructions (in the main routine).
Understanding how the system executes your code can help you structure it better and improve memory usage. Each event task is established by a callback in an API call in an earlier task.
### So what Lua library functions enable the registration of Lua callbacks?
SDK Callbacks include:
| Lua Module | Functions which define or remove callbacks |
|------------|--------------------------------------------|
| tmr | `alarm(id, interval, repeat, function())` |
| node | `key(type, 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))` |
| gpio | `trig(pin, type, function(level))` |
| mqqt | `client:m:on(event, function(conn[, topic, data])` |
| uart | `uart.on(event, cnt, [function(data)], [run_input])` |
### So how is context passed between Lua event tasks?
* It is important to understand that any event callback task is associated with a single Lua function. This function is executed from the relevant NodeMCU library C code using a `lua_call()`. Even system initialisation which executes the `dofile("init.lua")` can be treated as a special case of this. Each function can invoke other functions and so on, but it must ultimate return control to the C library code.
* By their very nature Lua `local` variables only exist within the context of an executing Lua function, and so all locals are destroyed between these `lua_call()` actions. *No locals are retained across events*.
* So context can only be passed between event routines by one of three mechanisms:
* **Globals** are by nature globally accessible. Any global will persist until explicitly dereference by reassigning `nil` to it. Globals can be readily enumerated 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 can't be used to pass context. However the ESP8266 file system uses flash memory and this 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.
* **Upvalues**. When a function is declared within an outer function, all of the local variables in the outer scope are available to the inner function. Since all functions are stored by reference the scope of the inner function might outlast the scope of the outer function, and the Lua runtime system ensures that any such references persist for the life of any functions that reference it. This standard feature of Lua is known as *closure* and is described in [Pil 6]. Such values are often called *upvalues*. Functions which are global or [[#So how is the Lua Registry used and why is this important?|registered]] callbacks will persist between event routines, and hence any upvalues referenced by them can be used for passing context.
### So how is the Lua Registry used and why is this important?
So all Lua callbacks are called by C wrapper functions that are themselves callback 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 simply another Lua table which is used for this purpose, except that it is hidden from direct Lua access. Any content that needs to be saved is created with a unique key. Using a standard Lua table enables standard garbage collection algorithms to operate on its content.
Note that we have identified a number of cases where library code does not correctly clean up Registry content when closing out an action, leading to memory leaks.
### Why is it importance to understand how upvalues are implemented when programming for the ESP8266?
Routines directly or indirectly referenced in the globals table, **_G**, or in the Lua Registry may use upvalues. The number of upvalues associated with a given routine is determined by the compiler and a vector is allocated 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 functions'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*. However allocating these internal structures is quite expensive in terms of memory, and this hidden overhead is hard to track or to understand. If you are developing a Lua application for a PC where the working RAM for an application is measured in MB, then this isn't really an issue. However, if you are developing an application for the ESP8266 where you might have 20 KB for your program and data, this could prove a killer.
One further complication is that some library functions don't correctly dereference expired callback references and as a result their upvalues may not be correctly garbage collected (though we are tracking this down and hopefully removing this issue). This will all be manifested as a memory leak. So using upvalues can cause more frequent and difficult to diagnose PANICs during testing. So my general recommendation is still to stick to globals for this specific usecase of passing context between event callbacks, and `nil` them when you have done with them.
### Can I encapsulate actions such as sending an email in a Lua function?
Think about the implications of these last few answers.
* An action such as composing and sending an email involves a message dialogue with a mail server over TCP. This in turn requires calling multiple API calls to the SDK and your Lua code must return control to the C calling library for this to be scheduled, otherwise these requests will just queue up, you'll run out of RAM and your application will PANIC.
* Hence it is simply ***impossible*** to write a Lua module so that you can do something like:
```lua
-- prepare message
status = mail.send(to, subject, body)
-- move on to next phase of processing.
```
* But you could code up a event-driven task to do this and pass it a callback to be executed on completion of the mail send, something along the lines of the following. Note that since this involves a lot of asynchronous processing and which therefore won't take place until you've returned control to the calling library C code, you will typically execute this as the last step in a function and therefore this is best done as a tailcall [PiL 6.3].
```lua
-- prepare message
local ms = require("mail_sender")
return ms.send(to, subject, body, function(status) loadfile("process_next.lua")(status) end)
```
* Building an application on the ESP8266 is a bit like threading pearls onto a necklace. Each pearl is an event task which must be small enough to run within its RAM resources and the string is the variable context that links the pearls together.
### 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()` 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. It does this with interrupts disabled, because if they are enabled then there is no guarantee that the delay will be as requested.
`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 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.
The latest SDK includes a caution that if any (callback) task runs for more than 10 mSec, then the Wifi and TCP stacks might fail, so if you want a delay of more than 8 mSec or so, then *using `tmr.delay()` is the wrong approach*. You should be using a timer alarm or another library callback, to allow the other processing to take place. As the NodeMCU documentation correctly advises (translating Chinese English into English): *`tmr.delay()` will make the CPU work in non-interrupt mode, so other instructions and interrupts will be blocked. Take care in using this function.*
### How do I avoid a PANIC loop in init.lua?
Most of us have fallen into the trap of creating an `init.lua` that has a bug in it, which then causes the system to reboot and hence gets stuck in a reboot loop. If you haven't then you probably will do so at least once.
* When this happens, the only robust solution is to reflash the firmware.
* The simplest way to avoid having to do this is to keep the `init.lua` as simple as possible -- say configure the wifi and then start your app using a one-time `tmr.alarm()` after a 2-3 sec delay. This delay is long enough to issue a `file.remove("init.lua")` through the serial port and recover control that way.
* Also it is always best to test any new `init.lua` by creating it as `init_test.lua`, say, and manually issuing a `dofile("init_test.lua")` through the serial port, and then only rename it when you are certain it is working as you require.
## Techniques for Reducing RAM and SPIFFS footprint
### 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.
* 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.
* Using a tiered approach where all user access to the ESP8266 is passed through a controlling server means that the end-user interface (or smartphone connector), together with all of the associated validation and security can be implemented on a system designed to have the capacity to do this. This means that you can limit the scope of your ESP8266 application to a limited set of functions being sent to or responding to requests from this system.
* *If you are trying to implement a user-interface or HTTP webserver in your ESP8266 then you are really abusing its intended purpose. When it comes to scoping your ESP8266 applications, the adage **K**eep **I**t **S**imple **S**tupid truly applies.*
### How do I minimise the footprint of an application on the file system
* It is possible to write Lua code in a very compact format which is very dense in terms of functionality per KB of source code.
* 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
* 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.
* Consider using `node.compile()` to pre-compile any production code. This removes the debug information from the compiled code reducing its size by roughly 40%. (However this is still perhaps 1.5-2x larger than a LuaSrcDiet-compressed source format, so if SPIFFS is tight then you might consider leaving less frequently run modules in Lua format. If you do a compilation, then you should consider removing the Lua source copy from file system as there's little point in keeping both on the ESP8266.
### How do I minimise the footprint of running application?
* The Lua Garbage collector is very aggressive at scanning and recovering dead resources. It uses an incremental mark-and-sweep strategy which means that any data which is not ultimately referenced back to the Globals table, the Lua registry or in-scope local variables in the current Lua code will be collected.
* Setting any variable to `nil` dereferences the previous context of that variable. (Note that reference-based variables such as tables, strings and functions can have multiple variables referencing the same object, but once the last reference has been set to `nil`, the collector will recover the storage.
* Unlike other compile-on-load languages such as PHP, Lua compiled code is treated the same way as any other variable type when it comes to garbage collection and can be collected when fully dereferenced, so that the code-space can be reused.
* Lua execution is intrinsically divided into separate event tasks with each bound to a Lua callback. This, when coupled with the strong dispose on dereference feature, means that it is very easy to structure your application using an classic technique which dates back to the 1950s known as Overlays.
* Various approaches can be use to implement this. One is described by DP Whittaker in his [Massive memory optimization: flash functions](http://www.esp8266.com/viewtopic.php?f=19&t=1940) topic. Another is to use *volatile modules*. There are standard Lua templates for creating modules, but the `require()` library function creates a reference for the loaded module in the `package.loaded` table, and this reference prevents the module from being garbage collected. To make a module volatile, you should remove this reference to the loaded module by setting its corresponding entry in `package.loaded` to `nil`. You can't do this in the outermost level of the module (since the reference is only created once execution has returned from the module code), but you can do it in any module function, and typically an initialisation function for the module, as in the following example:
```lua
local s=net.createServer(net.TCP)
s:listen(80,function(c) require("connector").init(c) end)
```
* **`connector.lua`** would be a standard module pattern except that the `M.init()` routine must include the lines
```lua
local M, module = {}, ...
...
function M.init(csocket)
package.loaded[module]=nil
...
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.
* 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)
package.loaded[module]=nil
module = nil
...
end
```
* Also note that you should ***not*** normally code this up listener call as the following because the RAM now has to accommodate both the module which creates the server *and* the connector logic.
```lua
...
local s=net.createServer(net.TCP)
local connector = require("connector") -- don't do this unless you've got the RAM available!
s:listen(80,connector)
```
### How do I reduce the size of my compiled code?
Note that there are two methods of saving compiled Lua to SPIFFS:
- The first is to use `node.compile()` on the `.lua` source file, which generates the equivalent bytecode `.lc` file. This approach strips out all the debug line and variable information.
- The second is to use `loadfile()` to load the source file into memory, followed by `string.dump()` to convert it in-memory to a serialised load format which can then be written back to a `.lc` file. This approach creates a bytecode file which retains the debug information.
The memory footprint of the bytecode created by method (2) is the same as when executing source files directly, but the footprint of bytecode created by method (1) is typically **60% of this size**, because the debug information is almost as large as the code itself. So using `.lc` files generated by `node.compile()` considerably reduces code size in memory -- albeit with the downside that any runtime errors are extremely limited.
In general consider method (1) if you have stable production code that you want to run in as low a RAM footprint as possible. Yes, method (2) can be used if you are still debugging, but you will probably be changing this code quite frequently, so it is easier to stick with `.lua` files for code that you are still developing.
Note that if you use `require("XXX")` to load your code then this will automatically search for `XXX.lc` then `XXX.lua` so you don't need to include the conditional logic to load the bytecode version if it exists, falling back to the source version otherwise.
### How do I get a feel for how much memory my functions use?
* You should get an overall understanding of the VM model if you want to make good use of the limited resources available to Lua applications. An essential reference here is [A No Frills Introduction to Lua 5.1 VM Instructions](http://luaforge.net/docman/83/98/ANoFrillsIntroToLua51VMInstructions.pdf) . This explain how the code generator works, how much memory overhead is involved with each table, function, string etc..
* You can't easily get a bytecode listing of your ESP8266 code; however there are two broad options for doing this:
* **Generate a bytecode listing on your development PC**. The Lua 5.1 code generator is basically the same on the PC and on the ESP8266, so whilst it isn't identical, using the standard Lua batch compiler `luac` against your source on your PC with the `-l -s` option will give you a good idea of what your code will generate. The main difference between these two variants is the size_t for ESP8266 is 4 bytes rather than the 8 bytes size_t found on modern 64bit development PCs; and the eLua variants generate different access references for ROM data types. If you want to see what the `string.dump()` version generates then drop the `-s` option to retain the debug information.
* **Upload your `.lc` files to the PC and disassemble then there**. There are a number of Lua code disassemblers which can list off the compiled code that you application modules will generate, `if` you have a script to upload files from your ESP8266 to your development PC. I use [ChunkSpy](http://luaforge.net/projects/chunkspy/) which can be downloaded [here](http://files.luaforge.net/releases/chunkspy/chunkspy/ChunkSpy-0.9.8/ChunkSpy-0.9.8.zip) , but you will need to apply the following patch so that ChunkSpy understands eLua data types:
```diff
--- a/ChunkSpy-0.9.8/5.1/ChunkSpy.lua 2015-05-04 12:39:01.267975498 +0100
+++ b/ChunkSpy-0.9.8/5.1/ChunkSpy.lua 2015-05-04 12:35:59.623983095 +0100
@@ -2193,6 +2193,9 @@
config.AUTO_DETECT = true
elseif a == "--brief" then
config.DISPLAY_BRIEF = true
+ elseif a == "--elua" then
+ config.LUA_TNUMBER = 5
+ config.LUA_TSTRING = 6
elseif a == "--interact" then
perform = ChunkSpy_Interact
```
* Your other great friend is to use `node.heap()` regularly through your code.
* Use these tools and play with coding approaches to see how many instructions each typical line of code takes in your coding style. The Lua Wiki gives some general optimisation tips, but in general just remember that these focus on optimising for execution speed and you will be interested mainly in optimising for code and variable space as these are what consumes precious RAM.
### What is the cost of using functions?
Consider the output of `dofile("test1a.lua")` on the following code compared to the equivalent where the function `pnh()` is removed and the extra `print(heap())` statement is placed inline:
```lua
-- test1b.lua
collectgarbage()
local heap = node.heap
print(heap())
local function pnh() print(heap()) end
pnh()
print(heap())
```
|Heap Value | Function Call | Inline |
|-----------|---------------|--------|
| 1 | 20712 | 21064 |
| 2 | 20624 | 21024 |
| 3 | 20576 | 21024 |
Here bigger means less RAM used.
Of course you should still use functions to structure your code and encapsulate common repeated processing, but just bear in mind that each function definition has a relatively high overhead for its header record and stack frame (compared to the 20 odd KB RAM available). *So try to avoid overusing functions. If there are less than a dozen or so lines in the function then you should consider putting this code inline if it makes sense to do so.*
### 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.
## Firmware and Lua app development
### How to save memory?
* The NodeMCU development team recommends that you consider using a tailored firmware build, which only includes the modules that you plan to use before developing any Lua application. Once you have the ability to make and flash custom builds, the you also have the option of moving time sensitive or logic intensive code into your own custom module. Doing this can save a large amount of RAM as C code can be run directly from Flash memory. If you want an easy-to-use intermediate option then why note try the [cloud based NodeMCU custom build service](http://frightanic.com/NodeMCU-custom-build)?.
# ADC Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2014-12-24 | [Zeroday](https://github.com/funshine) | [jmattsson](https://github.com/jmattsson) | [adc.c](../../../app/modules/adc.c)|
The ADC module provides access to the in-built ADC.
On the ESP8266 there is only a single-channel, which is multiplexed with the battery voltage. Depending on the setting in the "esp init data" (byte 107) one can either use the ADC to read an external voltage, or to read the system voltage (vdd33), but not both.
Which mode to use the ADC in can be configured via the `adc.force_init_mode()` function. Note that after switching from one to the other a system restart (e.g. power cycle, reset button, [`node.restart()`](node.md#noderestart)) is required before the change takes effect.
## adc.force_init_mode()
Checks and if necessary reconfigures the ADC mode setting in the ESP init data block.
####Syntax
`adc.force_init_mode(mode_value)`
####Parameters
`mode_value` One of `adc.INIT_ADC` or `adc.INIT_VDD33`.
####Returns
True if the function had to change the mode, false if the mode was already configured. On a true return the ESP needs to be restarted for the change to take effect.
####Example
```lua
-- in you init.lua:
if adc.force_init_mode(adc.INIT_VDD33)
then
node.restart()
return -- don't bother continuing, the restart is scheduled
end
print("System voltage (mV):", adc.readvdd33(0))
```
####See also
[`node.restart()`](node.md#noderestart)
## adc.read()
Samples the ADC.
####Syntax
`adc.read(channel)`
####Parameters
`channel` always 0 on the ESP8266
####Returns
the sampled value (number)
If the ESP8266 has been configured to use the ADC for reading the system voltage, this function will always return 65535. This is a hardware and/or SDK limitation.
####Example
```lua
val = adc.read(0)
```
## adc.readvdd33()
Reads the system voltage.
####Syntax
`adc.readvdd33()`
####Parameters
none
####Returns
system voltage in millivolts (number)
If the ESP8266 has been configured to use the ADC for sampling the external pin, this function will always return 65535. This is a hardware and/or SDK limitation.
# AM2320 Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2016-02-14 | [Henk Vergonet](https://github.com/hvegh) | [Henk Vergonet](https://github.com/hvegh) | [am2320.c](../../../app/modules/am2320.c)|
This module provides access to the [AM2320](https://akizukidenshi.com/download/ds/aosong/AM2320.pdf) humidity and temperature sensor, using the i2c interface.
## am2320.init()
Initializes the module and sets the pin configuration. Returns model, version, serial but is seams these where all zero on my model.
#### Syntax
`model, version, serial = am2320.init(sda, scl)`
#### Parameters
- `sda` data pin
- `scl` clock pin
#### Returns
- `model` 16 bits number of model
- `version` 8 bits version number
- `serial` 32 bits serial number
Note: I have only observed values of 0 for all of these, maybe other sensors return more sensible readings.
## am2320.read()
Samples the sensor and returns the relative humidity in % and temperature in celsius, as an integer multiplied with 10.
#### Syntax
`am2320.read()`
#### Returns
- `relative humidity` percentage multiplied with 10 (integer)
- `temperature` in celcius multiplied with 10 (integer)
#### Example
```lua
am2320.init(1, 2)
rh, t = am2320.read()
print(string.format("RH: %s%%", rh / 10))
print(string.format("Temperature: %s degrees C", t / 10))
```
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