Преглед на файлове

Merge pull request #1653 from nodemcu/dev-for-drop

New December master drop
Marcel Stör преди 7 години
родител
ревизия
81ec3665cb
променени са 67 файла, в които са добавени 3872 реда и са изтрити 1153 реда
  1. 21 3
      CONTRIBUTING.md
  2. 2 2
      Makefile
  3. 7 0
      app/driver/i2c_master.c
  4. 11 5
      app/driver/uart.c
  5. 38 36
      app/http/httpclient.c
  6. 13 6
      app/http/httpclient.h
  7. 1 1
      app/include/driver/uart.h
  8. 2 0
      app/include/rom.h
  9. 1 0
      app/include/sections.h
  10. 6 0
      app/include/u8g_config.h
  11. 5 2
      app/include/user_config.h
  12. 1 0
      app/include/user_modules.h
  13. 2 0
      app/libc/c_stdio.h
  14. 164 0
      app/libc/dbg_printf.c
  15. 1 1
      app/lua/lauxlib.c
  16. 8 17
      app/lua/liolib.c
  17. 2 2
      app/lua/luaconf.h
  18. 4 23
      app/modules/enduser_setup.c
  19. 315 0
      app/modules/eus/enduser_setup.html
  20. BIN
      app/modules/eus/enduser_setup.html.gz
  21. 203 0
      app/modules/eus/http_html_backup.def
  22. 7 0
      app/modules/eus/prepare.sh
  23. 172 60
      app/modules/file.c
  24. 15 5
      app/modules/gpio.c
  25. 3 3
      app/modules/http.c
  26. 47 47
      app/modules/mqtt.c
  27. 2 0
      app/modules/ow.c
  28. 1 1
      app/modules/sntp.c
  29. 248 0
      app/modules/somfy.c
  30. 69 224
      app/modules/u8g.c
  31. 347 0
      app/modules/u8g_glue.c
  32. 22 0
      app/modules/u8g_glue.h
  33. 58 6
      app/modules/websocket.c
  34. 494 303
      app/modules/wifi.c
  35. 10 1
      app/modules/wifi_common.h
  36. 216 40
      app/modules/ws2812.c
  37. 1 1
      app/net/nodemcu_mdns.c
  38. 2 1
      app/platform/vfs.c
  39. 1 1
      app/platform/vfs.h
  40. 26 8
      app/spiffs/spiffs.c
  41. 5 5
      app/spiffs/spiffs_config.h
  42. 46 13
      app/spiffs/spiffs_nucleus.c
  43. 1 1
      app/spiffs/spiffs_nucleus.h
  44. 0 3
      app/u8glib/u8g.h
  45. 1 1
      app/user/Makefile
  46. 63 19
      app/user/user_main.c
  47. 75 19
      app/websocket/websocketclient.c
  48. 7 1
      app/websocket/websocketclient.h
  49. 25 15
      docs/en/flash.md
  50. 1 1
      docs/en/index.md
  51. 23 4
      docs/en/modules/enduser-setup.md
  52. 196 108
      docs/en/modules/file.md
  53. 9 8
      docs/en/modules/gpio.md
  54. 40 31
      docs/en/modules/net.md
  55. 1 1
      docs/en/modules/ow.md
  56. 1 1
      docs/en/modules/rtctime.md
  57. 1 1
      docs/en/modules/sntp.md
  58. 45 0
      docs/en/modules/somfy.md
  59. 4 0
      docs/en/modules/spi.md
  60. 27 1
      docs/en/modules/u8g.md
  61. 21 4
      docs/en/modules/websocket.md
  62. 448 112
      docs/en/modules/wifi.md
  63. 131 3
      docs/en/modules/ws2812.md
  64. 145 0
      lua_examples/somfy.lua
  65. 6 0
      sdk-overrides/include/user_interface.h
  66. 1 1
      tools/pr-build.sh
  67. 1 1
      tools/spiffsimg/Makefile

+ 21 - 3
CONTRIBUTING.md

@@ -4,16 +4,22 @@
 
 The following is a set of guidelines for contributing to NodeMCU on GitHub. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request.
 
-It is appreciated but optional if you raise an issue _before_ you start changing NodeMCU, discussing the proposed change; emphasing that the you are proposing to develop the patch yourself, and outlining the strategy for implementation. This type of discussion is what we should be doing on the issues list and it is better to do this before or in parallel to developing the patch rather than having "you should have done it this way" type of feedback on the PR itself.
+It is appreciated if you raise an issue _before_ you start changing NodeMCU, discussing the proposed change; emphasing that the you are proposing to develop the patch yourself, and outlining the strategy for implementation. This type of discussion is what we should be doing on the issues list and it is better to do this before or in parallel to developing the patch rather than having "you should have done it this way" type of feedback on the PR itself.
 
 ### Table Of Contents
-
+* [General remarks](#general-remarks)
 * [Development environment setup](#development-environment-setup)
 * [Writing Documentation](#writing-documentation)
 * [Working with Git and GitHub](#working-with-git-and-github)
   * [General flow](#general-flow)
   * [Keeping your fork in sync](#keeping-your-fork-in-sync)
   * [Commit messages](#commit-messages)
+* [For collaborators](#for-collaborators)
+  * [Handling releases](#handling-release)
+  
+## General remarks
+We are a friendly and welcoming community and look forward to your contributions. Once your contribution is integrated into this repository we feel responsible for it. Therefore, be prepared for constructive feedback. Before we merge anything we need to ensure that it fits in and is consistent with the rest of NodeMCU.
+If you made something really cool but won't spend time to integrate it into this upstream project please still share it in your fork on GitHub. If you mention it in an issues we'll take a look at it anyway.
 
 ## Development environment setup
 Use the platform and tools you feel most comfortable with. There are  no constraints imposed by this project. You have (at least) two options to set up the toolchain to build the NodeMCU firmware:
@@ -52,7 +58,7 @@ Avoid intermediate merge commits. [Rebase](https://www.atlassian.com/git/tutoria
 1. `git checkout <branch-name>`
 1. Make changes to the code base and commit them using e.g. `git commit -a -m 'Look ma, I did it'`
 1. When you're done:
- 1. [Squash your commits](http://www.andrewconnell.com/blog/squash-multiple-git-commits-into-one). There are [several ways](http://stackoverflow.com/a/5201642/131929) of doing this.
+ 1. Think about [squashing (some of) your commits](http://www.andrewconnell.com/blog/squash-multiple-git-commits-into-one). There are [several ways](http://stackoverflow.com/a/5201642/131929) to do this. There's no need to squash everything into a single commit as GitHub offers to do this when we merge your changes. However, you might want to trim your commit history to relevant chunks.
  1. Bring your fork up-to-date with the NodeMCU upstream repo ([see below](#keeping-your-fork-in-sync)). Then rebase your branch on `dev` running `git rebase dev`.
 1. `git push`
 1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request/) (PR) on GitHub. 
@@ -90,3 +96,15 @@ Further paragraphs come after blank lines.
 Don't forget to [reference affected issues](https://help.github.com/articles/closing-issues-via-commit-messages/) in the commit message to have them closed automatically on GitHub.
 
 [Amend](https://help.github.com/articles/changing-a-commit-message/) your commit messages if necessary to make sure what the world sees on GitHub is as expressive and meaningful as possible.
+
+## For collaborators
+
+### Handling releases
+- Create a [milestone](https://github.com/nodemcu/nodemcu-firmware/milestones) right after you cut a new release. Give it a meaningful name if you already have an idea what the scope of the upcoming release is going to be. Also set the due date to ~2 months in the future.
+- Add this milestone to every PR before you merge it. Also add the milestone to PRs you want to see land in this milestone.
+- Add notes to the description of the milestone in the course of the ~2 months it lives.
+- Be careful and reluctant to merge PRs once we're past the 6-weeks mark of a milestone. Ideally we don't merge anything in the last 2 weeks.
+- Cutting a release
+ - Create an annotated tag like so: `git tag -a <SDK-version>-master_<yyyyMMdd> -m ""`, `git push --tags`
+ - Create a new [release](https://github.com/nodemcu/nodemcu-firmware/releases) based on the tag you just pushed. The version name is the same as the tag name.
+ - Write release notes. Mention breaking changes explicitly. Since every PR that went into this release is linked to from the milestone it should be fairly easy to include important changes in the release notes.

+ 2 - 2
Makefile

@@ -207,14 +207,14 @@ sdk_patched: sdk_extracted $(TOP_DIR)/sdk/.patched-$(SDK_VER)
 
 $(TOP_DIR)/sdk/.extracted-$(SDK_BASE_VER): $(TOP_DIR)/cache/esp_iot_sdk_v$(SDK_FILE_VER).zip
 	mkdir -p "$(dir $@)"
-	(cd "$(dir $@)" && rm -fr esp_iot_sdk_v$(SDK_VER) ESP8266_NONOS_SDK && unzip $(TOP_DIR)/cache/esp_iot_sdk_v$(SDK_FILE_VER).zip ESP8266_NONOS_SDK/lib/* ESP8266_NONOS_SDK/ld/eagle.rom.addr.v6.ld ESP8266_NONOS_SDK/include/* )
+	(cd "$(dir $@)" && rm -fr esp_iot_sdk_v$(SDK_VER) ESP8266_NONOS_SDK && unzip $(TOP_DIR)/cache/esp_iot_sdk_v$(SDK_FILE_VER).zip ESP8266_NONOS_SDK/lib/* ESP8266_NONOS_SDK/ld/eagle.rom.addr.v6.ld ESP8266_NONOS_SDK/include/* ESP8266_NONOS_SDK/bin/esp_init_data_default.bin)
 	mv $(dir $@)/ESP8266_NONOS_SDK $(dir $@)/esp_iot_sdk_v$(SDK_VER)
 	rm -f $(SDK_DIR)/lib/liblwip.a
 	touch $@
 
 $(TOP_DIR)/sdk/.patched-$(SDK_VER): $(TOP_DIR)/cache/esp_iot_sdk_v$(SDK_PATCH_VER).zip
 	mkdir -p "$(dir $@)/patch"
-	(cd "$(dir $@)/patch" && unzip $(TOP_DIR)/cache/esp_iot_sdk_v$(SDK_PATCH_VER)*.zip *.a && mv *.a $(SDK_DIR)/lib/)
+	(cd "$(dir $@)/patch" && unzip $(TOP_DIR)/cache/esp_iot_sdk_v$(SDK_PATCH_VER)*.zip *.a esp_init_data_default.bin && mv *.a $(SDK_DIR)/lib/ && mv esp_init_data_default.bin $(SDK_DIR)/bin/)
 	rmdir $(dir $@)/patch
 	rm -f $(SDK_DIR)/lib/liblwip.a
 	touch $@

+ 7 - 0
app/driver/i2c_master.c

@@ -33,6 +33,8 @@ LOCAL uint8 pinSCL = 15;
 LOCAL void ICACHE_FLASH_ATTR
 i2c_master_setDC(uint8 SDA, uint8 SCL)
 {
+    uint8 sclLevel;
+
     SDA	&= 0x01;
     SCL	&= 0x01;
     m_nLastSDA = SDA;
@@ -47,6 +49,11 @@ i2c_master_setDC(uint8 SDA, uint8 SCL)
     } else {
         I2C_MASTER_SDA_HIGH_SCL_HIGH();
     }
+    if(1 == SCL) {
+        do {
+            sclLevel = GPIO_INPUT_GET(GPIO_ID_PIN(I2C_MASTER_SCL_GPIO));
+        } while(sclLevel == 0);
+    }
 }
 
 /******************************************************************************

+ 11 - 5
app/driver/uart.c

@@ -30,6 +30,8 @@
 
 // For event signalling
 static task_handle_t sig = 0;
+static uint8 *sig_flag;
+static uint8 isr_flag = 0;
 
 // UartDev is defined and initialized in rom code.
 extern UartDevice UartDev;
@@ -277,8 +279,12 @@ uart0_rx_intr_handler(void *para)
         got_input = true;
     }
 
-    if (got_input && sig)
-      task_post_low (sig, false);
+    if (got_input && sig) {
+      if (isr_flag == *sig_flag) {
+        isr_flag ^= 0x01;
+        task_post_low (sig, 0x8000 | isr_flag << 14 | false);
+      }
+    }
 }
 
 static void 
@@ -316,21 +322,21 @@ uart_stop_autobaud()
  * Description  : user interface for init uart
  * Parameters   : UartBautRate uart0_br - uart0 bautrate
  *                UartBautRate uart1_br - uart1 bautrate
- *                uint8        task_prio - task priority to signal on input
  *                os_signal_t  sig_input - signal to post
+ *                uint8       *flag_input - flag of consumer task
  * Returns      : NONE
 *******************************************************************************/
 void ICACHE_FLASH_ATTR
-uart_init(UartBautRate uart0_br, UartBautRate uart1_br, os_signal_t sig_input)
+uart_init(UartBautRate uart0_br, UartBautRate uart1_br, os_signal_t sig_input, uint8 *flag_input)
 {
     sig = sig_input;
+    sig_flag = flag_input;
 
     // rom use 74880 baut_rate, here reinitialize
     UartDev.baut_rate = uart0_br;
     uart_config(UART0);
     UartDev.baut_rate = uart1_br;
     uart_config(UART1);
-    ETS_UART_INTR_ENABLE();
 #ifdef BIT_RATE_AUTOBAUD
     uart_init_autobaud(0);
 #endif

+ 38 - 36
app/http/httpclient.c

@@ -13,6 +13,7 @@
  */
 
 #include "osapi.h"
+#include "../libc/c_stdio.h"
 #include "user_interface.h"
 #include "espconn.h"
 #include "mem.h"
@@ -94,7 +95,7 @@ static int ICACHE_FLASH_ATTR http_chunked_decode( const char * chunked, char * d
 		char * endstr;
 		/* [chunk-size] */
 		i = strtoul( str + j, NULL, 16 );
-		HTTPCLIENT_DEBUG( "Chunk Size:%d\r\n", i );
+		HTTPCLIENT_DEBUG( "Chunk Size:%d", i );
 		if ( i <= 0 )
 			break;
 		/* [chunk-size-end-ptr] */
@@ -137,7 +138,7 @@ static void ICACHE_FLASH_ATTR http_receive_callback( void * arg, char * buf, uns
 	char		* new_buffer;
 	if ( new_size > BUFFER_SIZE_MAX || NULL == (new_buffer = (char *) os_malloc( new_size ) ) )
 	{
-		HTTPCLIENT_DEBUG( "Response too long (%d)\n", new_size );
+		HTTPCLIENT_ERR( "Response too long (%d)", new_size );
 		req->buffer[0] = '\0';                                                                  /* Discard the buffer to avoid using an incomplete response. */
 		if ( req->secure )
 			espconn_secure_disconnect( conn );
@@ -163,12 +164,12 @@ static void ICACHE_FLASH_ATTR http_send_callback( void * arg )
 
 	if ( req->post_data == NULL )
 	{
-		HTTPCLIENT_DEBUG( "All sent\n" );
+		HTTPCLIENT_DEBUG( "All sent" );
 	}
 	else  
 	{
 		/* The headers were sent, now send the contents. */
-		HTTPCLIENT_DEBUG( "Sending request body\n" );
+		HTTPCLIENT_DEBUG( "Sending request body" );
 		if ( req->secure )
 			espconn_secure_send( conn, (uint8_t *) req->post_data, strlen( req->post_data ) );
 		else
@@ -181,7 +182,7 @@ static void ICACHE_FLASH_ATTR http_send_callback( void * arg )
 
 static void ICACHE_FLASH_ATTR http_connect_callback( void * arg )
 {
-	HTTPCLIENT_DEBUG( "Connected\n" );
+	HTTPCLIENT_DEBUG( "Connected" );
 	struct espconn	* conn	= (struct espconn *) arg;
 	request_args_t	* req	= (request_args_t *) conn->reverse;
 	espconn_regist_recvcb( conn, http_receive_callback );
@@ -250,7 +251,7 @@ static void ICACHE_FLASH_ATTR http_connect_callback( void * arg )
     }
 
     req->headers = NULL;
-    HTTPCLIENT_DEBUG( "Sending request header\n" );
+    HTTPCLIENT_DEBUG( "Sending request header" );
 }
 
 static void http_free_req( request_args_t * req)
@@ -272,7 +273,7 @@ static void http_free_req( request_args_t * req)
 
 static void ICACHE_FLASH_ATTR http_disconnect_callback( void * arg )
 {
-	HTTPCLIENT_DEBUG( "Disconnected\n" );
+	HTTPCLIENT_DEBUG( "Disconnected" );
 	struct espconn *conn = (struct espconn *) arg;
 
 	if ( conn == NULL )
@@ -295,7 +296,7 @@ static void ICACHE_FLASH_ATTR http_disconnect_callback( void * arg )
 
 		if ( req->buffer == NULL )
 		{
-			HTTPCLIENT_DEBUG( "Buffer probably shouldn't be NULL\n" );
+			HTTPCLIENT_DEBUG( "Buffer probably shouldn't be NULL" );
 		}
 		else if ( req->buffer[0] != '\0' )
 		{
@@ -305,7 +306,7 @@ static void ICACHE_FLASH_ATTR http_disconnect_callback( void * arg )
 			if (( os_strncmp( req->buffer, version_1_0, strlen( version_1_0 ) ) != 0 ) &&
 				( os_strncmp( req->buffer, version_1_1, strlen( version_1_1 ) ) != 0 ))
 			{
-				HTTPCLIENT_DEBUG( "Invalid version in %s\n", req->buffer );
+				HTTPCLIENT_ERR( "Invalid version in %s", req->buffer );
 			}
 			else  
 			{
@@ -326,7 +327,7 @@ static void ICACHE_FLASH_ATTR http_disconnect_callback( void * arg )
 
 						char *locationOffsetEnd = (char *) os_strstr(locationOffset, "\r\n");
 						if ( locationOffsetEnd == NULL ) {
-							HTTPCLIENT_DEBUG( "Found Location header but was incomplete\n" );
+							HTTPCLIENT_ERR( "Found Location header but was incomplete" );
 							http_status = -1;
 						} else {
 							*locationOffsetEnd = '\0';
@@ -371,7 +372,7 @@ static void ICACHE_FLASH_ATTR http_disconnect_callback( void * arg )
 							return;
 						}
 					} else {
-						HTTPCLIENT_DEBUG("Too many redirections\n");
+						HTTPCLIENT_ERR("Too many redirections");
 						http_status = -1;
 					}
 				} else {
@@ -379,7 +380,7 @@ static void ICACHE_FLASH_ATTR http_disconnect_callback( void * arg )
 
 					if (NULL == body) {
 						  /* Find missing body */
-						  HTTPCLIENT_DEBUG("Body shouldn't be NULL\n");
+						  HTTPCLIENT_ERR("Body shouldn't be NULL");
 						  /* To avoid NULL body */
 						  body = "";
 					} else {
@@ -411,16 +412,9 @@ static void ICACHE_FLASH_ATTR http_disconnect_callback( void * arg )
 }
 
 
-static void ICACHE_FLASH_ATTR http_error_callback( void *arg, sint8 errType )
-{
-	HTTPCLIENT_DEBUG( "Disconnected with error\n" );
-	http_disconnect_callback( arg );
-}
-
-
 static void ICACHE_FLASH_ATTR http_timeout_callback( void *arg )
 {
-	HTTPCLIENT_DEBUG( "Connection timeout\n" );
+	HTTPCLIENT_ERR( "Connection timeout" );
 	struct espconn * conn = (struct espconn *) arg;
 	if ( conn == NULL )
 	{
@@ -439,13 +433,20 @@ static void ICACHE_FLASH_ATTR http_timeout_callback( void *arg )
 }
 
 
+static void ICACHE_FLASH_ATTR http_error_callback( void *arg, sint8 errType )
+{
+	HTTPCLIENT_ERR( "Disconnected with error: %d", errType );
+	http_timeout_callback( arg );
+}
+
+
 static void ICACHE_FLASH_ATTR http_dns_callback( const char * hostname, ip_addr_t * addr, void * arg )
 {
 	request_args_t * req = (request_args_t *) arg;
 
 	if ( addr == NULL )
 	{
-		HTTPCLIENT_DEBUG( "DNS failed for %s\n", hostname );
+		HTTPCLIENT_ERR( "DNS failed for %s", hostname );
 		if ( req->callback_handle != NULL )
 		{
 			req->callback_handle( "", -1, "" );
@@ -454,7 +455,7 @@ static void ICACHE_FLASH_ATTR http_dns_callback( const char * hostname, ip_addr_
 	}
 	else  
 	{
-		HTTPCLIENT_DEBUG( "DNS found %s " IPSTR "\n", hostname, IP2STR( addr ) );
+		HTTPCLIENT_DEBUG( "DNS found %s " IPSTR, hostname, IP2STR( addr ) );
 
 		struct espconn * conn = (struct espconn *) os_zalloc( sizeof(struct espconn) );
 		conn->type			= ESPCONN_TCP;
@@ -489,7 +490,7 @@ static void ICACHE_FLASH_ATTR http_dns_callback( const char * hostname, ip_addr_
 
 void ICACHE_FLASH_ATTR http_raw_request( const char * hostname, int port, bool secure, const char * method, const char * path, const char * headers, const char * post_data, http_callback_t callback_handle, int redirect_follow_count )
 {
-	HTTPCLIENT_DEBUG( "DNS request\n" );
+	HTTPCLIENT_DEBUG( "DNS request" );
 
 	request_args_t * req = (request_args_t *) os_zalloc( sizeof(request_args_t) );
 	req->hostname		= esp_strdup( hostname );
@@ -512,7 +513,7 @@ void ICACHE_FLASH_ATTR http_raw_request( const char * hostname, int port, bool s
 
 	if ( error == ESPCONN_INPROGRESS )
 	{
-		HTTPCLIENT_DEBUG( "DNS pending\n" );
+		HTTPCLIENT_DEBUG( "DNS pending" );
 	}
 	else if ( error == ESPCONN_OK )
 	{
@@ -523,9 +524,9 @@ void ICACHE_FLASH_ATTR http_raw_request( const char * hostname, int port, bool s
 	{
 		if ( error == ESPCONN_ARG )
 		{
-			HTTPCLIENT_DEBUG( "DNS arg error %s\n", hostname );
+			HTTPCLIENT_ERR( "DNS arg error %s", hostname );
 		}else  {
-			HTTPCLIENT_DEBUG( "DNS error code %d\n", error );
+			HTTPCLIENT_ERR( "DNS error code %d", error );
 		}
 		http_dns_callback( hostname, NULL, req ); /* Handle all DNS errors the same way. */
 	}
@@ -561,7 +562,7 @@ void ICACHE_FLASH_ATTR http_request( const char * url, const char * method, cons
 	} 
 	else 
 	{
-		HTTPCLIENT_DEBUG( "URL is not HTTP or HTTPS %s\n", url );
+		HTTPCLIENT_ERR( "URL is not HTTP or HTTPS %s", url );
 		return;
 	}
 
@@ -578,7 +579,7 @@ void ICACHE_FLASH_ATTR http_request( const char * url, const char * method, cons
 	}
 
 	if (path - url >= sizeof(hostname)) {
-		HTTPCLIENT_DEBUG( "hostname is too long %s\n", url );
+		HTTPCLIENT_ERR( "hostname is too long %s", url );
 		return;
 	}
 
@@ -592,7 +593,7 @@ void ICACHE_FLASH_ATTR http_request( const char * url, const char * method, cons
 		port = atoi( colon + 1 );
 		if ( port == 0 )
 		{
-			HTTPCLIENT_DEBUG( "Port error %s\n", url );
+			HTTPCLIENT_ERR( "Port error %s", url );
 			return;
 		}
 
@@ -606,10 +607,10 @@ void ICACHE_FLASH_ATTR http_request( const char * url, const char * method, cons
 		path = "/";
 	}
 
-	HTTPCLIENT_DEBUG( "hostname=%s\n", hostname );
-	HTTPCLIENT_DEBUG( "port=%d\n", port );
-	HTTPCLIENT_DEBUG( "method=%s\n", method );
-	HTTPCLIENT_DEBUG( "path=%s\n", path );
+	HTTPCLIENT_DEBUG( "hostname=%s", hostname );
+	HTTPCLIENT_DEBUG( "port=%d", port );
+	HTTPCLIENT_DEBUG( "method=%s", method );
+	HTTPCLIENT_DEBUG( "path=%s", path );
 	http_raw_request( hostname, port, secure, method, path, headers, post_data, callback_handle, redirect_follow_count);
 }
 
@@ -645,10 +646,11 @@ void ICACHE_FLASH_ATTR http_put( const char * url, const char * headers, const c
 
 void ICACHE_FLASH_ATTR http_callback_example( char * response, int http_status, char * full_response )
 {
-	os_printf( "http_status=%d\n", http_status );
+	dbg_printf( "http_status=%d\n", http_status );
 	if ( http_status != HTTP_STATUS_GENERIC_ERROR )
 	{
-		os_printf( "strlen(full_response)=%d\n", strlen( full_response ) );
-		os_printf( "response=%s<EOF>\n", response );
+		dbg_printf( "strlen(full_response)=%d\n", strlen( full_response ) );
+		dbg_printf( "response=%s<EOF>\n", response );
 	}
 }
+

+ 13 - 6
app/http/httpclient.h

@@ -10,18 +10,25 @@
 #ifndef __HTTPCLIENT_H__
 #define __HTTPCLIENT_H__
 
-#if defined(GLOBAL_DEBUG_ON)
-#define HTTPCLIENT_DEBUG_ON
+static const char log_prefix[] = "HTTP client: ";
+
+#if defined(DEVELOP_VERSION)
+  #define HTTPCLIENT_DEBUG_ON
 #endif
 #if defined(HTTPCLIENT_DEBUG_ON)
-#define HTTPCLIENT_DEBUG(format, ...) os_printf(format, ##__VA_ARGS__)
+  #define HTTPCLIENT_DEBUG(format, ...) dbg_printf("%s"format"\n", log_prefix, ##__VA_ARGS__)
+#else
+  #define HTTPCLIENT_DEBUG(...)
+#endif
+#if defined(NODE_ERROR)
+  #define HTTPCLIENT_ERR(format, ...) NODE_ERR("%s"format"\n", log_prefix, ##__VA_ARGS__)
 #else
-#define HTTPCLIENT_DEBUG(format, ...)
+  #define HTTPCLIENT_ERR(...)
 #endif
 
 #if defined(USES_SDK_BEFORE_V140)
-#define espconn_send espconn_sent
-#define espconn_secure_send espconn_secure_sent
+  #define espconn_send espconn_sent
+  #define espconn_secure_send espconn_secure_sent
 #endif
 
 /*

+ 1 - 1
app/include/driver/uart.h

@@ -101,7 +101,7 @@ typedef struct {
     int                      buff_uart_no;  //indicate which uart use tx/rx buffer
 } UartDevice;
 
-void uart_init(UartBautRate uart0_br, UartBautRate uart1_br, os_signal_t sig_input);
+void uart_init(UartBautRate uart0_br, UartBautRate uart1_br, os_signal_t sig_input, uint8 *flag_input);
 void uart0_alt(uint8 on);
 void uart0_sendStr(const char *str);
 void uart0_putc(const char c);

+ 2 - 0
app/include/rom.h

@@ -149,5 +149,7 @@ void uart_div_modify(int no, unsigned int freq);
 
 /* Returns 0 on success, 1 on failure */
 uint8_t SPIRead(uint32_t src_addr, uint32_t *des_addr, uint32_t size);
+uint8_t SPIWrite(uint32_t dst_addr, const uint32_t *src, uint32_t size);
+uint8_t SPIEraseSector(uint32_t sector);
 
 #endif

+ 1 - 0
app/include/sections.h

@@ -2,5 +2,6 @@
 #define _SECTIONS_H_
 
 #define TEXT_SECTION_ATTR __attribute__((section(".text")))
+#define RAM_CONST_SECTION_ATTR __attribute((section(".data")))
 
 #endif

+ 6 - 0
app/include/u8g_config.h

@@ -24,6 +24,7 @@
 //
 // I2C based displays go into here:
 //    U8G_DISPLAY_TABLE_ENTRY(sh1106_128x64_i2c)          \
+//    U8G_DISPLAY_TABLE_ENTRY(ssd1306_128x32_i2c)         \
 //    U8G_DISPLAY_TABLE_ENTRY(ssd1306_128x64_i2c)         \
 //    U8G_DISPLAY_TABLE_ENTRY(ssd1306_64x48_i2c)          \
 //    U8G_DISPLAY_TABLE_ENTRY(ssd1309_128x64_i2c)         \
@@ -40,6 +41,7 @@
 //    U8G_DISPLAY_TABLE_ENTRY(pcd8544_84x48_hw_spi)               \
 //    U8G_DISPLAY_TABLE_ENTRY(pcf8812_96x65_hw_spi)               \
 //    U8G_DISPLAY_TABLE_ENTRY(sh1106_128x64_hw_spi)               \
+//    U8G_DISPLAY_TABLE_ENTRY(ssd1306_128x32_hw_spi)              \
 //    U8G_DISPLAY_TABLE_ENTRY(ssd1306_128x64_hw_spi)              \
 //    U8G_DISPLAY_TABLE_ENTRY(ssd1306_64x48_hw_spi)               \
 //    U8G_DISPLAY_TABLE_ENTRY(ssd1309_128x64_hw_spi)              \
@@ -75,6 +77,10 @@
     U8G_DISPLAY_TABLE_ENTRY(ssd1306_128x64_hw_spi)              \
 
 #undef U8G_DISPLAY_TABLE_ENTRY
+
+// Special display device to provide run-length encoded framebuffer contents
+// to a Lua callback:
+//#define U8G_DISPLAY_FB_RLE
 //
 // ***************************************************************************
 

+ 5 - 2
app/include/user_config.h

@@ -36,13 +36,13 @@ extern void luaL_assertfail(const char *file, int line, const char *message);
 #define NODE_ERROR
 
 #ifdef NODE_DEBUG
-#define NODE_DBG c_printf
+#define NODE_DBG dbg_printf
 #else
 #define NODE_DBG
 #endif	/* NODE_DEBUG */
 
 #ifdef NODE_ERROR
-#define NODE_ERR c_printf
+#define NODE_ERR dbg_printf
 #else
 #define NODE_ERR
 #endif	/* NODE_ERROR */
@@ -76,6 +76,9 @@ extern void luaL_assertfail(const char *file, int line, const char *message);
 // maximum length of a filename
 #define FS_OBJ_NAME_LEN 31
 
+// maximum number of open files for SPIFFS
+#define SPIFFS_MAX_OPEN_FILES 4
+
 // Uncomment this next line for fastest startup 
 // It reduces the format time dramatically
 // #define SPIFFS_MAX_FILESYSTEM_SIZE	32768

+ 1 - 0
app/include/user_modules.h

@@ -53,6 +53,7 @@
 //#define LUA_USE_MODULES_RTCTIME
 //#define LUA_USE_MODULES_SIGMA_DELTA
 //#define LUA_USE_MODULES_SNTP
+//#define LUA_USE_MODULES_SOMFY
 #define LUA_USE_MODULES_SPI
 //#define LUA_USE_MODULES_STRUCT
 //#define LUA_USE_MODULES_SWITEC

+ 2 - 0
app/libc/c_stdio.h

@@ -63,6 +63,8 @@ extern void output_redirect(const char *str);
 int c_sprintf(char* s,const char *fmt, ...);
 #endif
 
+extern void dbg_printf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
+
 #define c_vsprintf ets_vsprintf
 #define c_printf(...) do {					\
 	unsigned char __print_buf[BUFSIZ];		\

+ 164 - 0
app/libc/dbg_printf.c

@@ -0,0 +1,164 @@
+/*	$NetBSD: printf.c,v 1.12 1997/06/26 19:11:48 drochner Exp $	*/
+
+/*-
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)printf.c	8.1 (Berkeley) 6/11/93
+ */
+
+// This version uses almost no stack, and does not suffer from buffer 
+// overflows. The downside is that it does not implement a wide range
+// of formatting characters.
+
+#include <c_stdlib.h>
+#include <c_types.h>
+#include <c_stdarg.h>
+#include "driver/uart.h"
+
+static void kprintn (void (*)(const char), uint32_t, int, int, char);
+static void kdoprnt (void (*)(const char), const char *, va_list);
+
+void
+dbg_printf(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	kdoprnt(uart0_putc, fmt, ap);
+	va_end(ap);
+}
+
+void
+dbg_vprintf(const char *fmt, va_list ap)
+{
+	kdoprnt(uart0_putc, fmt, ap);
+}
+
+void
+kdoprnt(void (*put)(const char), const char *fmt, va_list ap)
+{
+	register char *p;
+	register int ch, n;
+	unsigned long ul;
+	int lflag, set;
+        char zwidth;
+        char width;
+
+	for (;;) {
+		while ((ch = *fmt++) != '%') {
+			if (ch == '\0')
+				return;
+			put(ch);
+		}
+		lflag = 0;
+                width = 0;
+                zwidth = ' ';
+reswitch:	switch (ch = *fmt++) {
+		case '\0':
+			/* XXX print the last format character? */
+			return;
+		case 'l':
+			lflag = 1;
+			goto reswitch;
+		case 'c':
+			ch = va_arg(ap, int);
+				put(ch & 0x7f);
+			break;
+		case 's':
+			p = va_arg(ap, char *);
+                        if (p == 0) {
+                          p = "<null>";
+                        }
+			while ((ch = *p++))
+				put(ch);
+			break;
+		case 'd':
+			ul = lflag ?
+			    va_arg(ap, long) : va_arg(ap, int);
+			if ((long)ul < 0) {
+				put('-');
+				ul = -(long)ul;
+			}
+			kprintn(put, ul, 10, width, zwidth);
+			break;
+		case 'o':
+			ul = lflag ?
+			    va_arg(ap, uint32_t) : va_arg(ap, uint32_t);
+			kprintn(put, ul, 8, width, zwidth);
+			break;
+		case 'u':
+			ul = lflag ?
+			    va_arg(ap, uint32_t) : va_arg(ap, uint32_t);
+			kprintn(put, ul, 10, width, zwidth);
+			break;
+		case 'x':
+			ul = lflag ?
+			    va_arg(ap, uint32_t) : va_arg(ap, uint32_t);
+			kprintn(put, ul, 16, width, zwidth);
+			break;
+		default:
+                        if (ch >= '0' && ch <= '9') {
+                          if (ch == '0' && width == 0 && zwidth == ' ') {
+                            zwidth = '0';
+                          } else {
+                            width = width * 10 + ch - '0';
+                          }
+                          goto reswitch;
+                        }
+			put('%');
+			if (lflag)
+				put('l');
+			put(ch);
+		}
+	}
+	va_end(ap);
+}
+
+static void
+kprintn(void (*put)(const char), unsigned long ul, int base, int width, char padchar)
+{
+					/* hold a long in base 8 */
+	char *p, buf[(sizeof(long) * 8 / 3) + 2];
+
+	p = buf;
+	do {
+		*p++ = "0123456789abcdef"[ul % base];
+	} while (ul /= base);
+        
+        while (p - buf < width--) {
+          put(padchar);
+        }
+
+	do {
+		put(*--p);
+	} while (p > buf);
+}

+ 1 - 1
app/lua/lauxlib.c

@@ -807,7 +807,7 @@ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
 }
 
 LUALIB_API void luaL_assertfail(const char *file, int line, const char *message) {
-  c_printf("ASSERT@%s(%d): %s\n", file, line, message); 
+  dbg_printf("ASSERT@%s(%d): %s\n", file, line, message); 
 }
 
 static int panic (lua_State *L) {

+ 8 - 17
app/lua/liolib.c

@@ -343,28 +343,19 @@ static int read_line (lua_State *L, int f) {
 static int read_line (lua_State *L, int f) {
   luaL_Buffer b;
   luaL_buffinit(L, &b);
-  char *p = luaL_prepbuffer(&b);
-  signed char c = EOF;
-  int i = 0;
-  do{
+  signed char c;
+  do {
     c = (signed char)vfs_getc(f);
-    if(c==EOF){
+    if (c==EOF) {
       break;
     }
-    p[i++] = c;
-  }while((c!=EOF) && (c!='\n') && (i<LUAL_BUFFERSIZE) );
-
-  if(i>0 && p[i-1] == '\n')
-    i--;    /* do not include `eol' */
-
-  if(i==0){
-    luaL_pushresult(&b);  /* close buffer */
-    return (lua_objlen(L, -1) > 0);  /* check whether read something */
-  }
+    if (c != '\n') {
+      luaL_addchar(&b, c);
+    }
+  } while (c != '\n');
 
-  luaL_addsize(&b, i);
   luaL_pushresult(&b);  /* close buffer */
-  return 1;  /* read at least an `eol' */ 
+  return (lua_objlen(L, -1) > 0);  /* check whether read something */
 }
 #endif
 

+ 2 - 2
app/lua/luaconf.h

@@ -346,7 +346,7 @@ extern int readline4lua(const char *prompt, char *buffer, int length);
 ** (A format string with one argument is enough for Lua...)
 */
 #if !defined(LUA_USE_STDIO)
-#define luai_writestringerror(s,p)	c_printf((s), (p))
+#define luai_writestringerror(s,p)	dbg_printf((s), (p))
 #endif // defined(LUA_USE_STDIO)
 
 
@@ -556,7 +556,7 @@ extern int readline4lua(const char *prompt, char *buffer, int length);
 ** For example: If set to 4K a call to string.gsub will need more than 
 ** 5k C stack space.
 */
-#define LUAL_BUFFERSIZE		BUFSIZ
+#define LUAL_BUFFERSIZE		256
 
 /* }================================================================== */
 

Файловите разлики са ограничени, защото са твърде много
+ 4 - 23
app/modules/enduser_setup.c


+ 315 - 0
app/modules/eus/enduser_setup.html

@@ -0,0 +1,315 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+	<meta name='viewport' content='width=device-width, initial-scale=1.0'>
+	<title>WiFi Login</title>
+	<style type=text/css>
+		* {
+			margin: 0;
+			padding: 0;
+		}
+		
+		html,
+		body {
+			height: 100%;
+			font-family: sans-serif;
+			text-align: center;
+			background: #444d44;
+		}
+		
+		#content {
+			position: absolute;
+			top: 0;
+			right: 0;
+			bottom: 0;
+			left: 0;
+			width: 320px;
+			height: 480px;
+			margin: auto;
+		}
+		
+		input,
+		button,
+		select {
+			-webkit-appearance: none;
+			border-radius: 0;
+		}
+		
+		fieldset {
+			border: 0;
+			box-shadow: 0 0 15px 1px rgba(0, 0, 0, .4);
+			box-sizing: border-box;
+			padding: 20px 30px;
+			background: #fff;
+			min-height: 320px;
+			margin: -1px;
+		}
+		
+		input {
+			border: 1px solid #ccc;
+			margin-bottom: 10px;
+			width: 100%;
+			box-sizing: border-box;
+			color: #222;
+			font: 16px monospace;
+			padding: 15px;
+		}
+		
+		select {
+			font: 16px monospace;
+			background-color: transparent;
+			padding: 15px;
+		}
+		
+		button {
+			color: #fff;
+			border: 0;
+			border-radius: 3px;
+			cursor: pointer;
+			display: block;
+			font: 16px sans-serif;
+			text-decoration: none;
+			padding: 10px 5px;
+			background: #31b457;
+			width: 100%;
+		}
+		
+		button:focus,
+		button:hover {
+			box-shadow: 0 0 0 2px #fff, 0 0 0 3px #31b457;
+		}
+		
+		h3 {
+			font-size: 16px;
+			color: #666;
+			margin-bottom: 20px;
+		}
+		
+		h4 {
+			color: #ccc;
+			padding: 10px;
+		}
+		
+		.utility {
+			float: right;
+			clear: both;
+			max-width: 75%;
+			font-size: 13px;
+			color: #222;
+			margin: 10px 0;
+			padding: 5px 10px;
+			background: #ccc;
+		}
+		
+		.utility:focus,
+		.utility:hover {
+			box-shadow: 0 0 0 2px #fff, 0 0 0 3px #ccc;
+		}
+		
+		#dropdown,
+		#f2,
+		#f3,
+		#bk2 {
+			display: none;
+		}
+		
+		#dropdown {
+			position: relative;
+			width: 100%;
+			overflow: auto;
+			height: 51px;
+			margin-bottom: 10px;
+		}
+		
+		#aplist {
+			position: absolute;
+			width: 100%;
+			height: 100%;
+			top: 0;
+			left: 0;
+			bottom: 0;
+			border: 1px solid #ccc;
+			font-family: monospace;
+			padding: 10px 5px;
+		}
+		
+		#arrow {
+			color: #888;
+			position: absolute;
+			right: 8px;
+			top: 15px;
+		}
+		
+		#i {
+			text-align: justify;
+		}
+	</style>
+</head>
+
+<body>
+	<div id=content>
+		<fieldset>
+			<div id=deviceId></div>
+			<div id=f1>
+				<h3>Connect device to your Wi-Fi</h3>
+				<button id=networks type=button class=utility></button>
+				<div id=dropdown>
+					<span id=arrow>&#x25bc;</span>
+					<select id=aplist name=aplist></select>
+				</div>
+				<input id=ssid type=text autocorrect=off autocapitalize=none placeholder='Wi-Fi Name' />
+				<input id=wifi_password type=text autocorrect=off autocapitalize=none autocomplete=off placeholder=Password />
+				<button id=submit type=button>Save</button>
+			</div>
+			<div id=f2>
+				<h1>Success!</h1>
+				<div id=i>
+					<h3>Your device has successfully connected to the Wi-Fi network.</h3>
+				</div>
+			</div>
+			<div id=f3>
+				<h2>Trying...</h2>
+				<button id=bk2 type=button class='utility'>Go Back to Wi-Fi Setup</button>
+			</div>
+		</fieldset>
+		<h4 id='st'>Updating Status...</h4>
+	</div>
+	<script>
+        var $ = function (selector) { return document.querySelector(selector); };
+        var ab = $('#networks'), ap = $('#aplist');
+        var stopAll = false, ra, rs;
+
+        function show(f, y) {
+            if (y == null) y = f;
+            $(f).style.display = y == f ? 'block' : 'none';
+        }
+        function hide(f) {
+            $(f).style.display = 'none';
+        }
+        function to(cb, x) {
+            return setTimeout(cb, 1000 * x);
+        }
+        function refr() {
+            if (!stopAll)
+                fetch('/status.json?n=' + Math.random(), 'GET', newSt, 2);
+        }
+        function cur(f) {
+            show('#f1', f);
+            show('#f2', f);
+            show('#f3', f);
+        }
+        function newSt(s, d) {
+            clearTimeout(rs);
+            rs = to(refr, 3);
+
+            if (s != 200) {
+                $('#st').innerText = 'Awaiting Status (' + s + ')';
+            } else {
+                if (typeof d === 'string') {
+                    d = JSON.parse(d);
+                }
+
+                $('#deviceId').innerText = d.deviceid;
+
+                var c = d.pairing;
+
+                var s = [
+                    'Idle',
+                    'Connecting...',
+                    'Failed - wrong password',
+                    'Failed - network not found',
+                    'Failed',
+                    'Wi-Fi successfully connected!'
+                ][d.status];
+
+                $('#st').innerText = s;
+
+                if (d.status === 5) {
+                    cur('#f2');
+                    stopAll = true;
+                    clearTimeout(ra);
+                } else if (d.status > 1) {
+                    cur('#f1');
+                }
+            }
+        }
+        function submit() {
+            var url = '/setwifi?wifi_ssid=' + encodeURIComponent($('#ssid').value) + '&wifi_password=' + encodeURIComponent($('#wifi_password').value);
+            clearTimeout(rs);
+            fetch(url, 'GET', newSt, 2);
+            cur('#f3');
+        }
+        function fetch(url, method, callback, time_out) {
+            var xhr = new XMLHttpRequest();
+            xhr.onloadend = function () {
+                callback(xhr.status, xhr.responseText);
+            }
+            xhr.ontimeout = function () {
+                callback(-1, null);
+            }
+            xhr.open(method, url, true);
+            xhr.setRequestHeader('Accept', 'application/json');
+            xhr.timeout = (time_out || 10) * 1000;
+            xhr.send();
+        }
+        function gotAp(s, json) {
+            var list;
+            if (s === 200 && json != null) {
+                if (typeof json === 'string' && json.length > 0) {
+                    list = JSON.parse(json);
+                } else if (typeof json === 'object') {
+                    list = json;
+                }
+
+                list.sort(function (a, b) {
+                    return b.rssi - a.rssi;
+                });
+                var ops = '<option>Select a Network...</option>';
+                for (var i = 0; i < list.length; ++i) {
+                    ops += '<option>' + list[i].ssid + '</option>';
+                }
+                ap.innerHTML = ops;
+                ab.disabled = false;
+                togAp(null, true);
+                ab.onclick = togAp;
+            } else {
+                ab.innerText = 'No networks found (' + s + ')';
+                ra = to(refrAp, 5);
+            }
+        }
+        function togAp(ev, force) {
+            if (!force || ap.style.display == 'block') {
+                hide('#dropdown');
+                show('#ssid');
+                ab.innerText = 'Scan for Networks';
+                ab.onclick = refrAp;
+            } else {
+                show('#dropdown');
+                hide('#ssid');
+                ab.innerText = 'Manual Entry';
+            }
+        }
+        function refrAp() {
+            ab.innerText = 'Searching for networks...';
+            ab.disabled = true;
+            ap.innerHTML = '<option disabled>Scanning...</option>';
+            if (!stopAll)
+                fetch('/aplist?n=' + Math.random(), 'GET', gotAp, 10);
+        }
+        window.onload = function() {
+          ab.innerText = 'Scan for Networks';
+          ab.onclick = refrAp;
+          $('#aplist').onchange = function () {
+              $('#ssid').value = $('#aplist').value;
+          };
+          $('#submit').onclick = submit;
+          $('#bk2').onclick = function () {
+              cur('#f1')
+          }
+          rs = to(refr, 0.5);
+        }
+  </script>
+</body>
+
+</html>

BIN
app/modules/eus/enduser_setup.html.gz


+ 203 - 0
app/modules/eus/http_html_backup.def

@@ -0,0 +1,203 @@
+static const char http_html_backup[] = {
+  0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x95, 0x59,
+  0xfd, 0x72, 0xdb, 0x36, 0x12, 0xff, 0xbb, 0x37, 0x73, 0xef, 0xb0, 0x1e,
+  0xf7, 0x42, 0xb9, 0xa1, 0x64, 0x7d, 0xd8, 0x69, 0xc6, 0x12, 0xd5, 0xc9,
+  0xe5, 0x92, 0x26, 0x37, 0x49, 0xda, 0xa9, 0xdd, 0xe9, 0x75, 0x32, 0x99,
+  0x0e, 0x44, 0x82, 0x26, 0x62, 0x0a, 0xe0, 0x01, 0xa0, 0x65, 0x5d, 0xea,
+  0x77, 0x3f, 0x2c, 0x01, 0x5a, 0x86, 0x40, 0x87, 0xb5, 0x5a, 0x57, 0x04,
+  0xb0, 0xdf, 0xfb, 0xc3, 0xee, 0x52, 0x5d, 0x1c, 0xfc, 0xeb, 0xa7, 0x97,
+  0x17, 0xbf, 0xff, 0xfc, 0x0a, 0x0a, 0xbd, 0x2e, 0x97, 0x7f, 0xff, 0xdb,
+  0xc2, 0x7d, 0xe3, 0x13, 0x25, 0x99, 0x79, 0xfa, 0x66, 0xb1, 0xa6, 0x9a,
+  0x00, 0x27, 0x6b, 0x9a, 0x44, 0xd7, 0x8c, 0x6e, 0x2a, 0x21, 0x75, 0x04,
+  0xa9, 0xe0, 0x9a, 0x72, 0x9d, 0x44, 0x1b, 0x96, 0xe9, 0x22, 0xc9, 0xe8,
+  0x35, 0x4b, 0xe9, 0xb0, 0x59, 0xc4, 0xc0, 0x38, 0xd3, 0x8c, 0x94, 0x43,
+  0x95, 0x92, 0x92, 0x26, 0x93, 0xd1, 0x38, 0x6a, 0x04, 0x69, 0xa6, 0x4b,
+  0xba, 0xfc, 0x8d, 0xbd, 0x66, 0xf0, 0x4e, 0x5c, 0x32, 0xbe, 0x38, 0xb6,
+  0x3b, 0x78, 0xa6, 0xf4, 0xb6, 0xa4, 0xa0, 0xb7, 0x15, 0x4d, 0x34, 0xbd,
+  0xd1, 0xc7, 0xa9, 0x52, 0xb8, 0xff, 0xcd, 0x77, 0xf0, 0x05, 0xbf, 0xbe,
+  0x59, 0x13, 0x69, 0x38, 0xce, 0x60, 0x3c, 0x6f, 0x96, 0x15, 0xc9, 0x32,
+  0xc6, 0x2f, 0xdb, 0xf5, 0x2d, 0xfe, 0x07, 0xff, 0xd0, 0xfc, 0x18, 0x1f,
+  0x56, 0x22, 0xdb, 0x3a, 0xd6, 0x82, 0xb2, 0xcb, 0x42, 0x9f, 0xc1, 0x64,
+  0x3c, 0xfe, 0x87, 0xe5, 0xce, 0x8d, 0xf1, 0xc3, 0x9c, 0xac, 0x59, 0xb9,
+  0x3d, 0x03, 0x45, 0xb8, 0x1a, 0x2a, 0x2a, 0x59, 0x6e, 0x0f, 0x51, 0xfd,
+  0x90, 0x94, 0xec, 0xd2, 0x68, 0x4b, 0x29, 0xd7, 0x54, 0xda, 0xfd, 0x15,
+  0x49, 0xaf, 0x2e, 0xa5, 0xa8, 0x79, 0x76, 0x06, 0x87, 0x27, 0x27, 0x27,
+  0xd9, 0xc9, 0x89, 0xaf, 0xfb, 0xd0, 0xc5, 0xc4, 0xa9, 0xad, 0x84, 0x62,
+  0x9a, 0x09, 0x23, 0x85, 0xac, 0x94, 0x28, 0x6b, 0x4d, 0x9d, 0x7c, 0x51,
+  0xdd, 0xb9, 0x21, 0xad, 0x65, 0x6e, 0xb5, 0x12, 0x5a, 0x8b, 0xf5, 0xdd,
+  0xb2, 0xa4, 0xf9, 0xee, 0xac, 0x89, 0xec, 0x19, 0xcc, 0xa6, 0xe3, 0xea,
+  0x66, 0xee, 0xb9, 0x75, 0xf2, 0x1c, 0xb7, 0xbc, 0x20, 0x91, 0x5a, 0x0b,
+  0xdf, 0x36, 0xc6, 0xab, 0x5a, 0xdb, 0xc0, 0xd4, 0x46, 0x09, 0x6f, 0x1e,
+  0x15, 0x2d, 0x69, 0xda, 0x9a, 0x3b, 0xdc, 0xd0, 0xd5, 0x15, 0x33, 0x9e,
+  0x57, 0x15, 0x25, 0x92, 0xf0, 0x94, 0x9e, 0x01, 0x17, 0x9c, 0xb6, 0xa6,
+  0xc9, 0x8c, 0xca, 0xa1, 0x24, 0x19, 0xab, 0x55, 0x10, 0xf5, 0x9c, 0xd1,
+  0x32, 0x53, 0xd4, 0x89, 0x72, 0xc4, 0xf7, 0xdc, 0xba, 0x19, 0xaa, 0x82,
+  0x64, 0x62, 0x63, 0xb6, 0xcc, 0x3f, 0x93, 0xd3, 0xea, 0x06, 0x26, 0xe6,
+  0x4f, 0x5e, 0xae, 0xc8, 0x60, 0x1c, 0x83, 0xfd, 0x77, 0x74, 0x72, 0x74,
+  0x8f, 0x9e, 0xfd, 0xaf, 0x49, 0xaf, 0xd3, 0x6b, 0xb6, 0xf6, 0xf2, 0x8e,
+  0x71, 0x80, 0x19, 0x7a, 0x1e, 0x26, 0x27, 0xcf, 0x73, 0x17, 0x0f, 0xc6,
+  0x87, 0x36, 0x4c, 0x18, 0xb9, 0x20, 0x4c, 0x43, 0x63, 0x44, 0x18, 0xa6,
+  0x7d, 0x2f, 0xd0, 0x52, 0x93, 0x3f, 0x96, 0xc1, 0x61, 0x9a, 0xa6, 0xf7,
+  0x25, 0x0c, 0xdb, 0x84, 0x4d, 0x9c, 0x68, 0x97, 0x26, 0x07, 0xb5, 0x5e,
+  0x67, 0x52, 0x51, 0x0a, 0x69, 0xec, 0x9d, 0x4e, 0xa7, 0x3b, 0x5c, 0x1a,
+  0xe6, 0x67, 0x46, 0xe3, 0x5a, 0x70, 0xa1, 0x2a, 0x92, 0x52, 0xdf, 0x6f,
+  0x0c, 0x9e, 0x6f, 0xb3, 0x9f, 0xc5, 0x6e, 0x09, 0x7e, 0x84, 0x86, 0x4e,
+  0xaf, 0x36, 0x59, 0x36, 0x04, 0x92, 0x72, 0xdd, 0xa7, 0xc4, 0xa1, 0xc6,
+  0x29, 0xb1, 0xfc, 0x2e, 0xce, 0x41, 0xc2, 0x03, 0xb0, 0xcc, 0xaa, 0xd6,
+  0xdf, 0x5a, 0x2a, 0x64, 0xac, 0x04, 0xdb, 0x5d, 0xab, 0x8c, 0xa9, 0xaa,
+  0x24, 0x5b, 0x13, 0x9d, 0x52, 0xa4, 0x57, 0x41, 0x1c, 0x3a, 0x6f, 0x67,
+  0x46, 0x53, 0x21, 0x89, 0xbd, 0x5c, 0x0e, 0xa3, 0x9e, 0xf9, 0x88, 0x8d,
+  0xd3, 0x4e, 0x68, 0xcc, 0x26, 0xab, 0x93, 0xd3, 0xef, 0xc3, 0x5c, 0x75,
+  0x38, 0x7b, 0x96, 0x8b, 0xb4, 0x56, 0xf1, 0xbd, 0x8d, 0x42, 0x5c, 0x53,
+  0x09, 0x5f, 0x3a, 0x41, 0x3d, 0x86, 0xa9, 0x51, 0x8a, 0x21, 0x89, 0xed,
+  0x1a, 0xdd, 0xde, 0xe9, 0xf3, 0x6a, 0xd4, 0xec, 0x5e, 0xb2, 0x10, 0x1c,
+  0xd4, 0xfa, 0xea, 0x83, 0xe2, 0xd9, 0xb3, 0x67, 0x9d, 0x58, 0xb3, 0x30,
+  0xf6, 0xe4, 0x9d, 0x78, 0x79, 0x71, 0x30, 0xf5, 0x23, 0x12, 0x30, 0x8d,
+  0x6a, 0xcd, 0x4a, 0xa6, 0xb7, 0xad, 0x29, 0xa5, 0x20, 0x26, 0xe4, 0x4d,
+  0x3d, 0x72, 0x76, 0x94, 0x94, 0x48, 0xc4, 0xac, 0x2e, 0x5a, 0x3b, 0x6e,
+  0x86, 0x2e, 0x64, 0xdf, 0x9f, 0xda, 0x88, 0x79, 0x1e, 0xcc, 0xaa, 0x6e,
+  0x58, 0xb7, 0x1e, 0x58, 0x2b, 0x82, 0x02, 0xde, 0x14, 0x83, 0xee, 0x7b,
+  0xec, 0xfc, 0x08, 0x8d, 0x76, 0xa9, 0xf1, 0xb6, 0x1e, 0x9f, 0x9c, 0x50,
+  0xfe, 0x61, 0x26, 0x45, 0x65, 0xb8, 0x78, 0xdc, 0xac, 0xf2, 0xa9, 0xfb,
+  0x9e, 0xd9, 0xef, 0xd5, 0xd5, 0xd4, 0x29, 0x70, 0xa0, 0x75, 0xe8, 0xeb,
+  0x96, 0x11, 0xb4, 0x01, 0x49, 0x4b, 0xa2, 0xd9, 0x35, 0x7d, 0xa0, 0x52,
+  0xa0, 0xfd, 0x26, 0x0d, 0x1b, 0x57, 0xbe, 0xbd, 0x1a, 0x7f, 0xea, 0x2a,
+  0x55, 0x77, 0xe5, 0xf1, 0xf5, 0x93, 0xaa, 0x64, 0xaa, 0xa7, 0x09, 0x85,
+  0xda, 0xc3, 0x26, 0xa9, 0x45, 0x15, 0xb4, 0xa2, 0xa0, 0x4d, 0xf5, 0xd4,
+  0x49, 0xaf, 0xd1, 0x7a, 0x05, 0x29, 0xb8, 0xae, 0xa1, 0x1f, 0x52, 0x8a,
+  0xcd, 0x1e, 0xb2, 0x9f, 0x3f, 0x7f, 0x3e, 0xff, 0xaa, 0x5f, 0xae, 0xa1,
+  0x3e, 0xb7, 0xd2, 0x9c, 0x13, 0x93, 0x50, 0x38, 0x83, 0x2f, 0x41, 0xb3,
+  0xff, 0x5c, 0x2b, 0xcd, 0xf2, 0xed, 0x1d, 0xe5, 0xe2, 0xb8, 0x19, 0x4a,
+  0x70, 0x2a, 0x3a, 0x76, 0xc3, 0x90, 0x79, 0xc4, 0xc1, 0x62, 0x89, 0xa7,
+  0x19, 0xbb, 0x06, 0x96, 0x25, 0xae, 0xe9, 0x2f, 0x91, 0x6b, 0xe1, 0x1a,
+  0xa1, 0x5d, 0xb5, 0x24, 0x6e, 0x38, 0x7a, 0x9b, 0x2d, 0x17, 0xc7, 0x66,
+  0xc7, 0x3f, 0xcb, 0x27, 0xb8, 0xc6, 0x8d, 0x62, 0xb6, 0x7c, 0x29, 0x38,
+  0xc7, 0x5a, 0x6e, 0x19, 0x40, 0x0b, 0xd8, 0x8a, 0x5a, 0xc2, 0x6f, 0x6c,
+  0xf8, 0x9a, 0x19, 0x1b, 0x66, 0x2d, 0xa9, 0xad, 0x47, 0xc8, 0xce, 0xa9,
+  0xde, 0x08, 0x79, 0xa5, 0xec, 0xe4, 0xe4, 0xf6, 0xd3, 0x92, 0x28, 0x95,
+  0xb8, 0x8b, 0x61, 0x94, 0xda, 0x6d, 0xc7, 0xdc, 0x2a, 0x76, 0x30, 0xb5,
+  0xdb, 0xb8, 0x6f, 0x52, 0xd3, 0x88, 0x6c, 0xe2, 0xbe, 0x7c, 0x72, 0x78,
+  0x33, 0x3d, 0x5d, 0xa5, 0x73, 0x13, 0x84, 0x8a, 0xec, 0x88, 0x6c, 0xb3,
+  0x41, 0x32, 0x07, 0x33, 0x9c, 0x0b, 0xdd, 0xb3, 0xd1, 0x64, 0x8f, 0x1d,
+  0xb5, 0xf3, 0xd6, 0x3e, 0xdb, 0xce, 0x6a, 0xf8, 0x94, 0x62, 0xd9, 0x6e,
+  0xce, 0x6b, 0xb0, 0x9e, 0x0a, 0x29, 0x0d, 0x5b, 0x22, 0xf2, 0xdc, 0xae,
+  0x49, 0xc5, 0x34, 0x29, 0x4d, 0x59, 0x49, 0xf0, 0x7e, 0x41, 0x55, 0x92,
+  0x94, 0x16, 0xa2, 0xcc, 0xa8, 0x4c, 0xa2, 0x26, 0x18, 0xf0, 0xc1, 0xa8,
+  0x8d, 0xe0, 0x38, 0x90, 0xbe, 0x61, 0x39, 0xfb, 0xa3, 0x32, 0xee, 0x9b,
+  0xa8, 0x3c, 0x52, 0x8d, 0xa3, 0x59, 0x57, 0x25, 0xd5, 0x14, 0x89, 0x3c,
+  0xbd, 0x3f, 0xb7, 0x32, 0x8f, 0xc3, 0x24, 0xa8, 0x7a, 0xb5, 0x66, 0xfa,
+  0x7e, 0x0a, 0x96, 0xe7, 0xe4, 0x9a, 0xfa, 0x81, 0xef, 0xca, 0xfd, 0xf4,
+  0x2e, 0xf7, 0x93, 0xe5, 0x79, 0x9d, 0xa6, 0x54, 0xa9, 0x03, 0x93, 0xe7,
+  0xc9, 0x5e, 0xaa, 0x98, 0x5d, 0x5b, 0x8c, 0xfc, 0x8e, 0x88, 0x70, 0x00,
+  0x29, 0x88, 0x02, 0x65, 0xf9, 0xf2, 0xba, 0x2c, 0xb7, 0x90, 0x5a, 0x00,
+  0xd1, 0x0c, 0xb1, 0xa3, 0x0b, 0x0a, 0x36, 0x5a, 0x0e, 0x25, 0x23, 0xc4,
+  0x50, 0x98, 0x9c, 0x2e, 0xcb, 0xee, 0xc8, 0x8a, 0xe9, 0xf2, 0x42, 0x6e,
+  0xcd, 0x3d, 0x1d, 0x8d, 0x90, 0x7d, 0x1a, 0x7a, 0x8f, 0x55, 0x31, 0x44,
+  0x5f, 0xe4, 0xe0, 0x17, 0x2d, 0x7f, 0x14, 0xf0, 0x4f, 0x53, 0xd5, 0xd1,
+  0x22, 0x6b, 0xcd, 0x39, 0xd5, 0x75, 0xd5, 0x1d, 0x1c, 0x7c, 0xf2, 0xee,
+  0xd0, 0xa2, 0x38, 0x41, 0x1d, 0x91, 0xd2, 0xd1, 0xf2, 0xd7, 0x2a, 0x23,
+  0xda, 0x58, 0x02, 0xe7, 0x9a, 0xe8, 0x5a, 0x59, 0x83, 0x4e, 0x90, 0xec,
+  0x8e, 0x7d, 0xa1, 0x52, 0xc9, 0x2a, 0x64, 0x05, 0xf7, 0xb9, 0x26, 0x12,
+  0xbe, 0x85, 0x04, 0xf2, 0x9a, 0xa7, 0x58, 0x30, 0x60, 0x60, 0x31, 0x2a,
+  0xe4, 0x11, 0x7c, 0x01, 0x69, 0x4c, 0x91, 0x1c, 0x32, 0x91, 0xd6, 0x6b,
+  0xca, 0xf5, 0xe8, 0xbf, 0x35, 0x95, 0xdb, 0x73, 0x47, 0xb0, 0xa3, 0x9c,
+  0xc3, 0xed, 0xdc, 0x17, 0x49, 0x56, 0x46, 0xe6, 0xb7, 0x83, 0xe8, 0xb0,
+  0xbd, 0x80, 0xd1, 0x51, 0x0c, 0xa4, 0x72, 0x9b, 0xf6, 0x3e, 0x44, 0x47,
+  0x7b, 0x4c, 0x4a, 0x8b, 0xea, 0x45, 0x59, 0xa2, 0x35, 0xa4, 0x54, 0x34,
+  0x06, 0x49, 0xcc, 0x9f, 0x32, 0x54, 0x77, 0x74, 0x3b, 0x3b, 0x55, 0x21,
+  0x36, 0x83, 0x3c, 0x86, 0xad, 0xb1, 0xd3, 0x1d, 0xbb, 0x0f, 0xcb, 0x61,
+  0xb0, 0x85, 0x24, 0x01, 0x6e, 0x72, 0x7e, 0x04, 0x5b, 0x94, 0x37, 0xf7,
+  0x48, 0x8c, 0x11, 0xf9, 0xd1, 0xa8, 0xa9, 0x5e, 0x23, 0xdb, 0xaa, 0x90,
+  0xa8, 0xe1, 0xc9, 0xe1, 0x07, 0x88, 0x9a, 0x51, 0x2b, 0x82, 0x33, 0x88,
+  0x10, 0xf8, 0xd1, 0x3d, 0xe6, 0xdb, 0x0e, 0x4b, 0x0a, 0x96, 0xd1, 0x41,
+  0x8e, 0x66, 0xf4, 0xea, 0xf8, 0x6b, 0x02, 0xb5, 0x18, 0xa4, 0xab, 0x18,
+  0x6e, 0x02, 0x91, 0x2e, 0x1d, 0x8a, 0xea, 0x0b, 0xb6, 0xa6, 0xa2, 0xd6,
+  0x48, 0x87, 0x2d, 0x69, 0x0c, 0xdf, 0x19, 0xf2, 0x1e, 0xb1, 0x92, 0xe6,
+  0x72, 0xd0, 0x19, 0xad, 0x03, 0x17, 0xf9, 0x23, 0xef, 0xc8, 0x72, 0x53,
+  0x9d, 0x16, 0x83, 0xe8, 0x58, 0x59, 0x4c, 0x7d, 0x56, 0x82, 0xff, 0xc0,
+  0x93, 0x08, 0x9e, 0xc2, 0x7b, 0xa2, 0x8b, 0x91, 0x24, 0x3c, 0x13, 0xeb,
+  0x81, 0x49, 0x6e, 0xf4, 0xe3, 0xab, 0x8b, 0x28, 0x06, 0x4e, 0x37, 0xe7,
+  0x3a, 0x86, 0x69, 0x9f, 0x31, 0x69, 0x2d, 0xc3, 0x98, 0xd9, 0xa4, 0x46,
+  0x87, 0xf9, 0xc4, 0x48, 0xca, 0x9d, 0x88, 0xe0, 0x70, 0xfa, 0xb5, 0xc3,
+  0x19, 0x1e, 0xf6, 0x28, 0xb7, 0x46, 0x0e, 0x54, 0x0c, 0x59, 0x60, 0x41,
+  0x33, 0xd6, 0xb5, 0xd1, 0x95, 0x6a, 0x5f, 0x8d, 0x54, 0x90, 0x60, 0x7e,
+  0x30, 0x96, 0x31, 0xcc, 0xf0, 0x38, 0x0c, 0xa7, 0x82, 0x83, 0xc4, 0xcc,
+  0xa1, 0xe3, 0x40, 0x38, 0x7e, 0x10, 0xff, 0x88, 0xfd, 0x11, 0xe3, 0x9c,
+  0xca, 0x0b, 0x7a, 0xa3, 0x11, 0x16, 0x2f, 0x36, 0x84, 0xdd, 0xbb, 0xbb,
+  0x30, 0xc0, 0x08, 0x2b, 0xf3, 0x17, 0x1d, 0x45, 0x7b, 0x26, 0xdc, 0x02,
+  0x2d, 0x15, 0x0d, 0x44, 0x3b, 0xdd, 0x58, 0x69, 0x44, 0x0e, 0x99, 0xc1,
+  0xb2, 0x11, 0xab, 0xb4, 0x34, 0x42, 0xa3, 0xc0, 0x10, 0xf7, 0x41, 0x32,
+  0xf8, 0xf7, 0xf9, 0x4f, 0x1f, 0x46, 0x15, 0x91, 0x8a, 0x0e, 0xb2, 0x7d,
+  0x77, 0x5d, 0x00, 0xbb, 0xbd, 0x68, 0xdb, 0xf6, 0x9e, 0x2f, 0xd9, 0xc8,
+  0x1e, 0xb0, 0x2c, 0x08, 0x4e, 0x7b, 0xd1, 0xd3, 0x86, 0xac, 0x22, 0x0c,
+  0xad, 0x7b, 0x90, 0x0a, 0x43, 0xfd, 0xb1, 0xdb, 0xee, 0xe8, 0x6d, 0x56,
+  0xd2, 0x28, 0x7e, 0xe0, 0xd0, 0x8d, 0x09, 0xb6, 0x28, 0x3f, 0x48, 0xf5,
+  0x9a, 0xb0, 0x92, 0x66, 0x30, 0x84, 0x8d, 0x14, 0x26, 0xf0, 0x6d, 0x53,
+  0xec, 0xa7, 0x77, 0x25, 0x0d, 0xb8, 0xd0, 0x90, 0x8b, 0x9a, 0xf7, 0xb1,
+  0x3c, 0x78, 0x6c, 0xcb, 0x7d, 0x77, 0x83, 0x3a, 0x88, 0x42, 0xa6, 0x4f,
+  0x1f, 0xb3, 0x91, 0xbd, 0x87, 0x9f, 0xda, 0xa0, 0xf5, 0x43, 0x4b, 0x39,
+  0xd2, 0x10, 0x2a, 0xad, 0xb0, 0x06, 0x2a, 0xa7, 0x0f, 0x61, 0x04, 0x6f,
+  0xaa, 0xbd, 0x75, 0x01, 0x38, 0xdc, 0x67, 0x57, 0xb6, 0xb5, 0xac, 0x69,
+  0x40, 0xd4, 0x75, 0xaf, 0x48, 0x87, 0x2c, 0x07, 0x6c, 0xdf, 0xb4, 0x25,
+  0x4c, 0xfa, 0x0c, 0x9b, 0x44, 0x0f, 0xa0, 0xb6, 0x7b, 0xd9, 0x59, 0x10,
+  0xec, 0x84, 0x12, 0x16, 0x47, 0x84, 0x61, 0x2d, 0xd1, 0x35, 0x53, 0x01,
+  0xa9, 0xc6, 0xe9, 0xe9, 0x87, 0x66, 0x84, 0xc2, 0x29, 0xad, 0xa9, 0x82,
+  0x94, 0xa7, 0x22, 0xa3, 0xbf, 0xfe, 0xf2, 0xf6, 0xa5, 0x58, 0x57, 0x82,
+  0x53, 0xae, 0x07, 0x98, 0x06, 0x3c, 0x37, 0x89, 0xb8, 0x26, 0x65, 0x4d,
+  0x8f, 0xf0, 0x1a, 0x3f, 0xf1, 0x26, 0xaf, 0xaf, 0xb1, 0x7a, 0x84, 0x77,
+  0x32, 0xe6, 0xbd, 0x65, 0x2a, 0xac, 0xda, 0xc6, 0xf2, 0xee, 0xba, 0x1c,
+  0x06, 0x71, 0x16, 0xf5, 0xd5, 0xcc, 0x7b, 0x22, 0xd7, 0x54, 0x17, 0x22,
+  0x8b, 0x21, 0x25, 0x65, 0x89, 0x2f, 0xa4, 0x31, 0x68, 0x63, 0xc9, 0x1f,
+  0xc6, 0x94, 0xce, 0x00, 0xde, 0x14, 0x12, 0x12, 0x34, 0x00, 0xfe, 0xf3,
+  0xfe, 0xdd, 0x1b, 0xad, 0xab, 0x5f, 0xa8, 0x99, 0x23, 0x94, 0x1e, 0xec,
+  0xd9, 0x82, 0x84, 0x23, 0xc1, 0x4b, 0x41, 0x32, 0xca, 0x33, 0x6f, 0x26,
+  0xe9, 0xc4, 0x40, 0xab, 0x7e, 0x80, 0x7c, 0x16, 0x2f, 0x71, 0x23, 0x43,
+  0x52, 0x65, 0xe2, 0xa9, 0x28, 0xde, 0x80, 0x7d, 0x1d, 0xb7, 0x5d, 0x2a,
+  0xb5, 0x0d, 0xe4, 0x23, 0x54, 0x0e, 0x27, 0xb1, 0x1d, 0x2f, 0xfe, 0x82,
+  0xf8, 0x8a, 0xf2, 0x41, 0x1b, 0x32, 0x8c, 0x5f, 0x73, 0x4d, 0xba, 0x7c,
+  0x57, 0x54, 0xbb, 0xd0, 0xbc, 0xa1, 0x24, 0xa3, 0x26, 0x31, 0x2f, 0xd2,
+  0x94, 0x56, 0xda, 0xa4, 0x2f, 0x22, 0x55, 0x55, 0xb2, 0x94, 0xa0, 0x6d,
+  0xc7, 0xd8, 0x84, 0xa3, 0x2e, 0x01, 0x3b, 0x3f, 0x06, 0x6d, 0x46, 0xe0,
+  0xcf, 0x3f, 0x61, 0x62, 0x3a, 0xd1, 0x77, 0xcd, 0x98, 0xd0, 0xa9, 0x94,
+  0x67, 0x83, 0xbe, 0xe4, 0x5f, 0x0a, 0xfd, 0xa2, 0xc2, 0x86, 0x89, 0xaa,
+  0x3b, 0x93, 0x8c, 0x03, 0xdd, 0xbc, 0xab, 0x15, 0x26, 0x49, 0xd3, 0x0b,
+  0xe1, 0xc9, 0x93, 0x86, 0x19, 0x0e, 0xdc, 0x5c, 0xd6, 0xd3, 0xc0, 0x90,
+  0xd6, 0xeb, 0x61, 0xad, 0x80, 0x51, 0x49, 0xf9, 0xa5, 0x2e, 0x60, 0x09,
+  0xe3, 0x07, 0x2b, 0x03, 0x1a, 0xe3, 0x77, 0x36, 0xe4, 0xec, 0xa9, 0x39,
+  0x81, 0x62, 0xb1, 0xfa, 0x4c, 0x53, 0x1d, 0xf5, 0x69, 0x41, 0x86, 0x9e,
+  0xb6, 0xe9, 0xb1, 0x8c, 0x94, 0x90, 0x7a, 0xb0, 0x03, 0x1a, 0x89, 0x61,
+  0xe5, 0xeb, 0x08, 0x27, 0xbe, 0xd5, 0x48, 0x2a, 0xc5, 0x60, 0x08, 0xa4,
+  0x79, 0xe8, 0x52, 0x17, 0x78, 0xe7, 0x12, 0x23, 0x2a, 0x85, 0xe5, 0x6b,
+  0x21, 0x2a, 0x54, 0xb7, 0xb4, 0xc3, 0x3b, 0x10, 0xf8, 0x60, 0xbb, 0x58,
+  0xf3, 0x96, 0xe0, 0xce, 0xa2, 0x0e, 0x11, 0xb9, 0x90, 0x30, 0x40, 0x39,
+  0xcc, 0x48, 0x19, 0xcf, 0xcd, 0xd7, 0xc2, 0x7a, 0x61, 0xd3, 0x30, 0x87,
+  0xa7, 0x4f, 0xd9, 0x83, 0xd6, 0xa3, 0xee, 0xa7, 0xf7, 0x94, 0x63, 0xd5,
+  0x43, 0xe6, 0x8f, 0xec, 0xd3, 0x08, 0x2b, 0x24, 0x16, 0xc6, 0xaf, 0x6a,
+  0xbf, 0x0d, 0xb7, 0x48, 0x65, 0xdb, 0xdb, 0x9b, 0x8b, 0xf7, 0xef, 0x20,
+  0x41, 0x15, 0x1d, 0x7c, 0x64, 0x85, 0x33, 0x37, 0x59, 0x95, 0x34, 0x6b,
+  0xdf, 0x26, 0x3a, 0xa8, 0xb4, 0xb8, 0x34, 0xa8, 0x46, 0x34, 0x76, 0xdf,
+  0x49, 0x27, 0x49, 0xf0, 0xb4, 0x64, 0xe9, 0x15, 0x24, 0x96, 0xa1, 0x67,
+  0x1c, 0xf3, 0x58, 0xbd, 0x19, 0xef, 0x83, 0x68, 0x47, 0x07, 0x65, 0xe7,
+  0x86, 0xae, 0x29, 0xcf, 0x4b, 0x3e, 0xd9, 0x0d, 0x9b, 0x2f, 0xaa, 0xd8,
+  0xf4, 0xe8, 0xf9, 0x63, 0xba, 0x9a, 0xf3, 0x8f, 0x5e, 0xc7, 0x98, 0xc5,
+  0x94, 0x76, 0x8f, 0xfe, 0xcd, 0x11, 0x56, 0x09, 0x52, 0xed, 0xbf, 0xad,
+  0x24, 0xed, 0x9b, 0x50, 0x57, 0x82, 0xed, 0x6b, 0x4f, 0xd4, 0xfe, 0x9c,
+  0xe7, 0x17, 0x24, 0x7f, 0x2a, 0x77, 0xdd, 0x70, 0xde, 0x1f, 0xa2, 0xf3,
+  0x94, 0x70, 0x34, 0xb6, 0x45, 0xa7, 0x8a, 0xfa, 0x52, 0x82, 0xb1, 0xe9,
+  0xc9, 0x49, 0x60, 0x50, 0x60, 0x74, 0xe8, 0xd8, 0x63, 0x8c, 0x7e, 0x4f,
+  0x78, 0x4d, 0x4a, 0x78, 0xc5, 0xb5, 0xdc, 0x46, 0x8f, 0xca, 0x90, 0xb3,
+  0x3e, 0xec, 0x35, 0x41, 0x5c, 0x28, 0x91, 0x69, 0x61, 0xca, 0x20, 0x06,
+  0xe7, 0x0e, 0x45, 0xe6, 0xee, 0x3a, 0x7d, 0xdd, 0xc0, 0x0f, 0xe7, 0xb1,
+  0xe0, 0xfa, 0xb4, 0x57, 0x13, 0x5a, 0xae, 0x25, 0xa6, 0x80, 0x1b, 0x4d,
+  0x61, 0x61, 0x78, 0xf4, 0x3b, 0xa3, 0x7d, 0xcd, 0xff, 0xda, 0xeb, 0xa2,
+  0x6d, 0x2c, 0xf8, 0x0a, 0xfb, 0x40, 0x07, 0xda, 0x30, 0xc3, 0xb0, 0x71,
+  0x53, 0xc1, 0xbd, 0xfe, 0xec, 0x85, 0xec, 0xf1, 0x40, 0xea, 0x81, 0x90,
+  0xf7, 0x23, 0x05, 0x52, 0x16, 0x84, 0x5f, 0xd2, 0xbe, 0xf1, 0x60, 0x7f,
+  0xee, 0xf3, 0x7f, 0xec, 0x70, 0x9b, 0x9e, 0x9a, 0xdb, 0x40, 0xa9, 0x1d,
+  0x44, 0xad, 0x52, 0x67, 0x9e, 0xdd, 0x0a, 0x28, 0x57, 0x57, 0x53, 0x8f,
+  0xac, 0xd3, 0xb4, 0x70, 0x54, 0xf6, 0xd4, 0xe3, 0xa2, 0xfb, 0xcd, 0x76,
+  0x3c, 0x3a, 0x0d, 0x32, 0xb2, 0x38, 0xbe, 0xfb, 0xf5, 0x68, 0x71, 0xec,
+  0x7e, 0xe6, 0xc5, 0xc7, 0xe6, 0x7f, 0x89, 0xff, 0x1f, 0x01, 0x02, 0x3f,
+  0xe6, 0x2a, 0x1f, 0x00, 0x00
+};
+unsigned int http_html_backup_len = 2393;

+ 7 - 0
app/modules/eus/prepare.sh

@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Uses zopfli for better gzip compression
+# sudo apt-get install zopfli
+zopfli --gzip ./enduser_setup.html
+xxd -i ./enduser_setup.html.gz | sed 's/unsigned char/static const char/; s/__enduser_setup_html_gz/http_html_backup/' > http_html_backup.def
+

+ 172 - 60
app/modules/file.c

@@ -2,15 +2,24 @@
 
 #include "module.h"
 #include "lauxlib.h"
+#include "lmem.h"
 #include "platform.h"
 
 #include "c_types.h"
 #include "vfs.h"
 #include "c_string.h"
 
+#include <alloca.h>
+
+#define FILE_READ_CHUNK 1024
+
 static int file_fd = 0;
+static int file_fd_ref = LUA_NOREF;
 static int rtc_cb_ref = LUA_NOREF;
 
+typedef struct _file_fd_ud {
+  int fd;
+} file_fd_ud;
 
 static void table2tm( lua_State *L, vfs_time *tm )
 {
@@ -91,13 +100,48 @@ static int file_on(lua_State *L)
 // Lua: close()
 static int file_close( lua_State* L )
 {
-  if(file_fd){
-    vfs_close(file_fd);
-    file_fd = 0;
+  int need_pop = FALSE;
+  file_fd_ud *ud;
+
+  if (lua_type( L, 1 ) != LUA_TUSERDATA) {
+    // fall back to last opened file
+    if (file_fd_ref != LUA_NOREF) {
+      lua_rawgeti( L, LUA_REGISTRYINDEX, file_fd_ref );
+      // top of stack is now default file descriptor
+      ud = (file_fd_ud *)luaL_checkudata(L, -1, "file.obj");
+      lua_pop( L, 1 );
+    } else {
+      // no default file currently opened
+      return 0;
+    }
+  } else {
+    ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
+  }
+
+  // unref default file descriptor
+  luaL_unref( L, LUA_REGISTRYINDEX, file_fd_ref );
+  file_fd_ref = LUA_NOREF;
+
+  if(ud->fd){
+      vfs_close(ud->fd);
+      // mark as closed
+      ud->fd = 0;
   }
   return 0;  
 }
 
+static int file_obj_free( lua_State *L )
+{
+  file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
+  if (ud->fd) {
+    // close file if it's still open
+    vfs_close(ud->fd);
+    ud->fd = 0;
+  }
+
+  return 0;
+}
+
 // Lua: format()
 static int file_format( lua_State* L )
 {
@@ -130,10 +174,10 @@ static int file_fscfg (lua_State *L)
 static int file_open( lua_State* L )
 {
   size_t len;
-  if(file_fd){
-    vfs_close(file_fd);
-    file_fd = 0;
-  }
+
+  // unref last file descriptor to allow gc'ing if not kept by user script
+  luaL_unref( L, LUA_REGISTRYINDEX, file_fd_ref );
+  file_fd_ref = LUA_NOREF;
 
   const char *fname = luaL_checklstring( L, 1, &len );
   const char *basename = vfs_basename( fname );
@@ -146,7 +190,14 @@ static int file_open( lua_State* L )
   if(!file_fd){
     lua_pushnil(L);
   } else {
-    lua_pushboolean(L, 1);
+    file_fd_ud *ud = (file_fd_ud *) lua_newuserdata( L, sizeof( file_fd_ud ) );
+    ud->fd = file_fd;
+    luaL_getmetatable( L, "file.obj" );
+    lua_setmetatable( L, -2 );
+
+    // store reference to opened file
+    lua_pushvalue( L, -1 );
+    file_fd_ref = luaL_ref( L, LUA_REGISTRYINDEX );
   }
   return 1; 
 }
@@ -170,19 +221,36 @@ static int file_list( lua_State* L )
   return 0;
 }
 
-static int file_seek (lua_State *L) 
+static int get_file_obj( lua_State *L, int *argpos )
+{
+  if (lua_type( L, 1 ) == LUA_TUSERDATA) {
+    file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
+    *argpos = 2;
+    return ud->fd;
+  } else {
+    *argpos = 1;
+    return file_fd;
+  }
+}
+
+#define GET_FILE_OBJ int argpos; \
+  int fd = get_file_obj( L, &argpos );
+
+static int file_seek (lua_State *L)
 {
+  GET_FILE_OBJ;
+
   static const int mode[] = {VFS_SEEK_SET, VFS_SEEK_CUR, VFS_SEEK_END};
   static const char *const modenames[] = {"set", "cur", "end", NULL};
-  if(!file_fd)
+  if(!fd)
     return luaL_error(L, "open a file first");
-  int op = luaL_checkoption(L, 1, "cur", modenames);
-  long offset = luaL_optlong(L, 2, 0);
-  op = vfs_lseek(file_fd, offset, mode[op]);
+  int op = luaL_checkoption(L, argpos, "cur", modenames);
+  long offset = luaL_optlong(L, ++argpos, 0);
+  op = vfs_lseek(fd, offset, mode[op]);
   if (op < 0)
     lua_pushnil(L);  /* error */
   else
-    lua_pushinteger(L, vfs_tell(file_fd));
+    lua_pushinteger(L, vfs_tell(fd));
   return 1;
 }
 
@@ -210,7 +278,6 @@ static int file_remove( lua_State* L )
   const char *fname = luaL_checklstring( L, 1, &len );    
   const char *basename = vfs_basename( fname );
   luaL_argcheck(L, c_strlen(basename) <= FS_OBJ_NAME_LEN && c_strlen(fname) == len, 1, "filename invalid");
-  file_close(L);
   vfs_remove((char *)fname);
   return 0;
 }
@@ -218,9 +285,11 @@ static int file_remove( lua_State* L )
 // Lua: flush()
 static int file_flush( lua_State* L )
 {
-  if(!file_fd)
+  GET_FILE_OBJ;
+
+  if(!fd)
     return luaL_error(L, "open a file first");
-  if(vfs_flush(file_fd) == 0)
+  if(vfs_flush(fd) == 0)
     lua_pushboolean(L, 1);
   else
     lua_pushnil(L);
@@ -231,10 +300,6 @@ static int file_flush( lua_State* L )
 static int file_rename( lua_State* L )
 {
   size_t len;
-  if(file_fd){
-    vfs_close(file_fd);
-    file_fd = 0;
-  }
 
   const char *oldname = luaL_checklstring( L, 1, &len );
   const char *basename = vfs_basename( oldname );
@@ -253,38 +318,64 @@ static int file_rename( lua_State* L )
 }
 
 // g_read()
-static int file_g_read( lua_State* L, int n, int16_t end_char )
+static int file_g_read( lua_State* L, int n, int16_t end_char, int fd )
 {
-  if(n <= 0 || n > LUAL_BUFFERSIZE)
-    n = LUAL_BUFFERSIZE;
+  static char *heap_mem = NULL;
+  // free leftover memory
+  if (heap_mem) {
+    luaM_free(L, heap_mem);
+    heap_mem = NULL;
+  }
+
+  if(n <= 0)
+    n = FILE_READ_CHUNK;
+
   if(end_char < 0 || end_char >255)
     end_char = EOF;
-  
-  luaL_Buffer b;
-  if(!file_fd)
+
+
+  if(!fd)
     return luaL_error(L, "open a file first");
 
-  luaL_buffinit(L, &b);
-  char *p = luaL_prepbuffer(&b);
+  char *p;
   int i;
 
-  n = vfs_read(file_fd, p, n);
-  for (i = 0; i < n; ++i)
-    if (p[i] == end_char)
-    {
-      ++i;
-      break;
-    }
+  if (n > LUAL_BUFFERSIZE) {
+    // get buffer from heap
+    p = heap_mem = luaM_malloc(L, n);
+  } else {
+    // small chunks go onto the stack
+    p = alloca(n);
+  }
 
-  if(i==0){
-    luaL_pushresult(&b);  /* close buffer */
-    return (lua_objlen(L, -1) > 0);  /* check whether read something */
+  n = vfs_read(fd, p, n);
+  // bypass search if no end character provided
+  if (n > 0 && end_char != EOF) {
+    for (i = 0; i < n; ++i)
+      if (p[i] == end_char)
+      {
+        ++i;
+        break;
+      }
+  } else {
+    i = n;
   }
 
-  vfs_lseek(file_fd, -(n - i), VFS_SEEK_CUR);
-  luaL_addsize(&b, i);
-  luaL_pushresult(&b);  /* close buffer */
-  return 1;  /* read at least an `eol' */ 
+  if (i == 0 || n == VFS_RES_ERR) {
+    if (heap_mem) {
+      luaM_free(L, heap_mem);
+      heap_mem = NULL;
+    }
+    return 0;
+  }
+
+  vfs_lseek(fd, -(n - i), VFS_SEEK_CUR);
+  lua_pushlstring(L, p, i);
+  if (heap_mem) {
+    luaM_free(L, heap_mem);
+    heap_mem = NULL;
+  }
+  return 1;
 }
 
 // Lua: read()
@@ -293,42 +384,46 @@ static int file_g_read( lua_State* L, int n, int16_t end_char )
 // file.read('q') will read until 'q' or EOF is reached. 
 static int file_read( lua_State* L )
 {
-  unsigned need_len = LUAL_BUFFERSIZE;
+  unsigned need_len = FILE_READ_CHUNK;
   int16_t end_char = EOF;
   size_t el;
-  if( lua_type( L, 1 ) == LUA_TNUMBER )
+
+  GET_FILE_OBJ;
+
+  if( lua_type( L, argpos ) == LUA_TNUMBER )
   {
-    need_len = ( unsigned )luaL_checkinteger( L, 1 );
-    if( need_len > LUAL_BUFFERSIZE ){
-      need_len = LUAL_BUFFERSIZE;
-    }
+    need_len = ( unsigned )luaL_checkinteger( L, argpos );
   }
-  else if(lua_isstring(L, 1))
+  else if(lua_isstring(L, argpos))
   {
-    const char *end = luaL_checklstring( L, 1, &el );
+    const char *end = luaL_checklstring( L, argpos, &el );
     if(el!=1){
       return luaL_error( L, "wrong arg range" );
     }
     end_char = (int16_t)end[0];
   }
 
-  return file_g_read(L, need_len, end_char);
+  return file_g_read(L, need_len, end_char, fd);
 }
 
 // Lua: readline()
 static int file_readline( lua_State* L )
 {
-  return file_g_read(L, LUAL_BUFFERSIZE, '\n');
+  GET_FILE_OBJ;
+
+  return file_g_read(L, LUAL_BUFFERSIZE, '\n', fd);
 }
 
 // Lua: write("string")
 static int file_write( lua_State* L )
 {
-  if(!file_fd)
+  GET_FILE_OBJ;
+
+  if(!fd)
     return luaL_error(L, "open a file first");
   size_t l, rl;
-  const char *s = luaL_checklstring(L, 1, &l);
-  rl = vfs_write(file_fd, s, l);
+  const char *s = luaL_checklstring(L, argpos, &l);
+  rl = vfs_write(fd, s, l);
   if(rl==l)
     lua_pushboolean(L, 1);
   else
@@ -339,13 +434,15 @@ static int file_write( lua_State* L )
 // Lua: writeline("string")
 static int file_writeline( lua_State* L )
 {
-  if(!file_fd)
+  GET_FILE_OBJ;
+
+  if(!fd)
     return luaL_error(L, "open a file first");
   size_t l, rl;
-  const char *s = luaL_checklstring(L, 1, &l);
-  rl = vfs_write(file_fd, s, l);
+  const char *s = luaL_checklstring(L, argpos, &l);
+  rl = vfs_write(fd, s, l);
   if(rl==l){
-    rl = vfs_write(file_fd, "\n", 1);
+    rl = vfs_write(fd, "\n", 1);
     if(rl==1)
       lua_pushboolean(L, 1);
     else
@@ -420,6 +517,20 @@ static int file_vol_umount( lua_State *L )
 }
 
 
+static const LUA_REG_TYPE file_obj_map[] =
+{
+  { LSTRKEY( "close" ),     LFUNCVAL( file_close ) },
+  { LSTRKEY( "read" ),      LFUNCVAL( file_read ) },
+  { LSTRKEY( "readline" ),  LFUNCVAL( file_readline ) },
+  { LSTRKEY( "write" ),     LFUNCVAL( file_write ) },
+  { LSTRKEY( "writeline" ), LFUNCVAL( file_writeline ) },
+  { LSTRKEY( "seek" ),      LFUNCVAL( file_seek ) },
+  { LSTRKEY( "flush" ),     LFUNCVAL( file_flush ) },
+  { LSTRKEY( "__gc" ),      LFUNCVAL( file_obj_free ) },
+  { LSTRKEY( "__index" ),   LROVAL( file_obj_map ) },
+  { LNILKEY, LNILVAL }
+};
+
 static const LUA_REG_TYPE file_vol_map[] =
 {
   { LSTRKEY( "umount" ),   LFUNCVAL( file_vol_umount )},
@@ -459,6 +570,7 @@ static const LUA_REG_TYPE file_map[] = {
 
 int luaopen_file( lua_State *L ) {
   luaL_rometatable( L, "file.vol",  (void *)file_vol_map );
+  luaL_rometatable( L, "file.obj",  (void *)file_obj_map );
   return 0;
 }
 

+ 15 - 5
app/modules/gpio.c

@@ -190,19 +190,20 @@ static void seroutasync_done (task_param_t arg)
 {
   lua_State *L = lua_getstate();
   luaM_freearray(L, serout.delay_table, serout.tablelen, uint32);
-  if (serout.lua_done_ref != LUA_REFNIL) { // we're here so serout.lua_done_ref != LUA_NOREF
+  serout.delay_table = NULL;
+  if (serout.lua_done_ref != LUA_NOREF) {
     lua_rawgeti (L, LUA_REGISTRYINDEX, serout.lua_done_ref);
+    luaL_unref (L, LUA_REGISTRYINDEX, serout.lua_done_ref);
+    serout.lua_done_ref = LUA_NOREF;
     if (lua_pcall(L, 0, 0, 0)) {
       // Uncaught Error. Print instead of sudden reset
       luaL_error(L, "error: %s", lua_tostring(L, -1));
     }
-    luaL_unref (L, LUA_REGISTRYINDEX, serout.lua_done_ref);
   }
 }
 
 static void ICACHE_RAM_ATTR seroutasync_cb(os_param_t p) {
   (void) p;
-  NODE_DBG("%d\t%d\t%d\t%d\t%d\t%d\t%d\n", serout.repeats, serout.index, serout.level, serout.pin, serout.tablelen, serout.delay_table[serout.index], system_get_time()); // timing is delayed for short timings when debug output is enabled
   if (serout.index < serout.tablelen) {
     GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[serout.pin]), serout.level);
     serout.level = serout.level==LOW ? HIGH : LOW;
@@ -220,17 +221,25 @@ static int lgpio_serout( lua_State* L )
   serout.pin = luaL_checkinteger( L, 1 );
   serout.level = luaL_checkinteger( L, 2 );
   serout.repeats = luaL_optint( L, 4, 1 )-1;
+  luaL_unref (L, LUA_REGISTRYINDEX, serout.lua_done_ref);
+  uint8_t is_async = FALSE;
   if (!lua_isnoneornil(L, 5)) {
     if (lua_isnumber(L, 5)) {
-      serout.lua_done_ref = LUA_REFNIL;
+      serout.lua_done_ref = LUA_NOREF;
     } else {
       lua_pushvalue(L, 5);
       serout.lua_done_ref = luaL_ref(L, LUA_REGISTRYINDEX);
     }
+    is_async = TRUE;
   } else {
     serout.lua_done_ref = LUA_NOREF;
   }
 
+  if (serout.delay_table) {
+    luaM_freearray(L, serout.delay_table, serout.tablelen, uint32);
+    serout.delay_table = NULL;
+  }
+
   luaL_argcheck(L, platform_gpio_exists(serout.pin), 1, "Invalid pin");
   luaL_argcheck(L, serout.level==HIGH || serout.level==LOW, 2, "Wrong level type" );
   luaL_argcheck(L, lua_istable( L, 3 ) &&
@@ -244,7 +253,7 @@ static int lgpio_serout( lua_State* L )
     lua_pop( L, 1 );
   }
 
-  if (serout.lua_done_ref != LUA_NOREF) { // async version for duration above 15 mSec
+  if (is_async) { // async version for duration above 15 mSec
     if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, TRUE)) {
       // Failed to init the timer
       luaL_error(L, "Unable to initialize timer");
@@ -296,6 +305,7 @@ int luaopen_gpio( lua_State *L ) {
   platform_gpio_init(task_get_id(gpio_intr_callback_task));
 #endif
   serout.done_taskid = task_get_id((task_callback_t) seroutasync_done);
+  serout.lua_done_ref = LUA_NOREF;
   return 0;
 }
 

+ 3 - 3
app/modules/http.c

@@ -14,13 +14,13 @@ static int http_callback_registry  = LUA_NOREF;
 static void http_callback( char * response, int http_status, char * full_response )
 {
 #if defined(HTTPCLIENT_DEBUG_ON)
-  c_printf( "http_status=%d\n", http_status );
+  dbg_printf( "http_status=%d\n", http_status );
   if ( http_status != HTTP_STATUS_GENERIC_ERROR )
   {
     if (full_response != NULL) {
-      c_printf( "strlen(full_response)=%d\n", strlen( full_response ) );
+      dbg_printf( "strlen(full_response)=%d\n", strlen( full_response ) );
     }
-    c_printf( "response=%s<EOF>\n", response );
+    dbg_printf( "response=%s<EOF>\n", response );
   }
 #endif
   if (http_callback_registry != LUA_NOREF)

+ 47 - 47
app/modules/mqtt.c

@@ -72,6 +72,7 @@ typedef struct lmqtt_userdata
   uint8_t secure;
 #endif
   bool connected;     // indicate socket connected, not mqtt prot connected.
+  bool keepalive_sent;
   ETSTimer mqttTimer;
   tConnState connState;
 }lmqtt_userdata;
@@ -212,9 +213,9 @@ static void mqtt_connack_fail(lmqtt_userdata * mud, int reason_code)
   {
     return;
   }
-  
-  lua_State *L = lua_getstate();		  
-        
+
+  lua_State *L = lua_getstate();
+
   lua_rawgeti(L, LUA_REGISTRYINDEX, mud->cb_connect_fail_ref);
   lua_rawgeti(L, LUA_REGISTRYINDEX, mud->self_ref);  // pass the userdata(client) to callback func in lua
   lua_pushinteger(L, reason_code);
@@ -241,12 +242,12 @@ static sint8 mqtt_send_if_possible(struct espconn *pesp_conn)
 #ifdef CLIENT_SSL_ENABLE
       if( mud->secure )
       {
-	espconn_status = espconn_secure_send( pesp_conn, pending_msg->msg.data, pending_msg->msg.length );
+        espconn_status = espconn_secure_send( pesp_conn, pending_msg->msg.data, pending_msg->msg.length );
       }
       else
 #endif
       {
-	espconn_status = espconn_send( pesp_conn, pending_msg->msg.data, pending_msg->msg.length );
+        espconn_status = espconn_send( pesp_conn, pending_msg->msg.data, pending_msg->msg.length );
       }
       mud->keep_alive_tick = 0;
     }
@@ -275,7 +276,7 @@ static void mqtt_socket_received(void *arg, char *pdata, unsigned short len)
 
 READPACKET:
   if(length > MQTT_BUF_SIZE || length <= 0)
-	  return;
+          return;
 
   // c_memcpy(in_buffer, pdata, length);
   uint8_t temp_buffer[MQTT_BUF_SIZE];
@@ -287,7 +288,7 @@ READPACKET:
     case MQTT_CONNECT_SENDING:
     case MQTT_CONNECT_SENT:
         mud->event_timeout = 0;
-         
+
       if(mqtt_get_type(in_buffer) != MQTT_MSG_TYPE_CONNACK){
         NODE_DBG("MQTT: Invalid packet\r\n");
         mud->connState = MQTT_INIT;
@@ -301,16 +302,16 @@ READPACKET:
         {
           espconn_disconnect(pesp_conn);
         }
-        
+
         mqtt_connack_fail(mud, MQTT_CONN_FAIL_NOT_A_CONNACK_MSG);
-        
+
         break;
-            
+
       } else if (mqtt_get_connect_ret_code(in_buffer) != MQTT_CONNACK_ACCEPTED) {
         NODE_DBG("MQTT: CONNACK REFUSED (CODE: %d)\n", mqtt_get_connect_ret_code(in_buffer));
-        
+
         mud->connState = MQTT_INIT;
-                
+
 #ifdef CLIENT_SSL_ENABLE
         if(mud->secure)
         {
@@ -320,12 +321,12 @@ READPACKET:
 #endif
         {
           espconn_disconnect(pesp_conn);
-        } 
-        
+        }
+
         mqtt_connack_fail(mud, mqtt_get_connect_ret_code(in_buffer));
-        
+
         break;
-                
+
       } else {
         mud->connState = MQTT_DATA;
         NODE_DBG("MQTT: Connected\r\n");
@@ -454,6 +455,7 @@ READPACKET:
           break;
         case MQTT_MSG_TYPE_PINGRESP:
           // Ignore
+          mud->keepalive_sent = 0;
           NODE_DBG("MQTT: PINGRESP received\r\n");
           break;
       }
@@ -585,7 +587,7 @@ void mqtt_socket_timer(void *arg)
   NODE_DBG("timer, queue size: %d\n", msg_size(&(mud->mqtt_state.pending_msg_q)));
   if(mud->event_timeout > 0){
     NODE_DBG("event_timeout: %d.\n", mud->event_timeout);
-  	mud->event_timeout --;
+        mud->event_timeout --;
     if(mud->event_timeout > 0){
       return;
     } else {
@@ -605,7 +607,7 @@ void mqtt_socket_timer(void *arg)
     NODE_DBG("sSend MQTT_CONNECT failed.\n");
     mud->connState = MQTT_INIT;
     mqtt_connack_fail(mud, MQTT_CONN_FAIL_TIMEOUT_SENDING);
-            
+
 #ifdef CLIENT_SSL_ENABLE
     if(mud->secure)
     {
@@ -620,7 +622,7 @@ void mqtt_socket_timer(void *arg)
   } else if(mud->connState == MQTT_CONNECT_SENT) { // wait for CONACK time out.
     NODE_DBG("MQTT_CONNECT timeout.\n");
     mud->connState = MQTT_INIT;
-    
+
 #ifdef CLIENT_SSL_ENABLE
     if(mud->secure)
     {
@@ -630,7 +632,7 @@ void mqtt_socket_timer(void *arg)
 #endif
     {
       espconn_disconnect(mud->pesp_conn);
-    }    
+    }
     mqtt_connack_fail(mud, MQTT_CONN_FAIL_TIMEOUT_RECEIVING);
   } else if(mud->connState == MQTT_DATA){
     msg_queue_t *pending_msg = msg_peek(&(mud->mqtt_state.pending_msg_q));
@@ -640,13 +642,20 @@ void mqtt_socket_timer(void *arg)
       // no queued event.
       mud->keep_alive_tick ++;
       if(mud->keep_alive_tick > mud->mqtt_state.connect_info->keepalive){
-        uint8_t temp_buffer[MQTT_BUF_SIZE];
-        mqtt_msg_init(&mud->mqtt_state.mqtt_connection, temp_buffer, MQTT_BUF_SIZE);
-        NODE_DBG("\r\nMQTT: Send keepalive packet\r\n");
-        mqtt_message_t* temp_msg = mqtt_msg_pingreq(&mud->mqtt_state.mqtt_connection);
-        msg_queue_t *node = msg_enqueue( &(mud->mqtt_state.pending_msg_q), temp_msg,
-                            0, MQTT_MSG_TYPE_PINGREQ, (int)mqtt_get_qos(temp_msg->data) );
-        mqtt_send_if_possible(mud->pesp_conn);
+        if (mud->keepalive_sent) {
+          // Oh dear -- keepalive timer expired and still no ack of previous message
+          mqtt_socket_reconnected(mud->pesp_conn, 0);
+        } else {
+          uint8_t temp_buffer[MQTT_BUF_SIZE];
+          mqtt_msg_init(&mud->mqtt_state.mqtt_connection, temp_buffer, MQTT_BUF_SIZE);
+          NODE_DBG("\r\nMQTT: Send keepalive packet\r\n");
+          mqtt_message_t* temp_msg = mqtt_msg_pingreq(&mud->mqtt_state.mqtt_connection);
+          msg_queue_t *node = msg_enqueue( &(mud->mqtt_state.pending_msg_q), temp_msg,
+                              0, MQTT_MSG_TYPE_PINGREQ, (int)mqtt_get_qos(temp_msg->data) );
+          mud->keepalive_sent = 1;
+          mud->keep_alive_tick = 0;     // Need to reset to zero in case flow control stopped.
+          mqtt_send_if_possible(mud->pesp_conn);
+        }
       }
     }
   }
@@ -675,6 +684,7 @@ static int mqtt_socket_client( lua_State* L )
 
   // create a object
   mud = (lmqtt_userdata *)lua_newuserdata(L, sizeof(lmqtt_userdata));
+  c_memset(mud, 0, sizeof(*mud));
   // pre-initialize it, in case of errors
   mud->self_ref = LUA_NOREF;
   mud->cb_connect_ref = LUA_NOREF;
@@ -685,18 +695,8 @@ static int mqtt_socket_client( lua_State* L )
   mud->cb_suback_ref = LUA_NOREF;
   mud->cb_unsuback_ref = LUA_NOREF;
   mud->cb_puback_ref = LUA_NOREF;
-  mud->pesp_conn = NULL;
-#ifdef CLIENT_SSL_ENABLE
-  mud->secure = 0;
-#endif
 
-  mud->keep_alive_tick = 0;
-  mud->event_timeout = 0;
   mud->connState = MQTT_INIT;
-  mud->connected = false;
-  c_memset(&mud->mqttTimer, 0, sizeof(ETSTimer));
-  c_memset(&mud->mqtt_state, 0, sizeof(mqtt_state_t));
-  c_memset(&mud->connect_info, 0, sizeof(mqtt_connect_info_t));
 
   // set its metatable
   luaL_getmetatable(L, "mqtt.socket");
@@ -761,7 +761,7 @@ static int mqtt_socket_client( lua_State* L )
       c_free(mud->connect_info.password);
       mud->connect_info.password = NULL;
     }
-  	return luaL_error(L, "not enough memory");
+        return luaL_error(L, "not enough memory");
   }
 
   c_memcpy(mud->connect_info.client_id, clientId, idl);
@@ -819,8 +819,8 @@ static int mqtt_delete( lua_State* L )
 
   // ---- alloc-ed in mqtt_socket_lwt()
   if(mud->connect_info.will_topic){
-  	c_free(mud->connect_info.will_topic);
-  	mud->connect_info.will_topic = NULL;
+        c_free(mud->connect_info.will_topic);
+        mud->connect_info.will_topic = NULL;
   }
 
   if(mud->connect_info.will_message){
@@ -926,7 +926,7 @@ static sint8 socket_dns_found(const char *name, ip_addr_t *ipaddr, void *arg)
     if( dns_reconn_count >= 5 ){
       NODE_ERR( "DNS Fail!\n" );
       // Note: should delete the pesp_conn or unref self_ref here.
-      
+
       struct espconn *pesp_conn = arg;
       if(pesp_conn != NULL) {
           lmqtt_userdata *mud = (lmqtt_userdata *)pesp_conn->reverse;
@@ -934,7 +934,7 @@ static sint8 socket_dns_found(const char *name, ip_addr_t *ipaddr, void *arg)
             mqtt_connack_fail(mud, MQTT_CONN_FAIL_DNS);
           }
       }
-      
+
       mqtt_socket_disconnected(arg);   // although not connected, but fire disconnect callback to release every thing.
       return -1;
     }
@@ -1070,7 +1070,7 @@ static int mqtt_socket_connect( lua_State* L )
   }
 
   stack++;
-  
+
   // call back function when a connection fails
   if ((stack<=top) && (lua_type(L, stack) == LUA_TFUNCTION || lua_type(L, stack) == LUA_TLIGHTFUNCTION)){
     lua_pushvalue(L, stack);  // copy argument (func) to the top of stack
@@ -1135,7 +1135,7 @@ static int mqtt_socket_close( lua_State* L )
     // Send disconnect message
     mqtt_message_t* temp_msg = mqtt_msg_disconnect(&mud->mqtt_state.mqtt_connection);
     NODE_DBG("Send MQTT disconnect infomation, data len: %d, d[0]=%d \r\n", temp_msg->length,  temp_msg->data[0]);
-  
+
 #ifdef CLIENT_SSL_ENABLE
     if(mud->secure) {
       espconn_status = espconn_secure_send(mud->pesp_conn, temp_msg->data, temp_msg->length);
@@ -1150,7 +1150,7 @@ static int mqtt_socket_close( lua_State* L )
     }
   }
   mud->connected = 0;
-  
+
   while (mud->mqtt_state.pending_msg_q) {
     msg_destroy(msg_dequeue(&(mud->mqtt_state.pending_msg_q)));
   }
@@ -1595,7 +1595,7 @@ static const LUA_REG_TYPE mqtt_socket_map[] = {
   { LNILKEY, LNILVAL }
 };
 
-   
+
 static const LUA_REG_TYPE mqtt_map[] = {
   { LSTRKEY( "Client" ),                                LFUNCVAL( mqtt_socket_client ) },
 
@@ -1609,7 +1609,7 @@ static const LUA_REG_TYPE mqtt_map[] = {
   { LSTRKEY( "CONNACK_REFUSED_ID_REJECTED" ),           LNUMVAL( MQTT_CONNACK_REFUSED_ID_REJECTED ) },
   { LSTRKEY( "CONNACK_REFUSED_SERVER_UNAVAILABLE" ),    LNUMVAL( MQTT_CONNACK_REFUSED_SERVER_UNAVAILABLE ) },
   { LSTRKEY( "CONNACK_REFUSED_BAD_USER_OR_PASS" ),      LNUMVAL( MQTT_CONNACK_REFUSED_BAD_USER_OR_PASS ) },
-  { LSTRKEY( "CONNACK_REFUSED_NOT_AUTHORIZED" ),        LNUMVAL( MQTT_CONNACK_REFUSED_NOT_AUTHORIZED ) },      
+  { LSTRKEY( "CONNACK_REFUSED_NOT_AUTHORIZED" ),        LNUMVAL( MQTT_CONNACK_REFUSED_NOT_AUTHORIZED ) },
 
   { LSTRKEY( "__metatable" ),                           LROVAL( mqtt_map ) },
   { LNILKEY, LNILVAL }

+ 2 - 0
app/modules/ow.c

@@ -134,6 +134,8 @@ static int ow_read_bytes( lua_State *L )
   if( size == 0 )
     return 0;
 
+  luaL_argcheck(L, size <= LUAL_BUFFERSIZE, 2, "Attempt to read too many characters");
+
   luaL_Buffer b;
   luaL_buffinit( L, &b );
   char *p = luaL_prepbuffer(&b);

+ 1 - 1
app/modules/sntp.c

@@ -53,7 +53,7 @@
 #define MAX_ATTEMPTS 5
 
 #if 0
-# define sntp_dbg(...) c_printf(__VA_ARGS__)
+# define sntp_dbg(...) dbg_printf(__VA_ARGS__)
 #else
 # define sntp_dbg(...)
 #endif

+ 248 - 0
app/modules/somfy.c

@@ -0,0 +1,248 @@
+// ***************************************************************************
+// Somfy module for ESP8266 with NodeMCU
+// 
+// Written by Lukas Voborsky, @voborsky
+// based on https://github.com/Nickduino/Somfy_Remote
+// Somfy protocol description: https://pushstack.wordpress.com/somfy-rts-protocol/
+// and discussion: https://forum.arduino.cc/index.php?topic=208346.0
+// 
+// MIT license, http://opensource.org/licenses/MIT
+// ***************************************************************************
+
+//#define NODE_DEBUG
+
+#include "os_type.h"
+#include "osapi.h"
+#include "sections.h"
+
+#include "module.h"
+#include "lauxlib.h"
+#include "lmem.h"
+#include "platform.h"
+#include "hw_timer.h"
+#include "user_interface.h"
+
+#define SYMBOL 640 // symbol width in microseconds
+#define SOMFY_UP    0x2
+#define SOMFY_STOP  0x1
+#define SOMFY_DOWN  0x4
+#define SOMFY_PROG  0x8
+
+#define DIRECT_WRITE_LOW(pin)    (GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), 0))
+#define DIRECT_WRITE_HIGH(pin)   (GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), 1))
+
+static const os_param_t TIMER_OWNER = 0x736f6d66; // "somf"
+static task_handle_t done_taskid;
+
+static uint8_t    pin;
+static uint8_t    frame[7];
+static uint8_t    sync;
+static uint8_t    repeat;
+
+//static uint32_t   delay[10] = {9415, 89565, 4*SYMBOL, 4*SYMBOL, 4*SYMBOL, 4550, SYMBOL, SYMBOL, SYMBOL, 30415}; // in us
+// the `delay` array of constants must be in RAM as it is accessed from the timer interrupt
+static const RAM_CONST_SECTION_ATTR uint32_t delay[10] = {US_TO_RTC_TIMER_TICKS(9415), US_TO_RTC_TIMER_TICKS(89565), US_TO_RTC_TIMER_TICKS(4*SYMBOL), US_TO_RTC_TIMER_TICKS(4*SYMBOL), US_TO_RTC_TIMER_TICKS(4*SYMBOL), US_TO_RTC_TIMER_TICKS(4550), US_TO_RTC_TIMER_TICKS(SYMBOL), US_TO_RTC_TIMER_TICKS(SYMBOL), US_TO_RTC_TIMER_TICKS(SYMBOL), US_TO_RTC_TIMER_TICKS(30415)}; // in ticks (no need to recalculate)
+
+static uint8_t    repeatindex;
+static uint8_t    signalindex;
+static uint8_t    subindex;
+static uint8_t    bitcondition;
+
+int lua_done_ref; // callback when transmission is done
+
+void buildFrame(uint8_t *frame, uint64_t remote, uint8_t button, uint16_t code) {
+    // NODE_DBG("remote: %x\n", remote);
+    // NODE_DBG("button: %x\n", button);
+    // NODE_DBG("rolling code: %x\n", code);
+    frame[0] = 0xA7; // Encryption key. Doesn't matter much
+    frame[1] = button << 4; // Which button did you press? The 4 LSB will be the checksum
+    frame[2] = code >> 8; // Rolling code (big endian)
+    frame[3] = code;         // Rolling code
+    frame[4] = remote >> 16; // Remote address
+    frame[5] = remote >> 8; // Remote address
+    frame[6] = remote;         // Remote address
+    // frame[7] = 0x80;
+    // frame[8] = 0x0;
+    // frame[9] = 0x0;
+
+    // NODE_DBG("Frame:\t\t\t%02x %02x %02x %02x %02x %02x %02x\n", frame[0], frame[1], frame[2], frame[3], frame[4], frame[5], frame[6]);
+    // Checksum calculation: a XOR of all the nibbles
+    uint8_t checksum = 0;
+    for(uint8_t i = 0; i < 7; i++) {
+        checksum = checksum ^ frame[i] ^ (frame[i] >> 4);
+    }
+    checksum &= 0b1111; // We keep the last 4 bits only
+
+    //Checksum integration
+    frame[1] |= checksum; //    If a XOR of all the nibbles is equal to 0, the blinds will consider the checksum ok.
+    // NODE_DBG("With checksum:\t%02x %02x %02x %02x %02x %02x %02x\n", frame[0], frame[1], frame[2], frame[3], frame[4], frame[5], frame[6]);
+
+    // Obfuscation: a XOR of all the uint8_ts
+    for(uint8_t i = 1; i < 7; i++) {
+        frame[i] ^= frame[i-1];
+    }
+    // NODE_DBG("Obfuscated:\t\t%02x %02x %02x %02x %02x %02x %02x\n", frame[0], frame[1], frame[2], frame[3], frame[4], frame[5], frame[6]);
+}
+
+static void somfy_transmissionDone (task_param_t arg)
+{
+    lua_State *L = lua_getstate();
+    lua_rawgeti (L, LUA_REGISTRYINDEX, lua_done_ref);
+    luaL_unref (L, LUA_REGISTRYINDEX, lua_done_ref);
+    lua_done_ref = LUA_NOREF;
+    lua_call (L, 0, 0);
+}
+
+static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
+    (void) p;
+    //  NODE_DBG("%d\t%d\n", signalindex, subindex);
+    switch (signalindex) {
+        case 0:
+            subindex = 0;
+            if(sync == 2) { // Only with the first frame.
+                //Wake-up pulse & Silence
+                DIRECT_WRITE_HIGH(pin);
+                signalindex++;
+                // delayMicroseconds(9415);
+                break;
+            } else {
+                signalindex++; signalindex++; //no break means: go directly to step 3
+            }
+        case 1:
+            //Wake-up pulse & Silence
+            DIRECT_WRITE_LOW(pin);
+            signalindex++;
+            // delayMicroseconds(89565);
+            break;
+        case 2:
+            signalindex++; 
+            // no break means go directly to step 3
+            // a "useless" step to allow repeating the hardware sync w/o the silence after wake-up pulse
+        case 3:
+            // Hardware sync: two sync for the first frame, seven for the following ones.
+            DIRECT_WRITE_HIGH(pin);
+            signalindex++;
+            // delayMicroseconds(4*SYMBOL);
+            break;
+        case 4:
+            DIRECT_WRITE_LOW(pin);
+            subindex++;
+            if (subindex < sync) {signalindex--;} else {signalindex++;}
+            // delayMicroseconds(4*SYMBOL);
+            break;
+        case 5:
+            // Software sync
+            DIRECT_WRITE_HIGH(pin);
+            signalindex++;
+            // delayMicroseconds(4550);
+            break;
+        case 6:
+            DIRECT_WRITE_LOW(pin);
+            signalindex++;
+            subindex=0;
+            // delayMicroseconds(SYMBOL);
+            break;
+        case 7:
+            //Data: bits are sent one by one, starting with the MSB.
+            bitcondition = ((frame[subindex/8] >> (7 - (subindex%8))) & 1) == 1;
+            if(bitcondition) {
+                DIRECT_WRITE_LOW(pin);
+            }
+            else {
+                DIRECT_WRITE_HIGH(pin);
+            }
+            signalindex++;
+            // delayMicroseconds(SYMBOL);
+            break;
+        case 8:
+            //Data: bits are sent one by one, starting with the MSB.
+            if(bitcondition) {
+                DIRECT_WRITE_HIGH(pin);
+            }
+            else {
+                DIRECT_WRITE_LOW(pin);
+            }
+            
+            if (subindex<56) {
+                subindex++;
+                signalindex--;
+            }
+            else {
+                signalindex++;
+            }
+            // delayMicroseconds(SYMBOL);
+            break;
+        case 9:
+            DIRECT_WRITE_LOW(pin);
+            signalindex++;
+            // delayMicroseconds(30415); // Inter-frame silence
+            break;
+        case 10:
+            repeatindex++;
+            if (repeatindex<repeat) {
+                DIRECT_WRITE_HIGH(pin); //start repeat from step 3, but don't wait as after step 1
+                signalindex=4; subindex=0; sync=7;
+            } else {
+                platform_hw_timer_close(TIMER_OWNER);
+                if (lua_done_ref != LUA_NOREF) {
+                    task_post_low (done_taskid, (task_param_t)0);
+                }
+            }
+            break;
+    }
+    if (signalindex<10) {
+        platform_hw_timer_arm_ticks(TIMER_OWNER, delay[signalindex-1]);
+    }
+}
+
+static int somfy_lua_sendcommand(lua_State* L) { // pin, remote, command, rolling_code, num_repeat, callback
+    if (!lua_isnumber(L, 4)) {
+        return luaL_error(L, "wrong arg range");
+    }
+    pin = luaL_checkinteger(L, 1);
+    uint64_t remote = luaL_checkinteger(L, 2);
+    uint8_t cmd = luaL_checkinteger(L, 3);
+    uint16_t code = luaL_checkinteger(L, 4);
+    repeat=luaL_optint( L, 5, 2 );
+
+    luaL_argcheck(L, platform_gpio_exists(pin), 1, "Invalid pin");
+
+    luaL_unref(L, LUA_REGISTRYINDEX, lua_done_ref);
+    if (!lua_isnoneornil(L, 6)) {
+        lua_pushvalue(L, 6);
+        lua_done_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+    } else {
+        lua_done_ref = LUA_NOREF;
+    }
+
+    MOD_CHECK_ID(gpio, pin);
+    platform_gpio_mode(pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_PULLUP);
+
+    buildFrame(frame, remote, cmd, code);
+    
+    if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, TRUE)) {
+        // Failed to init the timer
+        luaL_error(L, "Unable to initialize timer");
+    }
+    platform_hw_timer_set_func(TIMER_OWNER, sendCommand, 0);
+    sync=2;
+    signalindex=0; repeatindex=0;
+    sendCommand(0);
+    return 0;
+}
+
+static const LUA_REG_TYPE somfy_map[] = {
+    { LSTRKEY( "UP" ),    LNUMVAL( SOMFY_UP ) },
+    { LSTRKEY( "DOWN" ),    LNUMVAL( SOMFY_DOWN ) },
+    { LSTRKEY( "PROG" ),    LNUMVAL( SOMFY_PROG ) },
+    { LSTRKEY( "STOP" ),    LNUMVAL( SOMFY_STOP ) },
+    { LSTRKEY( "sendcommand" ), LFUNCVAL(somfy_lua_sendcommand)},
+    { LNILKEY, LNILVAL}
+};
+
+int luaopen_somfy( lua_State *L ) {
+    done_taskid = task_get_id((task_callback_t) somfy_transmissionDone);
+    return 0;
+}
+
+NODEMCU_MODULE(SOMFY, "somfy", somfy_map, luaopen_somfy);

+ 69 - 224
app/modules/u8g.c

@@ -2,24 +2,13 @@
 
 #include "module.h"
 #include "lauxlib.h"
-#include "platform.h"
-
-#include "c_stdlib.h"
+#include "lmem.h"
 
 #include "u8g.h"
+#include "u8g_glue.h"
 
 #include "u8g_config.h"
 
-struct _lu8g_userdata_t
-{
-    u8g_t u8g;
-};
-
-typedef struct _lu8g_userdata_t lu8g_userdata_t;
-
-// shorthand macro for the u8g structure inside the userdata
-#define LU8G (&(lud->u8g))
-
 
 // helper function: retrieve and check userdata argument
 static lu8g_userdata_t *get_lud( lua_State *L )
@@ -780,214 +769,6 @@ static int lu8g_setFontLineSpacingFactor( lua_State *L )
 }
 
 
-// ------------------------------------------------------------
-// comm functions
-//
-#define I2C_CMD_MODE    0x000
-#define I2C_DATA_MODE   0x040
-
-#define ESP_I2C_ID 0
-
-
-static uint8_t do_i2c_start(uint8_t id, uint8_t sla)
-{
-    platform_i2c_send_start( id );
-
-    // ignore return value -> tolerate missing ACK
-    platform_i2c_send_address( id, sla, PLATFORM_I2C_DIRECTION_TRANSMITTER );
-
-    return 1;
-}
-
-static uint8_t u8g_com_esp8266_ssd_start_sequence(u8g_t *u8g)
-{
-    /* are we requested to set the a0 state? */
-    if ( u8g->pin_list[U8G_PI_SET_A0] == 0 )
-        return 1;
-
-    /* setup bus, might be a repeated start */
-    if ( do_i2c_start( ESP_I2C_ID, u8g->i2c_addr ) == 0 )
-        return 0;
-    if ( u8g->pin_list[U8G_PI_A0_STATE] == 0 )
-    {
-        // ignore return value -> tolerate missing ACK
-        if ( platform_i2c_send_byte( ESP_I2C_ID, I2C_CMD_MODE ) == 0 )
-            ; //return 0;
-    }
-    else
-    {
-        platform_i2c_send_byte( ESP_I2C_ID, I2C_DATA_MODE );
-    }
-
-    u8g->pin_list[U8G_PI_SET_A0] = 0;
-    return 1;
-}
-
-
-static void lu8g_digital_write( u8g_t *u8g, uint8_t pin_index, uint8_t value )
-{
-    uint8_t pin;
-
-    pin = u8g->pin_list[pin_index];
-    if ( pin != U8G_PIN_NONE )
-        platform_gpio_write( pin, value );
-}
-
-void u8g_Delay(u8g_t *u8g, uint16_t msec)
-{
-    const uint16_t chunk = 50;
-
-    if (u8g->use_delay == 0)
-        return;
-
-    while (msec > chunk)
-    {
-        os_delay_us( chunk*1000 );
-        msec -= chunk;
-    }
-    if (msec > 0)
-        os_delay_us( msec*1000 );
-}
-void u8g_MicroDelay(void)
-{
-    os_delay_us( 1 );
-}
-void u8g_10MicroDelay(void)
-{
-    os_delay_us( 10 );
-}
-
-
-uint8_t u8g_com_esp8266_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr)
-{
-    switch(msg)
-    {
-    case U8G_COM_MSG_STOP:
-        break;
-    
-    case U8G_COM_MSG_INIT:
-        // we assume that the SPI interface was already initialized
-        // just care for the /CS and D/C pins
-        lu8g_digital_write( u8g, U8G_PI_CS, PLATFORM_GPIO_HIGH );
-        platform_gpio_mode( u8g->pin_list[U8G_PI_CS], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT );
-        platform_gpio_mode( u8g->pin_list[U8G_PI_A0], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT );
-        break;
-    
-    case U8G_COM_MSG_ADDRESS:                     /* define cmd (arg_val = 0) or data mode (arg_val = 1) */
-        lu8g_digital_write( u8g, U8G_PI_A0, arg_val == 0 ? PLATFORM_GPIO_LOW : PLATFORM_GPIO_HIGH );
-        break;
-
-    case U8G_COM_MSG_CHIP_SELECT:
-        if (arg_val == 0)
-        {
-            /* disable */
-            lu8g_digital_write( u8g, U8G_PI_CS, PLATFORM_GPIO_HIGH );
-        }
-        else
-        {
-            /* enable */
-            //u8g_com_arduino_digital_write(u8g, U8G_PI_SCK, LOW);
-            lu8g_digital_write( u8g, U8G_PI_CS, PLATFORM_GPIO_LOW );
-        }
-        break;
-      
-    case U8G_COM_MSG_RESET:
-        if ( u8g->pin_list[U8G_PI_RESET] != U8G_PIN_NONE )
-            lu8g_digital_write( u8g, U8G_PI_RESET, arg_val == 0 ? PLATFORM_GPIO_LOW : PLATFORM_GPIO_HIGH );
-        break;
-    
-    case U8G_COM_MSG_WRITE_BYTE:
-        platform_spi_send( 1, 8, arg_val );
-        break;
-    
-    case U8G_COM_MSG_WRITE_SEQ:
-    case U8G_COM_MSG_WRITE_SEQ_P:
-        {
-            register uint8_t *ptr = arg_ptr;
-            while( arg_val > 0 )
-            {
-                platform_spi_send( 1, 8, *ptr++ );
-                arg_val--;
-            }
-        }
-        break;
-    }
-    return 1;
-}
-
-
-uint8_t u8g_com_esp8266_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr)
-{
-    switch(msg)
-    {
-    case U8G_COM_MSG_INIT:
-        // we assume that the i2c bus was already initialized
-        //u8g_i2c_init(u8g->pin_list[U8G_PI_I2C_OPTION]);
-
-        break;
-    
-    case U8G_COM_MSG_STOP:
-        break;
-
-    case U8G_COM_MSG_RESET:
-        /* Currently disabled, but it could be enable. Previous restrictions have been removed */
-        /* u8g_com_arduino_digital_write(u8g, U8G_PI_RESET, arg_val); */
-        break;
-      
-    case U8G_COM_MSG_CHIP_SELECT:
-        u8g->pin_list[U8G_PI_A0_STATE] = 0;
-        u8g->pin_list[U8G_PI_SET_A0] = 1;		/* force a0 to set again, also forces start condition */
-        if ( arg_val == 0 )
-        {
-            /* disable chip, send stop condition */
-            platform_i2c_send_stop( ESP_I2C_ID );
-        }
-        else
-        {
-            /* enable, do nothing: any byte writing will trigger the i2c start */
-        }
-        break;
-
-    case U8G_COM_MSG_WRITE_BYTE:
-        //u8g->pin_list[U8G_PI_SET_A0] = 1;
-        if ( u8g_com_esp8266_ssd_start_sequence(u8g) == 0 )
-            return platform_i2c_send_stop( ESP_I2C_ID ), 0;
-        // ignore return value -> tolerate missing ACK
-        if ( platform_i2c_send_byte( ESP_I2C_ID, arg_val) == 0 )
-            ; //return platform_i2c_send_stop( ESP_I2C_ID ), 0;
-        // platform_i2c_send_stop( ESP_I2C_ID );
-        break;
-    
-    case U8G_COM_MSG_WRITE_SEQ:
-    case U8G_COM_MSG_WRITE_SEQ_P:
-        //u8g->pin_list[U8G_PI_SET_A0] = 1;
-        if ( u8g_com_esp8266_ssd_start_sequence(u8g) == 0 )
-            return platform_i2c_send_stop( ESP_I2C_ID ), 0;
-        {
-            register uint8_t *ptr = arg_ptr;
-            while( arg_val > 0 )
-            {
-                // ignore return value -> tolerate missing ACK
-                if ( platform_i2c_send_byte( ESP_I2C_ID, *ptr++) == 0 )
-                    ; //return platform_i2c_send_stop( ESP_I2C_ID ), 0;
-                arg_val--;
-            }
-        }
-        // platform_i2c_send_stop( ESP_I2C_ID );
-        break;
-
-    case U8G_COM_MSG_ADDRESS:                     /* define cmd (arg_val = 0) or data mode (arg_val = 1) */
-        u8g->pin_list[U8G_PI_A0_STATE] = arg_val;
-        u8g->pin_list[U8G_PI_SET_A0] = 1;		/* force a0 to set again */
-    
-        break;
-    }
-    return 1;
-}
-
-
-
-
 // device destructor
 static int lu8g_close_display( lua_State *L )
 {
@@ -996,6 +777,19 @@ static int lu8g_close_display( lua_State *L )
     if ((lud = get_lud( L )) == NULL)
         return 0;
 
+    if (lud->cb_ref != LUA_NOREF) {
+        // this is the fb_rle device
+        u8g_dev_t *fb_dev = LU8G->dev;
+        u8g_pb_t *fb_dev_pb = (u8g_pb_t *)(fb_dev->dev_mem);
+        uint8_t *fb_dev_buf = fb_dev_pb->buf;
+
+        luaM_free( L, fb_dev_buf );
+        luaM_free( L, fb_dev_pb );
+        luaM_free( L, fb_dev );
+
+        luaL_unref( L, lud->cb_ref, LUA_REGISTRYINDEX );
+    }
+
     return 0;
 }
 
@@ -1016,9 +810,10 @@ static int lu8g_close_display( lua_State *L )
             return luaL_error( L, "i2c address required" );             \
                                                                         \
         lu8g_userdata_t *lud = (lu8g_userdata_t *) lua_newuserdata( L, sizeof( lu8g_userdata_t ) ); \
+        lud->cb_ref = LUA_NOREF;                                        \
                                                                         \
-        lud->u8g.i2c_addr = (uint8_t)addr;                              \
-        lud->u8g.use_delay = del > 0 ? 1 : 0;                           \
+        lud->i2c_addr = (uint8_t)addr;                                  \
+        lud->use_delay = del > 0 ? 1 : 0;                               \
                                                                         \
         u8g_InitI2C( LU8G, &u8g_dev_ ## device, U8G_I2C_OPT_NONE);      \
                                                                         \
@@ -1049,8 +844,9 @@ U8G_DISPLAY_TABLE_I2C
         unsigned del = luaL_optinteger( L, 4, 0 );                      \
                                                                         \
         lu8g_userdata_t *lud = (lu8g_userdata_t *) lua_newuserdata( L, sizeof( lu8g_userdata_t ) ); \
+        lud->cb_ref = LUA_NOREF;                                        \
                                                                         \
-        lud->u8g.use_delay = del > 0 ? 1 : 0;                           \
+        lud->use_delay = del > 0 ? 1 : 0;                               \
                                                                         \
         u8g_InitHWSPI( LU8G, &u8g_dev_ ## device, cs, dc, res );        \
                                                                         \
@@ -1064,6 +860,53 @@ U8G_DISPLAY_TABLE_I2C
 // Unroll the display table and insert binding functions for SPI based displays.
 U8G_DISPLAY_TABLE_SPI
 //
+//
+//
+// This display forwards the framebuffer contents as run-length encoded chunks to a Lua callback
+static int lu8g_fb_rle( lua_State *L ) {
+    lu8g_userdata_t *lud;
+
+    if ((lua_type( L, 1 ) != LUA_TFUNCTION) &&
+        (lua_type( L, 1 ) != LUA_TLIGHTFUNCTION)) {
+        luaL_typerror( L, 1, "function" );
+    }
+
+    int width = luaL_checkint( L, 2 );
+    int height = luaL_checkint( L, 3 );
+
+    luaL_argcheck( L, (width > 0) && (width < 256) && ((width % 8) == 0), 2, "invalid width" );
+    luaL_argcheck( L, (height > 0) && (height < 256) && ((height % 8) == 0), 3, "invalid height" );
+
+    // construct display device structures manually because width and height are configurable
+    uint8_t *fb_dev_buf = (uint8_t *)luaM_malloc( L, width );
+
+    u8g_pb_t *fb_dev_pb = (u8g_pb_t *)luaM_malloc( L, sizeof( u8g_pb_t ) );
+    fb_dev_pb->p.page_height  = 8;
+    fb_dev_pb->p.total_height = height;
+    fb_dev_pb->p.page_y0      = 0;
+    fb_dev_pb->p.page_y1      = 0;
+    fb_dev_pb->p.page         = 0;
+    fb_dev_pb->width = width;
+    fb_dev_pb->buf   = fb_dev_buf;
+
+    u8g_dev_t *fb_dev = (u8g_dev_t *)luaM_malloc( L, sizeof( u8g_dev_t ) );
+    fb_dev->dev_fn = u8g_dev_gen_fb_fn;
+    fb_dev->dev_mem = fb_dev_pb;
+    fb_dev->com_fn = u8g_com_esp8266_fbrle_fn;
+
+    lud = (lu8g_userdata_t *) lua_newuserdata( L, sizeof( lu8g_userdata_t ) );
+    lua_pushvalue( L, 1 );  // copy argument (func) to the top of stack
+    lud->cb_ref = luaL_ref( L, LUA_REGISTRYINDEX );
+
+    /* set metatable for userdata */
+    luaL_getmetatable(L, "u8g.display");
+    lua_setmetatable(L, -2);
+
+    u8g_Init8BitFixedPort( LU8G, fb_dev, width, height, 0, 0, 0);
+
+    return 1;
+}
+//
 // ***************************************************************************
 
 
@@ -1137,6 +980,8 @@ static const LUA_REG_TYPE lu8g_map[] = {
 #define U8G_FONT_TABLE_ENTRY(font) \
   { LSTRKEY( #font ),              LUDATA( (void *)(u8g_ ## font) ) },
   U8G_FONT_TABLE
+  //
+  { LSTRKEY( "fb_rle" ),  LFUNCVAL( lu8g_fb_rle ) },
   // Options for circle/ ellipse drawing
   { LSTRKEY( "DRAW_UPPER_RIGHT" ), LNUMVAL( U8G_DRAW_UPPER_RIGHT ) },
   { LSTRKEY( "DRAW_UPPER_LEFT" ),  LNUMVAL( U8G_DRAW_UPPER_LEFT ) },

+ 347 - 0
app/modules/u8g_glue.c

@@ -0,0 +1,347 @@
+/*
+  Functions for integrating U8glib into the nodemcu platform.
+*/
+
+#include "lauxlib.h"
+#include "platform.h"
+
+#include "c_stdlib.h"
+
+#include "u8g.h"
+#include "u8g_glue.h"
+
+
+// ------------------------------------------------------------
+// comm functions
+//
+#define I2C_CMD_MODE    0x000
+#define I2C_DATA_MODE   0x040
+
+#define ESP_I2C_ID 0
+
+
+static uint8_t do_i2c_start(uint8_t id, uint8_t sla)
+{
+    platform_i2c_send_start( id );
+
+    // ignore return value -> tolerate missing ACK
+    platform_i2c_send_address( id, sla, PLATFORM_I2C_DIRECTION_TRANSMITTER );
+
+    return 1;
+}
+
+static uint8_t u8g_com_esp8266_ssd_start_sequence(struct _lu8g_userdata_t *lu8g)
+{
+    /* are we requested to set the a0 state? */
+    if ( lu8g->u8g.pin_list[U8G_PI_SET_A0] == 0 )
+        return 1;
+
+    /* setup bus, might be a repeated start */
+    if ( do_i2c_start( ESP_I2C_ID, lu8g->i2c_addr ) == 0 )
+        return 0;
+    if ( lu8g->u8g.pin_list[U8G_PI_A0_STATE] == 0 )
+    {
+        // ignore return value -> tolerate missing ACK
+        if ( platform_i2c_send_byte( ESP_I2C_ID, I2C_CMD_MODE ) == 0 )
+            ; //return 0;
+    }
+    else
+    {
+        platform_i2c_send_byte( ESP_I2C_ID, I2C_DATA_MODE );
+    }
+
+    lu8g->u8g.pin_list[U8G_PI_SET_A0] = 0;
+    return 1;
+}
+
+
+static void lu8g_digital_write( struct _lu8g_userdata_t *lu8g, uint8_t pin_index, uint8_t value )
+{
+    uint8_t pin;
+
+    pin = lu8g->u8g.pin_list[pin_index];
+    if ( pin != U8G_PIN_NONE )
+        platform_gpio_write( pin, value );
+}
+
+void u8g_Delay(u8g_t *u8g, uint16_t msec)
+{
+    struct _lu8g_userdata_t *lu8g = (struct _lu8g_userdata_t *)u8g;
+    const uint16_t chunk = 50;
+
+    if (lu8g->use_delay == 0)
+        return;
+
+    while (msec > chunk)
+    {
+        os_delay_us( chunk*1000 );
+        msec -= chunk;
+    }
+    if (msec > 0)
+        os_delay_us( msec*1000 );
+}
+void u8g_MicroDelay(void)
+{
+    os_delay_us( 1 );
+}
+void u8g_10MicroDelay(void)
+{
+    os_delay_us( 10 );
+}
+
+
+uint8_t u8g_com_esp8266_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr)
+{
+    struct _lu8g_userdata_t *lu8g = (struct _lu8g_userdata_t *)u8g;
+
+    switch(msg)
+    {
+    case U8G_COM_MSG_STOP:
+        break;
+    
+    case U8G_COM_MSG_INIT:
+        // we assume that the SPI interface was already initialized
+        // just care for the /CS and D/C pins
+        lu8g_digital_write( lu8g, U8G_PI_CS, PLATFORM_GPIO_HIGH );
+        platform_gpio_mode( lu8g->u8g.pin_list[U8G_PI_CS], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT );
+        platform_gpio_mode( lu8g->u8g.pin_list[U8G_PI_A0], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT );
+        break;
+    
+    case U8G_COM_MSG_ADDRESS:                     /* define cmd (arg_val = 0) or data mode (arg_val = 1) */
+        lu8g_digital_write( lu8g, U8G_PI_A0, arg_val == 0 ? PLATFORM_GPIO_LOW : PLATFORM_GPIO_HIGH );
+        break;
+
+    case U8G_COM_MSG_CHIP_SELECT:
+        if (arg_val == 0)
+        {
+            /* disable */
+            lu8g_digital_write( lu8g, U8G_PI_CS, PLATFORM_GPIO_HIGH );
+        }
+        else
+        {
+            /* enable */
+            lu8g_digital_write( lu8g, U8G_PI_CS, PLATFORM_GPIO_LOW );
+        }
+        break;
+      
+    case U8G_COM_MSG_RESET:
+        if ( lu8g->u8g.pin_list[U8G_PI_RESET] != U8G_PIN_NONE )
+            lu8g_digital_write( lu8g, U8G_PI_RESET, arg_val == 0 ? PLATFORM_GPIO_LOW : PLATFORM_GPIO_HIGH );
+        break;
+    
+    case U8G_COM_MSG_WRITE_BYTE:
+        platform_spi_send( 1, 8, arg_val );
+        break;
+    
+    case U8G_COM_MSG_WRITE_SEQ:
+    case U8G_COM_MSG_WRITE_SEQ_P:
+        {
+            register uint8_t *ptr = arg_ptr;
+            while( arg_val > 0 )
+            {
+                platform_spi_send( 1, 8, *ptr++ );
+                arg_val--;
+            }
+        }
+        break;
+    }
+    return 1;
+}
+
+
+uint8_t u8g_com_esp8266_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr)
+{
+    struct _lu8g_userdata_t *lu8g = (struct _lu8g_userdata_t *)u8g;
+
+    switch(msg)
+    {
+    case U8G_COM_MSG_INIT:
+        // we assume that the i2c bus was already initialized
+        //u8g_i2c_init(u8g->pin_list[U8G_PI_I2C_OPTION]);
+
+        break;
+    
+    case U8G_COM_MSG_STOP:
+        break;
+
+    case U8G_COM_MSG_RESET:
+        /* Currently disabled, but it could be enable. Previous restrictions have been removed */
+        /* u8g_com_arduino_digital_write(u8g, U8G_PI_RESET, arg_val); */
+        break;
+      
+    case U8G_COM_MSG_CHIP_SELECT:
+        lu8g->u8g.pin_list[U8G_PI_A0_STATE] = 0;
+        lu8g->u8g.pin_list[U8G_PI_SET_A0] = 1;		/* force a0 to set again, also forces start condition */
+        if ( arg_val == 0 )
+        {
+            /* disable chip, send stop condition */
+            platform_i2c_send_stop( ESP_I2C_ID );
+        }
+        else
+        {
+            /* enable, do nothing: any byte writing will trigger the i2c start */
+        }
+        break;
+
+    case U8G_COM_MSG_WRITE_BYTE:
+        //u8g->pin_list[U8G_PI_SET_A0] = 1;
+        if ( u8g_com_esp8266_ssd_start_sequence(lu8g) == 0 )
+            return platform_i2c_send_stop( ESP_I2C_ID ), 0;
+        // ignore return value -> tolerate missing ACK
+        if ( platform_i2c_send_byte( ESP_I2C_ID, arg_val) == 0 )
+            ; //return platform_i2c_send_stop( ESP_I2C_ID ), 0;
+        // platform_i2c_send_stop( ESP_I2C_ID );
+        break;
+    
+    case U8G_COM_MSG_WRITE_SEQ:
+    case U8G_COM_MSG_WRITE_SEQ_P:
+        //u8g->pin_list[U8G_PI_SET_A0] = 1;
+        if ( u8g_com_esp8266_ssd_start_sequence(lu8g) == 0 )
+            return platform_i2c_send_stop( ESP_I2C_ID ), 0;
+        {
+            register uint8_t *ptr = arg_ptr;
+            while( arg_val > 0 )
+            {
+                // ignore return value -> tolerate missing ACK
+                if ( platform_i2c_send_byte( ESP_I2C_ID, *ptr++) == 0 )
+                    ; //return platform_i2c_send_stop( ESP_I2C_ID ), 0;
+                arg_val--;
+            }
+        }
+        // platform_i2c_send_stop( ESP_I2C_ID );
+        break;
+
+    case U8G_COM_MSG_ADDRESS:                     /* define cmd (arg_val = 0) or data mode (arg_val = 1) */
+        lu8g->u8g.pin_list[U8G_PI_A0_STATE] = arg_val;
+        lu8g->u8g.pin_list[U8G_PI_SET_A0] = 1;		/* force a0 to set again */
+    
+        break;
+    }
+    return 1;
+}
+
+
+// ***************************************************************************
+// Generic framebuffer device and RLE comm driver
+//
+uint8_t u8g_dev_gen_fb_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t msg, void *arg)
+{
+  switch(msg)
+  {
+    case U8G_DEV_MSG_PAGE_FIRST:
+      // tell comm driver to start new framebuffer
+      u8g_SetChipSelect(u8g, dev, 1);
+      break;
+    case U8G_DEV_MSG_PAGE_NEXT:
+      {
+        u8g_pb_t *pb = (u8g_pb_t *)(dev->dev_mem);
+        if ( u8g_pb_WriteBuffer(pb, u8g, dev) == 0 )
+          return 0;
+      }
+      break;
+  }
+
+  return u8g_dev_pb8v1_base_fn(u8g, dev, msg, arg);
+}
+
+static int bit_at( uint8_t *buf, int line, int x )
+{
+    uint8_t byte = buf[x];
+
+    return buf[x] & (1 << line) ? 1 : 0;
+}
+
+struct _lu8g_fbrle_item
+{
+    uint8_t start_x;
+    uint8_t len;
+};
+
+struct _lu8g_fbrle_line
+{
+    uint8_t num_valid;
+    struct _lu8g_fbrle_item items[0];
+};
+
+uint8_t u8g_com_esp8266_fbrle_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr)
+{
+    struct _lu8g_userdata_t *lud = (struct _lu8g_userdata_t *)u8g;
+
+    switch(msg)
+    {
+    case U8G_COM_MSG_INIT:
+        // allocate memory -> done
+        // init buffer
+        break;
+
+    case U8G_COM_MSG_CHIP_SELECT:
+        if (arg_val == 1) {
+            // new frame starts
+            if (lud->cb_ref != LUA_NOREF) {
+                // fire callback with nil argument
+                lua_State *L = lua_getstate();
+                lua_rawgeti( L, LUA_REGISTRYINDEX, lud->cb_ref );
+                lua_pushnil( L );
+                lua_call( L, 1, 0 );
+            }
+        }
+        break;
+
+    case U8G_COM_MSG_WRITE_SEQ:
+    case U8G_COM_MSG_WRITE_SEQ_P:
+        {
+            uint8_t xwidth = u8g->pin_list[U8G_PI_EN];
+            size_t fbrle_line_size = sizeof( struct _lu8g_fbrle_line ) + sizeof( struct _lu8g_fbrle_item ) * (xwidth/2);
+            int num_lines = arg_val / (xwidth/8);
+            uint8_t *buf = (uint8_t *)arg_ptr;
+
+            struct _lu8g_fbrle_line *fbrle_line;
+            if (!(fbrle_line = (struct _lu8g_fbrle_line *)c_malloc( fbrle_line_size ))) {
+                break;
+            }
+
+            for (int line = 0; line < num_lines; line++) {
+                int start_run = -1;
+                fbrle_line->num_valid = 0;
+
+                for (int x = 0; x < xwidth; x++) {
+                    if (bit_at( buf, line, x ) == 0) {
+                        if (start_run >= 0) {
+                            // inside run, end it and enter result
+                            fbrle_line->items[fbrle_line->num_valid].start_x = start_run;
+                            fbrle_line->items[fbrle_line->num_valid++].len = x - start_run;
+                            //NODE_ERR( "         line: %d x: %d len: %d\n", line, start_run, x - start_run );
+                            start_run = -1;
+                        }
+                    } else {
+                        if (start_run < 0) {
+                            // outside run, start it
+                            start_run = x;
+                        }
+                    }
+
+                    if (fbrle_line->num_valid >= xwidth/2) break;
+                }
+
+                // active run?
+                if (start_run >= 0 && fbrle_line->num_valid < xwidth/2) {
+                    fbrle_line->items[fbrle_line->num_valid].start_x = start_run;
+                    fbrle_line->items[fbrle_line->num_valid++].len = xwidth - start_run;
+                }
+
+                // line done, trigger callback
+                if (lud->cb_ref != LUA_NOREF) {
+                    lua_State *L = lua_getstate();
+
+                    lua_rawgeti( L, LUA_REGISTRYINDEX, lud->cb_ref );
+                    lua_pushlstring( L, (const char *)fbrle_line, fbrle_line_size );
+                    lua_call( L, 1, 0 );
+                }
+            }
+
+            c_free( fbrle_line );
+        }
+        break;
+    }
+    return 1;
+}

+ 22 - 0
app/modules/u8g_glue.h

@@ -0,0 +1,22 @@
+
+#ifndef _U8G_GLUE_H_
+#define _U8G_GLUE_H_
+
+#include "u8g.h"
+
+struct _lu8g_userdata_t
+{
+    u8g_t u8g;
+    uint8_t i2c_addr;
+    uint8_t use_delay;
+    int cb_ref;
+};
+typedef struct _lu8g_userdata_t lu8g_userdata_t;
+
+// shorthand macro for the u8g structure inside the userdata
+#define LU8G (&(lud->u8g))
+
+uint8_t u8g_com_esp8266_fbrle_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr);
+uint8_t u8g_dev_gen_fb_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t msg, void *arg);
+
+#endif

+ 58 - 6
app/modules/websocket.c

@@ -15,6 +15,7 @@
 
 #include "c_types.h"
 #include "c_string.h"
+#include "c_stdlib.h"
 
 #include "websocketclient.h"
 
@@ -45,7 +46,7 @@ static void websocketclient_onConnectionCallback(ws_info *ws) {
   }
 }
 
-static void websocketclient_onReceiveCallback(ws_info *ws, char *message, int opCode) {
+static void websocketclient_onReceiveCallback(ws_info *ws, int len, char *message, int opCode) {
   NODE_DBG("websocketclient_onReceiveCallback\n");
 
   lua_State *L = lua_getstate();
@@ -59,7 +60,7 @@ static void websocketclient_onReceiveCallback(ws_info *ws, char *message, int op
   if (data->onReceive != LUA_NOREF) {
     lua_rawgeti(L, LUA_REGISTRYINDEX, data->onReceive); // load the callback function
     lua_rawgeti(L, LUA_REGISTRYINDEX, data->self_ref);  // pass itself, #1 callback argument
-    lua_pushstring(L, message); // #2 callback argument
+    lua_pushlstring(L, message, len); // #2 callback argument
     lua_pushnumber(L, opCode); // #3 callback argument
     lua_call(L, 3, 0);
   }
@@ -102,6 +103,7 @@ static int websocket_createClient(lua_State *L) {
 
   ws_info *ws = (ws_info *) lua_newuserdata(L, sizeof(ws_info));
   ws->connectionState = 0;
+  ws->extraHeaders = NULL;
   ws->onConnection = &websocketclient_onConnectionCallback;
   ws->onReceive = &websocketclient_onReceiveCallback;
   ws->onFailure = &websocketclient_onCloseCallback;
@@ -118,7 +120,6 @@ static int websocketclient_on(lua_State *L) {
   NODE_DBG("websocketclient_on\n");
 
   ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);  
-  luaL_argcheck(L, ws, 1, "Client websocket expected");
 
   ws_data *data = (ws_data *) ws->reservedData;
 
@@ -170,7 +171,6 @@ static int websocketclient_connect(lua_State *L) {
   NODE_DBG("websocketclient_connect is called.\n");
 
   ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);  
-  luaL_argcheck(L, ws, 1, "Client websocket expected");
 
   ws_data *data = (ws_data *) ws->reservedData;
 
@@ -188,11 +188,61 @@ static int websocketclient_connect(lua_State *L) {
   return 0;
 }
 
+static header_t *realloc_headers(header_t *headers, int new_size) {
+  if(headers) {
+    for(header_t *header = headers; header->key; header++) {
+      c_free(header->value);
+      c_free(header->key);
+    }
+    c_free(headers);
+  }
+  if(!new_size)
+    return NULL;
+  return (header_t *)c_malloc(sizeof(header_t) * (new_size + 1));
+}
+
+static int websocketclient_config(lua_State *L) {
+  NODE_DBG("websocketclient_config is called.\n");
+
+  ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);
+
+  ws_data *data = (ws_data *) ws->reservedData;
+
+  luaL_checktype(L, 2, LUA_TTABLE);
+  lua_getfield(L, 2, "headers");
+  if(lua_istable(L, -1)) {
+
+    lua_pushnil(L);
+    int size = 0;
+    while(lua_next(L, -2)) {
+      size++;
+      lua_pop(L, 1);
+    }
+
+    ws->extraHeaders = realloc_headers(ws->extraHeaders, size);
+    if(ws->extraHeaders) {
+      header_t *header = ws->extraHeaders;
+
+      lua_pushnil(L);
+      while(lua_next(L, -2)) {
+        header->key = c_strdup(lua_tostring(L, -2));
+        header->value = c_strdup(lua_tostring(L, -1));
+        header++;
+        lua_pop(L, 1);
+      }
+
+      header->key = header->value = NULL;
+    }
+  }
+  lua_pop(L, 1); // pop headers
+
+  return 0;
+}
+
 static int websocketclient_send(lua_State *L) {
   NODE_DBG("websocketclient_send is called.\n");
 
   ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);  
-  luaL_argcheck(L, ws, 1, "Client websocket expected");
 
   ws_data *data = (ws_data *) ws->reservedData;
 
@@ -225,7 +275,8 @@ static int websocketclient_gc(lua_State *L) {
   NODE_DBG("websocketclient_gc\n");
 
   ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT);  
-  luaL_argcheck(L, ws, 1, "Client websocket expected");
+
+  ws->extraHeaders = realloc_headers(ws->extraHeaders, 0);
 
   ws_data *data = (ws_data *) ws->reservedData;
 
@@ -265,6 +316,7 @@ static const LUA_REG_TYPE websocket_map[] =
 static const LUA_REG_TYPE websocketclient_map[] =
 {
   { LSTRKEY("on"), LFUNCVAL(websocketclient_on) },
+  { LSTRKEY("config"), LFUNCVAL(websocketclient_config) },
   { LSTRKEY("connect"), LFUNCVAL(websocketclient_connect) },
   { LSTRKEY("send"), LFUNCVAL(websocketclient_send) },
   { LSTRKEY("close"), LFUNCVAL(websocketclient_close) },

Файловите разлики са ограничени, защото са твърде много
+ 494 - 303
app/modules/wifi.c


+ 10 - 1
app/modules/wifi_common.h

@@ -13,6 +13,9 @@
 #include "c_stdio.h"
 #include "task/task.h"
 
+//#define WIFI_DEBUG
+//#define EVENT_DEBUG
+
 void wifi_add_sprintf_field(lua_State* L, char* name, char* string, ...);
 void wifi_add_int_field(lua_State* L, char* name, lua_Integer integer);
 
@@ -37,7 +40,13 @@ static inline void unregister_lua_cb(lua_State* L, int* cb_ref)
 
 void wifi_change_default_host_name(void);
 
-#ifdef NODE_DEBUG
+#if defined(WIFI_DEBUG) || defined(NODE_DEBUG)
+#define WIFI_DBG(...) c_printf(__VA_ARGS__)
+#else
+#define WIFI_DBG(...) //c_printf(__VA_ARGS__)
+#endif
+
+#if defined(EVENT_DEBUG) || defined(NODE_DEBUG)
 #define EVENT_DBG(...) c_printf(__VA_ARGS__)
 #else
 #define EVENT_DBG(...) //c_printf(__VA_ARGS__)

+ 216 - 40
app/modules/ws2812.c

@@ -19,7 +19,6 @@
 
 
 typedef struct {
-  int canary;
   int size;
   uint8_t colorsPerLed;
   uint8_t values[0];
@@ -132,8 +131,7 @@ static int ws2812_write(lua_State* L) {
   }
   else if (type == LUA_TUSERDATA)
   {
-    ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
-    luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
+    ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
 
     buffer1 = buffer->values;
     length1 = buffer->colorsPerLed*buffer->size;
@@ -156,8 +154,7 @@ static int ws2812_write(lua_State* L) {
   }
   else if (type == LUA_TUSERDATA)
   {
-    ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 2);
-    luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 2, "ws2812.buffer expected");
+    ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 2, "ws2812.buffer");
 
     buffer2 = buffer->values;
     length2 = buffer->colorsPerLed*buffer->size;
@@ -173,14 +170,13 @@ static int ws2812_write(lua_State* L) {
   return 0;
 }
 
-// Handle a buffer where we can store led values
-static int ws2812_new_buffer(lua_State *L) {
-  const int leds = luaL_checkint(L, 1);
-  const int colorsPerLed = luaL_checkint(L, 2);
-
-  luaL_argcheck(L, leds > 0, 1, "should be a positive integer");
-  luaL_argcheck(L, colorsPerLed > 0, 2, "should be a positive integer");
+static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) {
+  /* relative string position: negative means back from end */
+  if (pos < 0) pos += (ptrdiff_t)len + 1;
+  return (pos >= 0) ? pos : 0;
+}
 
+static ws2812_buffer *allocate_buffer(lua_State *L, int leds, int colorsPerLed) {
   // Allocate memory
   size_t size = sizeof(ws2812_buffer) + colorsPerLed*leds*sizeof(uint8_t);
   ws2812_buffer * buffer = (ws2812_buffer*)lua_newuserdata(L, size);
@@ -193,16 +189,26 @@ static int ws2812_new_buffer(lua_State *L) {
   buffer->size = leds;
   buffer->colorsPerLed = colorsPerLed;
 
-  // Store canary for future type checks
-  buffer->canary = CANARY_VALUE;
+  return buffer;
+}
+
+// Handle a buffer where we can store led values
+static int ws2812_new_buffer(lua_State *L) {
+  const int leds = luaL_checkint(L, 1);
+  const int colorsPerLed = luaL_checkint(L, 2);
+
+  luaL_argcheck(L, leds > 0, 1, "should be a positive integer");
+  luaL_argcheck(L, colorsPerLed > 0, 2, "should be a positive integer");
+
+  ws2812_buffer * buffer = allocate_buffer(L, leds, colorsPerLed);
+
+  c_memset(buffer->values, 0, colorsPerLed * leds);
 
   return 1;
 }
 
 static int ws2812_buffer_fill(lua_State* L) {
-  ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
-
-  luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
 
   // Grab colors
   int i, j;
@@ -230,11 +236,10 @@ static int ws2812_buffer_fill(lua_State* L) {
 }
 
 static int ws2812_buffer_fade(lua_State* L) {
-  ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
   const int fade = luaL_checkinteger(L, 2);
   unsigned direction = luaL_optinteger( L, 3, FADE_OUT );
 
-  luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
   luaL_argcheck(L, fade > 0, 2, "fade value should be a strict positive number");
 
   uint8_t * p = &buffer->values[0];
@@ -260,17 +265,25 @@ static int ws2812_buffer_fade(lua_State* L) {
 
 
 static int ws2812_buffer_shift(lua_State* L) {
-  ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
   const int shiftValue = luaL_checkinteger(L, 2);
   const unsigned shift_type = luaL_optinteger( L, 3, SHIFT_LOGICAL );
 
-  luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
-  luaL_argcheck(L, shiftValue > 0-buffer->size && shiftValue < buffer->size, 2, "shifting more elements than buffer size");
+  ptrdiff_t start = posrelat(luaL_optinteger(L, 4, 1), buffer->size);
+  ptrdiff_t end = posrelat(luaL_optinteger(L, 5, -1), buffer->size);
+  if (start < 1) start = 1;
+  if (end > (ptrdiff_t)buffer->size) end = (ptrdiff_t)buffer->size;
+
+  start--;
+  int size = end - start;
+  size_t offset = start * buffer->colorsPerLed;
+
+  luaL_argcheck(L, shiftValue > 0-size && shiftValue < size, 2, "shifting more elements than buffer size");
 
   int shift = shiftValue >= 0 ? shiftValue : -shiftValue;
 
   // check if we want to shift at all
-  if (shift == 0)
+  if (shift == 0 || size <= 0)
   {
     return 0;
   }
@@ -280,38 +293,38 @@ static int ws2812_buffer_shift(lua_State* L) {
   size_t shift_len, remaining_len;
   // calculate length of shift section and remaining section
   shift_len = shift*buffer->colorsPerLed;
-  remaining_len = (buffer->size-shift)*buffer->colorsPerLed;
+  remaining_len = (size-shift)*buffer->colorsPerLed;
 
   if (shiftValue > 0)
   {
     // Store the values which are moved out of the array (last n pixels)
-    c_memcpy(tmp_pixels, &buffer->values[(buffer->size-shift)*buffer->colorsPerLed], shift_len);
+    c_memcpy(tmp_pixels, &buffer->values[offset + (size-shift)*buffer->colorsPerLed], shift_len);
     // Move pixels to end
-    os_memmove(&buffer->values[shift*buffer->colorsPerLed], &buffer->values[0], remaining_len);
+    os_memmove(&buffer->values[offset + shift*buffer->colorsPerLed], &buffer->values[offset], remaining_len);
     // Fill beginning with temp data
     if (shift_type == SHIFT_LOGICAL)
     {
-      c_memset(&buffer->values[0], 0, shift_len);
+      c_memset(&buffer->values[offset], 0, shift_len);
     }
     else
     {
-      c_memcpy(&buffer->values[0], tmp_pixels, shift_len);
+      c_memcpy(&buffer->values[offset], tmp_pixels, shift_len);
     }
   }
   else
   {
     // Store the values which are moved out of the array (last n pixels)
-    c_memcpy(tmp_pixels, &buffer->values[0], shift_len);
+    c_memcpy(tmp_pixels, &buffer->values[offset], shift_len);
     // Move pixels to end
-    os_memmove(&buffer->values[0], &buffer->values[shift*buffer->colorsPerLed], remaining_len);
+    os_memmove(&buffer->values[offset], &buffer->values[offset + shift*buffer->colorsPerLed], remaining_len);
     // Fill beginning with temp data
     if (shift_type == SHIFT_LOGICAL)
     {
-      c_memset(&buffer->values[(buffer->size-shift)*buffer->colorsPerLed], 0, shift_len);
+      c_memset(&buffer->values[offset + (size-shift)*buffer->colorsPerLed], 0, shift_len);
     }
     else
     {
-      c_memcpy(&buffer->values[(buffer->size-shift)*buffer->colorsPerLed], tmp_pixels, shift_len);
+      c_memcpy(&buffer->values[offset + (size-shift)*buffer->colorsPerLed], tmp_pixels, shift_len);
     }
   }
   // Free memory
@@ -320,13 +333,107 @@ static int ws2812_buffer_shift(lua_State* L) {
   return 0;
 }
 
+static int ws2812_buffer_dump(lua_State* L) {
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
+
+  lua_pushlstring(L, buffer->values, buffer->size * buffer->colorsPerLed);
+
+  return 1;
+}
+
+static int ws2812_buffer_replace(lua_State* L) {
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
+  size_t l = buffer->size;
+  ptrdiff_t start = posrelat(luaL_optinteger(L, 3, 1), l);
+
+  uint8_t *src;
+  size_t srcLen;
+
+  if (lua_type(L, 2) == LUA_TSTRING) {
+    size_t length;
+
+    src = (uint8_t *) lua_tolstring(L, 2, &length);
+    srcLen = length / buffer->colorsPerLed;
+  } else {
+    ws2812_buffer * rhs = (ws2812_buffer*)luaL_checkudata(L, 2, "ws2812.buffer");
+    src = rhs->values;
+    srcLen = rhs->size;
+    luaL_argcheck(L, rhs->colorsPerLed == buffer->colorsPerLed, 2, "Buffers have different colors");
+  }
+
+  luaL_argcheck(L, srcLen + start - 1 <= buffer->size, 2, "Does not fit into destination");
 
+  c_memcpy(buffer->values + (start - 1) * buffer->colorsPerLed, src, srcLen * buffer->colorsPerLed);
+
+  return 0;
+}
+
+// buffer:mix(factor1, buffer1, ..)
+// factor is 256 for 100%
+// uses saturating arithmetic (one buffer at a time)
+static int ws2812_buffer_mix(lua_State* L) {
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
+
+  int pos = 2;
+  size_t cells = buffer->size * buffer->colorsPerLed;
+
+  int n_sources = (lua_gettop(L) - 1) / 2;
+
+  struct {
+    int factor;
+    const uint8_t *values;
+  } source[n_sources];
+
+  int src;
+  for (src = 0; src < n_sources; src++, pos += 2) {
+    int factor = luaL_checkinteger(L, pos);
+    ws2812_buffer *src_buffer = (ws2812_buffer*) luaL_checkudata(L, pos + 1, "ws2812.buffer");
+
+    luaL_argcheck(L, src_buffer->size == buffer->size && src_buffer->colorsPerLed == buffer->colorsPerLed, pos + 1, "Buffer not same shape");
+    
+    source[src].factor = factor;
+    source[src].values = src_buffer->values;
+  }
+
+  size_t i;
+  for (i = 0; i < cells; i++) {
+    int val = 0;
+    for (src = 0; src < n_sources; src++) {
+      val += ((int)(source[src].values[i] * source[src].factor) >> 8);
+    }
+
+    if (val < 0) {
+      val = 0;
+    } else if (val > 255) {
+      val = 255;
+    }
+    buffer->values[i] = val;
+  }
+
+  return 0;
+}
+
+// Returns the total of all channels
+static int ws2812_buffer_power(lua_State* L) {
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
+
+  size_t cells = buffer->size * buffer->colorsPerLed;
+
+  size_t i;
+  int total = 0;
+  for (i = 0; i < cells; i++) {
+    total += buffer->values[i];
+  }
+
+  lua_pushnumber(L, total);
+
+  return 1;
+}
 
 static int ws2812_buffer_get(lua_State* L) {
-  ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
   const int led = luaL_checkinteger(L, 2) - 1;
 
-  luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
   luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range");
 
   int i;
@@ -339,10 +446,9 @@ static int ws2812_buffer_get(lua_State* L) {
 }
 
 static int ws2812_buffer_set(lua_State* L) {
-  ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
   const int led = luaL_checkinteger(L, 2) - 1;
 
-  luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
   luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range");
 
   int type = lua_type(L, 3);
@@ -387,24 +493,94 @@ static int ws2812_buffer_set(lua_State* L) {
 }
 
 static int ws2812_buffer_size(lua_State* L) {
-  ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
-
-  luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
 
   lua_pushnumber(L, buffer->size);
 
   return 1;
 }
 
+static int ws2812_buffer_sub(lua_State* L) {
+  ws2812_buffer * lhs = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
+  size_t l = lhs->size;
+  ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), l);
+  ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), l);
+  if (start < 1) start = 1;
+  if (end > (ptrdiff_t)l) end = (ptrdiff_t)l;
+  if (start <= end) {
+    ws2812_buffer *result = allocate_buffer(L, end - start + 1, lhs->colorsPerLed);
+    c_memcpy(result->values, lhs->values + lhs->colorsPerLed * (start - 1), lhs->colorsPerLed * (end - start + 1));
+  } else {
+    ws2812_buffer *result = allocate_buffer(L, 0, lhs->colorsPerLed);
+  }
+  return 1;
+}
+
+static int ws2812_buffer_concat(lua_State* L) {
+  ws2812_buffer * lhs = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
+  ws2812_buffer * rhs = (ws2812_buffer*)luaL_checkudata(L, 2, "ws2812.buffer");
+
+  luaL_argcheck(L, lhs->colorsPerLed == rhs->colorsPerLed, 1, "Can only concatenate buffers with same colors");
+
+  int colorsPerLed = lhs->colorsPerLed;
+  int leds = lhs->size + rhs->size;
+ 
+  ws2812_buffer * buffer = allocate_buffer(L, leds, colorsPerLed);
+
+  c_memcpy(buffer->values, lhs->values, lhs->colorsPerLed * lhs->size);
+  c_memcpy(buffer->values + lhs->colorsPerLed * lhs->size, rhs->values, rhs->colorsPerLed * rhs->size);
+
+  return 1;
+}
+
+static int ws2812_buffer_tostring(lua_State* L) {
+  ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
+
+  luaL_Buffer result;
+  luaL_buffinit(L, &result);
+
+  luaL_addchar(&result, '[');
+  int i;
+  int p = 0;
+  for (i = 0; i < buffer->size; i++) {
+    int j;
+    if (i > 0) {
+      luaL_addchar(&result, ',');
+    }
+    luaL_addchar(&result, '(');
+    for (j = 0; j < buffer->colorsPerLed; j++, p++) {
+      if (j > 0) {
+        luaL_addchar(&result, ',');
+      }
+      char numbuf[5];
+      c_sprintf(numbuf, "%d", buffer->values[p]);
+      luaL_addstring(&result, numbuf);
+    }
+    luaL_addchar(&result, ')');
+  }
+
+  luaL_addchar(&result, ']');
+  luaL_pushresult(&result);
+
+  return 1;
+}
+
 static const LUA_REG_TYPE ws2812_buffer_map[] =
 {
+  { LSTRKEY( "dump" ),    LFUNCVAL( ws2812_buffer_dump )},
   { LSTRKEY( "fade" ),    LFUNCVAL( ws2812_buffer_fade )},
   { LSTRKEY( "fill" ),    LFUNCVAL( ws2812_buffer_fill )},
   { LSTRKEY( "get" ),     LFUNCVAL( ws2812_buffer_get )},
+  { LSTRKEY( "replace" ), LFUNCVAL( ws2812_buffer_replace )},
+  { LSTRKEY( "mix" ),     LFUNCVAL( ws2812_buffer_mix )},
+  { LSTRKEY( "power" ),   LFUNCVAL( ws2812_buffer_power )},
   { LSTRKEY( "set" ),     LFUNCVAL( ws2812_buffer_set )},
-  { LSTRKEY( "size" ),    LFUNCVAL( ws2812_buffer_size )},
   { LSTRKEY( "shift" ),   LFUNCVAL( ws2812_buffer_shift )},
+  { LSTRKEY( "size" ),    LFUNCVAL( ws2812_buffer_size )},
+  { LSTRKEY( "sub" ),     LFUNCVAL( ws2812_buffer_sub )},
+  { LSTRKEY( "__concat" ),LFUNCVAL( ws2812_buffer_concat )},
   { LSTRKEY( "__index" ), LROVAL( ws2812_buffer_map )},
+  { LSTRKEY( "__tostring" ), LFUNCVAL( ws2812_buffer_tostring )},
   { LNILKEY, LNILVAL}
 };
 

+ 1 - 1
app/net/nodemcu_mdns.c

@@ -52,7 +52,7 @@
 #include "nodemcu_mdns.h"
 
 #if 0
-#define MDNS_DBG(...)  os_printf(...)
+#define MDNS_DBG(...)  dbg_printf(...)
 #else
 #define MDNS_DBG(...)  do {} while (0)
 #endif

+ 2 - 1
app/platform/vfs.c

@@ -355,11 +355,12 @@ sint32_t vfs_chdir( const char *path )
 sint32_t vfs_errno( const char *name )
 {
   vfs_fs_fns *fs_fns;
-  const char *normname = normalize_path( name );
   char *outname;
 
   if (!name) name = "";  // current drive
 
+  const char *normname = normalize_path( name );
+
 #ifdef BUILD_SPIFFS
   if (fs_fns = myspiffs_realm( normname, &outname, FALSE )) {
     return fs_fns->ferrno( );

+ 1 - 1
app/platform/vfs.h

@@ -85,7 +85,7 @@ inline sint32_t vfs_flush( int fd ) {
 //   Returns: File size
 inline uint32_t vfs_size( int fd ) {
   vfs_file *f = (vfs_file *)fd;
-  return f && f->fns->size ? f->fns->size( f ) : 0;
+  return f ? f->fns->size( f ) : 0;
 }
 
 // vfs_ferrno - get file system specific errno

+ 26 - 8
app/spiffs/spiffs.c

@@ -2,6 +2,8 @@
 #include "platform.h"
 #include "spiffs.h"
 
+#include "spiffs_nucleus.h"
+
 spiffs fs;
 
 #define LOG_PAGE_SIZE       	256
@@ -10,9 +12,9 @@ spiffs fs;
 #define MIN_BLOCKS_FS		4
   
 static u8_t spiffs_work_buf[LOG_PAGE_SIZE*2];
-static u8_t spiffs_fds[32*4];
+static u8_t spiffs_fds[sizeof(spiffs_fd) * SPIFFS_MAX_OPEN_FILES];
 #if SPIFFS_CACHE
-static u8_t spiffs_cache[(LOG_PAGE_SIZE+32)*2];
+static u8_t myspiffs_cache[(LOG_PAGE_SIZE+32)*2];
 #endif
 
 static s32_t my_spiffs_read(u32_t addr, u32_t size, u8_t *dst) {
@@ -168,8 +170,8 @@ static bool myspiffs_mount_internal(bool force_mount) {
     spiffs_fds,
     sizeof(spiffs_fds),
 #if SPIFFS_CACHE
-    spiffs_cache,
-    sizeof(spiffs_cache),
+    myspiffs_cache,
+    sizeof(myspiffs_cache),
 #else
     0, 0,
 #endif
@@ -243,6 +245,7 @@ static sint32_t myspiffs_vfs_lseek( const struct vfs_file *fd, sint32_t off, int
 static sint32_t myspiffs_vfs_eof( const struct vfs_file *fd );
 static sint32_t myspiffs_vfs_tell( const struct vfs_file *fd );
 static sint32_t myspiffs_vfs_flush( const struct vfs_file *fd );
+static uint32_t myspiffs_vfs_size( const struct vfs_file *fd );
 static sint32_t myspiffs_vfs_ferrno( const struct vfs_file *fd );
 
 static sint32_t  myspiffs_vfs_closedir( const struct vfs_dir *dd );
@@ -295,7 +298,7 @@ static vfs_file_fns myspiffs_file_fns = {
   .eof       = myspiffs_vfs_eof,
   .tell      = myspiffs_vfs_tell,
   .flush     = myspiffs_vfs_flush,
-  .size      = NULL,
+  .size      = myspiffs_vfs_size,
   .ferrno    = myspiffs_vfs_ferrno
 };
 
@@ -430,13 +433,17 @@ static sint32_t myspiffs_vfs_close( const struct vfs_file *fd ) {
 static sint32_t myspiffs_vfs_read( const struct vfs_file *fd, void *ptr, size_t len ) {
   GET_FILE_FH(fd);
 
-  return SPIFFS_read( &fs, fh, ptr, len );
+  sint32_t n = SPIFFS_read( &fs, fh, ptr, len );
+
+  return n >= 0 ? n : VFS_RES_ERR;
 }
 
 static sint32_t myspiffs_vfs_write( const struct vfs_file *fd, const void *ptr, size_t len ) {
   GET_FILE_FH(fd);
 
-  return SPIFFS_write( &fs, fh, (void *)ptr, len );
+  sint32_t n = SPIFFS_write( &fs, fh, (void *)ptr, len );
+
+  return n >= 0 ? n : VFS_RES_ERR;
 }
 
 static sint32_t myspiffs_vfs_lseek( const struct vfs_file *fd, sint32_t off, int whence ) {
@@ -456,7 +463,8 @@ static sint32_t myspiffs_vfs_lseek( const struct vfs_file *fd, sint32_t off, int
     break;
   }
 
-  return SPIFFS_lseek( &fs, fh, off, spiffs_whence );
+  sint32_t res = SPIFFS_lseek( &fs, fh, off, spiffs_whence );
+  return res >= 0 ? res : VFS_RES_ERR;
 }
 
 static sint32_t myspiffs_vfs_eof( const struct vfs_file *fd ) {
@@ -477,6 +485,16 @@ static sint32_t myspiffs_vfs_flush( const struct vfs_file *fd ) {
   return SPIFFS_fflush( &fs, fh ) >= 0 ? VFS_RES_OK : VFS_RES_ERR;
 }
 
+static uint32_t myspiffs_vfs_size( const struct vfs_file *fd ) {
+  GET_FILE_FH(fd);
+
+  int32_t curpos = SPIFFS_tell( &fs, fh );
+  int32_t size = SPIFFS_lseek( &fs, fh, 0, SPIFFS_SEEK_END );
+  (void) SPIFFS_lseek( &fs, fh, curpos, SPIFFS_SEEK_SET );
+
+   return size;
+}
+
 static sint32_t myspiffs_vfs_ferrno( const struct vfs_file *fd ) {
   return SPIFFS_errno( &fs );
 }

+ 5 - 5
app/spiffs/spiffs_config.h

@@ -20,19 +20,19 @@
 
 // Set generic spiffs debug output call.
 #ifndef SPIFFS_DBG
-#define SPIFFS_DBG(...) //printf(__VA_ARGS__)
+#define SPIFFS_DBG(...) //dbg_printf(__VA_ARGS__)
 #endif
 // Set spiffs debug output call for garbage collecting.
 #ifndef SPIFFS_GC_DBG
-#define SPIFFS_GC_DBG(...) //printf(__VA_ARGS__)
+#define SPIFFS_GC_DBG(...) //dbg_printf(__VA_ARGS__)
 #endif
 // Set spiffs debug output call for caching.
 #ifndef SPIFFS_CACHE_DBG
-#define SPIFFS_CACHE_DBG(...) //printf(__VA_ARGS__)
+#define SPIFFS_CACHE_DBG(...) //dbg_printf(__VA_ARGS__)
 #endif
 // Set spiffs debug output call for system consistency checks.
 #ifndef SPIFFS_CHECK_DBG
-#define SPIFFS_CHECK_DBG(...) //printf(__VA_ARGS__)
+#define SPIFFS_CHECK_DBG(...) //dbg_printf(__VA_ARGS__)
 #endif
 
 // Enable/disable API functions to determine exact number of bytes
@@ -211,7 +211,7 @@
 #endif
 #if SPIFFS_TEST_VISUALISATION
 #ifndef spiffs_printf
-#define spiffs_printf(...)                printf(__VA_ARGS__)
+#define spiffs_printf(...)                dbg_printf(__VA_ARGS__)
 #endif
 // spiffs_printf argument for a free page
 #ifndef SPIFFS_TEST_VIS_FREE_STR

+ 46 - 13
app/spiffs/spiffs_nucleus.c

@@ -292,27 +292,60 @@ s32_t spiffs_probe(
     SPIFFS_CHECK_RES(res);
   }
 
-  // check that we have sane number of blocks
-  if (bix_count[0] < 3) return SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS;
   // check that the order is correct, take aborted erases in calculation
+  // Note that bix_count[0] should be blockcnt, [1] should be blockcnt - 1
+  // and [2] should be blockcnt - 3
   // first block aborted erase
-  if (magic[0] == (spiffs_obj_id)(-1) && bix_count[1] - bix_count[2] == 1) {
-    return (bix_count[1]+1) * cfg->log_block_size;
-  }
+  int fs_size;
+  if (magic[0] == (spiffs_obj_id)(-1) && bix_count[1] - bix_count[2] == 2) {
+    fs_size = bix_count[1]+1;
+  } else
   // second block aborted erase
-  if (magic[1] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[2] == 2) {
-    return bix_count[0] * cfg->log_block_size;
-  }
+  if (magic[1] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[2] == 3) {
+    fs_size = bix_count[0];
+  } else
   // third block aborted erase
   if (magic[2] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[1] == 1) {
-    return bix_count[0] * cfg->log_block_size;
-  }
+    fs_size = bix_count[0];
+  } else 
   // no block has aborted erase
-  if (bix_count[0] - bix_count[1] == 1 && bix_count[1] - bix_count[2] == 1) {
-    return bix_count[0] * cfg->log_block_size;
+  if (bix_count[0] - bix_count[1] == 1 && bix_count[1] - bix_count[2] == 2) {
+    fs_size = bix_count[0];
+  } else {
+    return SPIFFS_ERR_PROBE_NOT_A_FS;
+  }
+
+  // check that we have sane number of blocks
+  if (fs_size < 3) return SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS;
+
+  dummy_fs.block_count = fs_size;
+
+  // Now verify that there is at least one good block at the end
+  for (bix = fs_size - 1; bix >= 3; bix--) {
+    spiffs_obj_id end_magic;
+    paddr = SPIFFS_MAGIC_PADDR(&dummy_fs, bix);
+#if SPIFFS_HAL_CALLBACK_EXTRA
+    // not any proper fs to report here, so callback with null
+    // (cross fingers that no-one gets angry)
+    res = cfg->hal_read_f((void *)0, paddr, sizeof(spiffs_obj_id), (u8_t *)&end_magic);
+#else
+    res = cfg->hal_read_f(paddr, sizeof(spiffs_obj_id), (u8_t *)&end_magic);
+#endif
+    if (res < 0) {
+      return SPIFFS_ERR_PROBE_NOT_A_FS;
+    }
+    if (end_magic == (spiffs_obj_id)(-1)) {
+      if (bix < fs_size - 1) {
+	return SPIFFS_ERR_PROBE_NOT_A_FS;
+      }
+    } else if (end_magic != SPIFFS_MAGIC(&dummy_fs, bix)) {
+      return SPIFFS_ERR_PROBE_NOT_A_FS;
+    } else {
+      break;
+    }
   }
 
-  return SPIFFS_ERR_PROBE_NOT_A_FS;
+  return fs_size * cfg->log_block_size;
 }
 #endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0
 

+ 1 - 1
app/spiffs/spiffs_nucleus.h

@@ -137,7 +137,7 @@
   ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs)))
 #else // SPIFFS_USE_MAGIC_LENGTH
 #define SPIFFS_MAGIC(fs, bix)           \
-  ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs) ^ ((fs)->block_count - (bix))))
+  ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs) ^ ((fs)->block_count - ((bix) < 3 ? (1<<(bix)) - 1 : (bix)<<2))))
 #endif // SPIFFS_USE_MAGIC_LENGTH
 #endif // SPIFFS_USE_MAGIC
 

+ 0 - 3
app/u8glib/u8g.h

@@ -1178,9 +1178,6 @@ struct _u8g_t
   u8g_state_cb state_cb;
   
   u8g_box_t current_page;		/* current box of the visible page */
-
-  uint8_t i2c_addr;
-  uint8_t use_delay;
 };
 
 #define u8g_GetFontAscent(u8g) ((u8g)->font_ref_ascent)

+ 1 - 1
app/user/Makefile

@@ -24,7 +24,7 @@ STD_CFLAGS=-std=gnu11 -Wimplicit
 #   makefile at its root level - these are then overridden
 #   for a subtree within the makefile rooted therein
 #
-#DEFINES += 
+DEFINES += -DESP_INIT_DATA_DEFAULT="\"$(SDK_DIR)/bin/esp_init_data_default.bin\""
 
 #############################################################
 # Recursion Magic - Don't touch this!!

+ 63 - 19
app/user/user_main.c

@@ -29,11 +29,21 @@
 #include "rtc/rtctime.h"
 #endif
 
-#define SIG_LUA 0
-#define SIG_UARTINPUT 1
-#define TASK_QUEUE_LEN 4
-
-static os_event_t *taskQueue;
+static task_handle_t input_sig;
+static uint8 input_sig_flag = 0;
+
+/* Contents of esp_init_data_default.bin */
+extern const uint32_t init_data[];
+extern const uint32_t init_data_end[];
+__asm__(
+  /* Place in .text for same reason as user_start_trampoline */
+  ".section \".text\"\n"
+  ".align 4\n"
+  "init_data:\n"
+  ".incbin \"" ESP_INIT_DATA_DEFAULT "\"\n"
+  "init_data_end:\n"
+  ".previous\n"
+);
 
 /* 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
@@ -50,6 +60,31 @@ void TEXT_SECTION_ATTR user_start_trampoline (void)
   rtctime_early_startup ();
 #endif
 
+  /* Re-implementation of default init data deployment. The SDK does not
+   * appear to be laying down its own version of init data anymore, so
+   * we have to do it again. To see whether we need to, we read out
+   * the flash size and do a test for esp_init_data based on that size.
+   * If it's missing, we need to initialize it *right now* before the SDK
+   * starts up and gets stuck at "rf_cal[0] !=0x05,is 0xFF".
+   * If the size byte is wrong, then we'll end up fixing up the init data
+   * again on the next boot, after we've corrected the size byte.
+   * 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, (uint32_t *)(&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;
+  uint32_t init_data_addr = flash_end_addr - 4 * SPI_FLASH_SEC_SIZE;
+  SPIRead (init_data_addr, &init_data_hdr, sizeof (init_data_hdr));
+  if (init_data_hdr == 0xffffffff)
+  {
+    SPIEraseSector (init_data_addr);
+    SPIWrite (init_data_addr, init_data, 4 * (init_data_end - init_data));
+  }
+
   call_user_start ();
 }
 
@@ -58,17 +93,18 @@ static void start_lua(task_param_t param, uint8 priority) {
   char* lua_argv[] = { (char *)"lua", (char *)"-i", NULL };
   NODE_DBG("Task task_lua started.\n");
   lua_main( 2, lua_argv );
+  // Only enable UART interrupts once we've successfully started up,
+  // otherwise the task queue might fill up with input events and prevent
+  // the start_lua task from being posted.
+  ETS_UART_INTR_ENABLE();
 }
 
 static void handle_input(task_param_t flag, uint8 priority) {
-//  c_printf("HANDLE_INPUT: %u %u\n", flag, priority);          REMOVE
-  lua_handle_input (flag);
-}
-
-static task_handle_t input_sig;
-
-task_handle_t user_get_input_sig(void) {
-  return input_sig;
+  (void)priority;
+  if (flag & 0x8000) {
+    input_sig_flag = flag & 0x4000 ? 1 : 0;
+  }
+  lua_handle_input (flag & 0x01);
 }
 
 bool user_process_input(bool force) {
@@ -92,9 +128,7 @@ void nodemcu_init(void)
         // Fit hardware real flash size.
         flash_rom_set_size_byte(flash_safe_get_size_byte());
 
-        // Reboot to get SDK to use (or write) init data at new location
         system_restart ();
-
         // Don't post the start_lua task, we're about to reboot...
         return;
     }
@@ -107,7 +141,7 @@ void nodemcu_init(void)
 #ifdef BUILD_SPIFFS
     if (!vfs_mount("/FLASH", 0)) {
         // Failed to mount -- try reformat
-	c_printf("Formatting file system. Please wait...\n");
+	dbg_printf("Formatting file system. Please wait...\n");
         if (!vfs_format()) {
             NODE_ERR( "\n*** ERROR ***: unable to format. FS might be compromised.\n" );
             NODE_ERR( "It is advised to re-flash the NodeMCU image.\n" );
@@ -118,7 +152,8 @@ void nodemcu_init(void)
 #endif
     // endpoint_setup();
 
-    task_post_low(task_get_id(start_lua),'s');
+    if (!task_post_low(task_get_id(start_lua),'s'))
+      NODE_ERR("Failed to post the start_lua task!\n");
 }
 
 #ifdef LUA_USE_MODULES_WIFI
@@ -146,7 +181,7 @@ void user_rf_pre_init(void)
 uint32
 user_rf_cal_sector_set(void)
 {
-    enum flash_size_map size_map = system_get_flash_size_map();
+    enum ext_flash_size_map size_map = system_get_flash_size_map();
     uint32 rf_cal_sec = 0;
 
     switch (size_map) {
@@ -165,9 +200,18 @@ user_rf_cal_sector_set(void)
 
         case FLASH_SIZE_32M_MAP_512_512:
         case FLASH_SIZE_32M_MAP_1024_1024:
+        case FLASH_SIZE_32M_MAP_2048_2048:
             rf_cal_sec = 1024 - 5;
             break;
 
+        case FLASH_SIZE_64M_MAP:
+            rf_cal_sec = 2048 - 5;
+            break;
+
+        case FLASH_SIZE_128M_MAP:
+            rf_cal_sec = 4096 - 5;
+            break;
+
         default:
             rf_cal_sec = 0;
             break;
@@ -191,7 +235,7 @@ void user_init(void)
     UartBautRate br = BIT_RATE_DEFAULT;
 
     input_sig = task_get_id(handle_input);
-    uart_init (br, br, input_sig);
+    uart_init (br, br, input_sig, &input_sig_flag);
 
 #ifndef NODE_DEBUG
     system_set_os_print(0);

+ 75 - 19
app/websocket/websocketclient.c

@@ -47,18 +47,10 @@
 #define PORT_INSECURE 80
 #define PORT_MAX_VALUE 65535
 
-// TODO: user agent configurable
-#define WS_INIT_HEADERS  "GET %s HTTP/1.1\r\n"\
-                         "Host: %s:%d\r\n"\
-                         "Upgrade: websocket\r\n"\
-                         "Connection: Upgrade\r\n"\
-                         "User-Agent: ESP8266\r\n"\
-                         "Sec-Websocket-Key: %s\r\n"\
-                         "Sec-WebSocket-Protocol: chat\r\n"\
-                         "Sec-WebSocket-Version: 13\r\n"\
-                         "\r\n"
-
-#define WS_INIT_HEADERS_LENGTH 169
+#define WS_INIT_REQUEST  "GET %s HTTP/1.1\r\n"\
+                         "Host: %s:%d\r\n"
+
+#define WS_INIT_REQUEST_LENGTH 30
 #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
 #define WS_GUID_LENGTH 36
 
@@ -77,6 +69,13 @@
 #define WS_OPCODE_PING 0x9
 #define WS_OPCODE_PONG 0xA
 
+header_t DEFAULT_HEADERS[] = {
+  {"User-Agent", "ESP8266"},
+  {"Sec-WebSocket-Protocol", "chat"},
+  {0}
+};
+header_t *EMPTY_HEADERS = DEFAULT_HEADERS + sizeof(DEFAULT_HEADERS) / sizeof(header_t) - 1;
+
 static char *cryptoSha1(char *data, unsigned int len) {
   SHA1_CTX ctx;
   SHA1Init(&ctx);
@@ -128,6 +127,44 @@ static void generateSecKeys(char **key, char **expectedKey) {
   os_free(keyEncrypted);
 }
 
+static char *_strcpy(char *dst, char *src) {
+    while(*dst++ = *src++);
+    return dst - 1;
+}
+
+static int headers_length(header_t *headers) {
+  int length = 0;
+  for(; headers->key; headers++)
+    length += strlen(headers->key) + strlen(headers->value) + 4;
+  return length;
+}
+
+static char *sprintf_headers(char *buf, ...) {
+  char *dst = buf;
+  va_list args;
+  va_start(args, buf);
+  for(header_t *header_set = va_arg(args, header_t *); header_set; header_set = va_arg(args, header_t *))
+    for(header_t *header = header_set; header->key; header++) {
+      va_list args2;
+      va_start(args2, buf);
+      for(header_t *header_set2 = va_arg(args2, header_t *); header_set2; header_set2 = va_arg(args2, header_t *))
+        for(header_t *header2 = header_set2; header2->key; header2++) {
+          if(header == header2)
+            goto ok;
+          if(!strcasecmp(header->key, header2->key))
+            goto skip;
+        }
+ok:
+      dst = _strcpy(dst, header->key);
+      dst = _strcpy(dst, ": ");
+      dst = _strcpy(dst, header->value);
+      dst = _strcpy(dst, "\r\n");
+skip:;
+    }
+  dst = _strcpy(dst, "\r\n");
+  return dst;
+}
+
 static void ws_closeSentCallback(void *arg) {
   NODE_DBG("ws_closeSentCallback \n");
   struct espconn *conn = (struct espconn *) arg;
@@ -452,7 +489,7 @@ static void ws_receiveCallback(void *arg, char *buf, unsigned short len) {
       } else if (opCode == WS_OPCODE_PONG) {
         // ping alarm was already reset...
       } else {
-        if (ws->onReceive) ws->onReceive(ws, payload, opCode);
+        if (ws->onReceive) ws->onReceive(ws, payloadLength, payload, opCode);
       }
       os_free(payload);
     }
@@ -509,7 +546,7 @@ static void ws_initReceiveCallback(void *arg, char *buf, unsigned short len) {
   }
 
   // Check server has valid sec key
-  if (strstr(buf, WS_HTTP_SEC_WEBSOCKET_ACCEPT) == NULL || strstr(buf, ws->expectedSecKey) == NULL) {
+  if (strstr(buf, ws->expectedSecKey) == NULL) {
     NODE_DBG("Server has invalid response\n");
     ws->knownFailureCode = -7;
     if (ws->isSecure)
@@ -550,12 +587,31 @@ static void connect_callback(void *arg) {
   char *key;
   generateSecKeys(&key, &ws->expectedSecKey);
 
-  char buf[WS_INIT_HEADERS_LENGTH + strlen(ws->path) + strlen(ws->hostname) + strlen(key)];
-  int len = os_sprintf(buf, WS_INIT_HEADERS, ws->path, ws->hostname, ws->port, key);
+  header_t headers[] = {
+	  {"Upgrade", "websocket"},
+	  {"Connection", "Upgrade"},
+	  {"Sec-WebSocket-Key", key},
+	  {"Sec-WebSocket-Version", "13"},
+	  {0}
+  };
 
-  os_free(key);
+  header_t *extraHeaders = ws->extraHeaders ? ws->extraHeaders : EMPTY_HEADERS;
+
+  char buf[WS_INIT_REQUEST_LENGTH + strlen(ws->path) + strlen(ws->hostname) +
+	  headers_length(DEFAULT_HEADERS) + headers_length(headers) + headers_length(extraHeaders) + 2];
 
-  NODE_DBG("connecting\n");
+  int len = os_sprintf(
+                  buf,
+                  WS_INIT_REQUEST,
+                  ws->path,
+                  ws->hostname,
+                  ws->port
+		  );
+
+  len = sprintf_headers(buf + len, headers, extraHeaders, DEFAULT_HEADERS, 0) - buf;
+
+  os_free(key);
+  NODE_DBG("request: %s", buf);
   if (ws->isSecure)
     espconn_secure_send(conn, (uint8_t *) buf, len);
   else
@@ -630,7 +686,7 @@ static void dns_callback(const char *hostname, ip_addr_t *addr, void *arg) {
   ws_info *ws = (ws_info *) conn->reverse;
 
   if (ws->conn == NULL || ws->connectionState == 4) {
-  	return;
+          return;
   }
 
   if (addr == NULL)  {

+ 7 - 1
app/websocket/websocketclient.h

@@ -40,9 +40,14 @@
 struct ws_info;
 
 typedef void (*ws_onConnectionCallback)(struct ws_info *wsInfo);
-typedef void (*ws_onReceiveCallback)(struct ws_info *wsInfo, char *message, int opCode);
+typedef void (*ws_onReceiveCallback)(struct ws_info *wsInfo, int len, char *message, int opCode);
 typedef void (*ws_onFailureCallback)(struct ws_info *wsInfo, int errorCode);
 
+typedef struct {
+	char *key;
+	char *value;
+} header_t;
+
 typedef struct ws_info {
   int connectionState;
 
@@ -51,6 +56,7 @@ typedef struct ws_info {
   int port;
   char *path;
   char *expectedSecKey;
+  header_t *extraHeaders;
 
   struct espconn *conn;
   void *reservedData;

Файловите разлики са ограничени, защото са твърде много
+ 25 - 15
docs/en/flash.md


+ 1 - 1
docs/en/index.md

@@ -44,7 +44,7 @@ print(gpio.read(pin))
 ```
 
 ## Getting Started
-1. [Build the firmeware](build.md) with the modules you need.
+1. [Build the firmware](build.md) with the modules you need.
 1. [Flash the firmware](flash.md) to the chip.
 1. [Upload code](upload.md) to the firmware.
 

+ 23 - 4
docs/en/modules/enduser-setup.md

@@ -7,10 +7,29 @@ This module provides a simple way of configuring ESP8266 chips without using a s
 
 ![enduser setup config dialog](../../img/enduser-setup.jpg "enduser setup config dialog")
 
-After running [`enduser_setup.start()`](#enduser_setupstart) a portal like the above can be accessed through a wireless network called SetupGadget_XXXXXX. The portal is used to submit the credentials for the WiFi of the enduser.
-After an IP address has been successfully obtained this module will stop as if [`enduser_setup.stop()`](#enduser_setupstop) had been called.
+After running [`enduser_setup.start()`](#enduser_setupstart), a wireless network named "SetupGadget_XXXXXX" will start. Connect to that SSID and then navigate to the root
+of any website (e.g., `http://example.com/` will work, but do not use `.local` domains because it will fail on iOS). A web page similar to the picture above will load, allowing the 
+end user to provide their Wi-Fi information.
+
+After an IP address has been successfully obtained, then this module will stop as if [`enduser_setup.stop()`](#enduser_setupstop) had been called. There is a 10-second delay before
+teardown to allow connected clients to obtain a last status message while the SoftAP is still active.
+
+Alternative HTML can be served by placing a file called `enduser_setup.html` on the filesystem. Everything needed by the web page must be included in this one file. This file will be kept 
+in RAM, so keep it as small as possible. The file can be gzip'd ahead of time to reduce the size (i.e., using `gzip -n` or `zopfli`), and when served, the End User Setup module will add 
+the appropriate `Content-Encoding` header to the response. *Note: Even if gzipped, the file still needs to be named `enduser_setup.html`.*
+
+The following HTTP endpoints exist:
+
+|Endpoint|Description|
+|--------|-----------|
+|/|Returns HTML for the web page. Will return the contents of `enduser_setup.html` if it exists on the filesystem, otherwise will return a page embedded into the firmware image.|
+|/aplist|Forces the ESP8266 to perform a site survey across all channels, reporting access points that it can find. Return payload is a JSON array: `[{"ssid":"foobar","rssi":-36,"chan":3}]`|
+|/generate_204|Returns a HTTP 204 status (expected by certain Android clients during Wi-Fi connectivity checks)|
+|/status|Returns plaintext status description, used by the web page|
+|/status.json|Returns a JSON payload containing the ESP8266's chip id in hexadecimal format and the status code: 0=Idle, 1=Connecting, 2=Wrong Password, 3=Network not Found, 4=Failed, 5=Success|
+|/setwifi|Endpoint intended for services to use for setting the wifi credentials. Identical to `/update` except returns the same payload as `/status.json` instead of redirecting to `/`.|
+|/update|Form submission target. Example: `http://example.com/update?wifi_ssid=foobar&wifi_password=CorrectHorseBatteryStaple`. Must be a GET request. Will redirect to `/` when complete. |
 
-Alternative HTML can be served by placing a file called `enduser_setup.html` in the filesystem. This file will be kept in RAM, so keep it as small as possible.
 
 ## enduser_setup.manual()
 
@@ -53,7 +72,7 @@ Starts the captive portal.
 #### Parameters
  - `onConnected()` callback will be fired when an IP-address has been obtained, just before the enduser_setup module will terminate itself
  - `onError()` callback will be fired if an error is encountered. `err_num` is a number describing the error, and `string` contains a description of the error.
- - `onDebug()` callback is disabled by default. It is intended to be used to find internal issues in the module. `string` contains a description of what is going on.
+ - `onDebug()` callback is disabled by default (controlled by `#define ENDUSER_SETUP_DEBUG_ENABLE` in `enduser_setup.c`). It is intended to be used to find internal issues in the module. `string` contains a description of what is going on.
 
 #### Returns
 `nil`

+ 196 - 108
docs/en/modules/file.md

@@ -7,8 +7,6 @@ The file module provides access to the file system and its individual files.
 
 The file system is a flat file system, with no notion of subdirectories/folders.
 
-Only one file can be open at any given time.
-
 Besides the SPIFFS file system on internal flash, this module can also access FAT partitions on an external SD card is [FatFS is enabled](../sdcard.md).
 
 ```lua
@@ -43,30 +41,6 @@ Current directory defaults to the root of internal SPIFFS (`/FLASH`) after syste
 #### Returns
 `true` on success, `false` otherwise
 
-## file.close()
-
-Closes the open file, if any.
-
-#### Syntax
-`file.close()`
-
-#### Parameters
-none
-
-#### Returns
-`nil`
-
-#### Example
-```lua
--- open 'init.lua', print the first line.
-if file.open("init.lua", "r") then
-  print(file.readline())
-  file.close()
-end
-```
-#### See also
-[`file.open()`](#fileopen)
-
 ## file.exists()
 
 Determines whether the specified file exists.
@@ -95,34 +69,6 @@ end
 #### See also
 [`file.list()`](#filelist)
 
-## file.flush()
-
-Flushes any pending writes to the file system, ensuring no data is lost on a restart. Closing the open file using [`file.close()`](#fileclose) performs an implicit flush as well.
-
-#### Syntax
-`file.flush()`
-
-#### Parameters
-none
-
-#### Returns
-`nil`
-
-#### Example
-```lua
--- open 'init.lua' in 'a+' mode
-if file.open("init.lua", "a+") then
-  -- write 'foo bar' to the end of the file
-  file.write('foo bar')
-  file.flush()
-  -- write 'baz' too
-  file.write('baz')
-  file.close()
-end
-```
-#### See also
-[`file.close()`](#fileclose)
-
 ## file.format()
 
 Format the file system. Completely erases any existing file system and writes a new one. Depending on the size of the flash chip in the ESP, this may take several seconds.
@@ -280,9 +226,9 @@ When done with the file, it must be closed using `file.close()`.
     - "a+": append update mode, previous data is preserved, writing is only allowed at the end of file
 
 #### Returns
-`nil` if file not opened, or not exists (read modes).  `true` if file opened ok.
+file object if file opened ok. `nil` if file not opened, or not exists (read modes).
 
-#### Example
+#### Example (basic model)
 ```lua
 -- open 'init.lua', print the first line.
 if file.open("init.lua", "r") then
@@ -290,120 +236,245 @@ if file.open("init.lua", "r") then
   file.close()
 end
 ```
+#### Example (object model)
+```lua
+-- open 'init.lua', print the first line.
+fd = file.open("init.lua", "r")
+if fd then
+  print(fd:readline())
+  fd:close(); fd = nil
+end
+```
+
 #### See also
 - [`file.close()`](#fileclose)
 - [`file.readline()`](#filereadline)
 
-## file.read()
+## file.remove()
 
-Read content from the open file.
+Remove a file from the file system. The file must not be currently open.
+
+###Syntax
+`file.remove(filename)`
+
+#### Parameters
+`filename` file to remove
+
+#### Returns
+`nil`
+
+#### Example
+
+```lua
+-- remove "foo.lua" from file system.
+file.remove("foo.lua")
+```
+#### See also
+[`file.open()`](#fileopen)
+
+## file.rename()
+
+Renames a file. If a file is currently open, it will be closed first.
 
 #### Syntax
-`file.read([n_or_str])`
+`file.rename(oldname, newname)`
 
 #### Parameters
-- `n_or_str`:
-	- if nothing passed in, read up to `LUAL_BUFFERSIZE` bytes (default 1024) or the entire file (whichever is smaller)
-	- if passed a number n, then read the file until the lesser of `n` bytes, `LUAL_BUFFERSIZE` bytes, or EOF is reached. Specifying a number larger than the buffer size will read the buffer size.
-	- if passed a string `str`, then read until `str` appears next in the file, `LUAL_BUFFERSIZE` bytes have been read, or EOF is reached
+- `oldname` old file name
+- `newname` new file name
 
 #### Returns
-File content as a string, or nil when EOF
+`true` on success, `false` on error.
 
 #### Example
+
 ```lua
--- print the first line of 'init.lua'
+-- rename file 'temp.lua' to 'init.lua'.
+file.rename("temp.lua","init.lua")
+```
+
+# File access functions
+
+The `file` module provides several functions to access the content of a file after it has been opened with [`file.open()`](#fileopen). They can be used as part of a basic model or an object model:
+
+## Basic model
+In the basic model there is max one file opened at a time. The file access functions operate on this file per default. If another file is opened, the previous default file needs to be closed beforehand.
+
+```lua
+-- open 'init.lua', print the first line.
 if file.open("init.lua", "r") then
-  print(file.read('\n'))
+  print(file.readline())
   file.close()
 end
+```
 
--- print the first 5 bytes of 'init.lua'
-if file.open("init.lua", "r") then
-  print(file.read(5))
-  file.close()
+## Object model
+Files are represented by file objects which are created by `file.open()`. File access functions are available as methods of this object, and multiple file objects can coexist.
+
+```lua
+src = file.open("init.lua", "r")
+if src then
+  dest = file.open("copy.lua", "w")
+  if dest then
+    local line
+    repeat
+      line = src:read()
+      if line then
+        dest:write(line)
+      end
+    until line == nil
+    dest:close(); dest = nil
+  end
+  src:close(); dest = nil
 end
 ```
 
+!!! Attention
+
+    It is recommended to use only one single model within the application. Concurrent use of both models can yield unpredictable behavior: Closing the default file from basic model will also close the correspoding file object. Closing a file from object model will also close the default file if they are the same file.
+
+!!! Note
+
+    The maximum number of open files on SPIFFS is determined at compile time by `SPIFFS_MAX_OPEN_FILES` in `user_config.h`.
+
+## file.close()
+## file.obj:close()
+
+Closes the open file, if any.
+
+#### Syntax
+`file.close()`
+
+`fd:close()`
+
+#### Parameters
+none
+
+#### Returns
+`nil`
+
 #### See also
-- [`file.open()`](#fileopen)
-- [`file.readline()`](#filereadline)
+[`file.open()`](#fileopen)
 
-## file.readline()
+## file.flush()
+## file.obj:flush()
 
-Read the next line from the open file. Lines are defined as zero or more bytes ending with a EOL ('\n') byte. If the next line is longer than `LUAL_BUFFERSIZE`, this function only returns the first `LUAL_BUFFERSIZE` bytes (this is 1024 bytes by default).
+Flushes any pending writes to the file system, ensuring no data is lost on a restart. Closing the open file using [`file.close()` / `fd:close()`](#fileclose) performs an implicit flush as well.
 
 #### Syntax
-`file.readline()`
+`file.flush()`
+
+`fd:flush()`
 
 #### Parameters
 none
 
 #### Returns
-File content in string, line by line, including EOL('\n'). Return `nil` when EOF.
+`nil`
 
-#### Example
+#### Example (basic model)
 ```lua
--- print the first line of 'init.lua'
-if file.open("init.lua", "r") then
-  print(file.readline())
+-- open 'init.lua' in 'a+' mode
+if file.open("init.lua", "a+") then
+  -- write 'foo bar' to the end of the file
+  file.write('foo bar')
+  file.flush()
+  -- write 'baz' too
+  file.write('baz')
   file.close()
 end
 ```
+
 #### See also
-- [`file.open()`](#fileopen)
-- [`file.close()`](#fileclose)
-- [`file.read()`](#filereade)
+[`file.close()` / `file.obj:close()`](#fileclose)
 
-## file.remove()
+## file.read()
+## file.obj:read()
 
-Remove a file from the file system. The file must not be currently open.
+Read content from the open file.
 
-###Syntax
-`file.remove(filename)`
+!!! note
+
+    The function temporarily allocates 2 * (number of requested bytes) on the heap for buffering and processing the read data. Default chunk size (`FILE_READ_CHUNK`) is 1024 bytes and is regarded to be safe. Pushing this by 4x or more can cause heap overflows depending on the application. Consider this when selecting a value for parameter `n_or_char`.
+
+#### Syntax
+`file.read([n_or_char])`
+
+`fd:read([n_or_char])`
 
 #### Parameters
-`filename` file to remove
+- `n_or_char`:
+	- if nothing passed in, then read up to `FILE_READ_CHUNK` bytes or the entire file (whichever is smaller).
+	- if passed a number `n`, then read up to `n` bytes or the entire file (whichever is smaller).
+	- if passed a string containing the single character `char`, then read until `char` appears next in the file, `FILE_READ_CHUNK` bytes have been read, or EOF is reached.
 
 #### Returns
-`nil`
+File content as a string, or nil when EOF
 
-#### Example
+#### Example (basic model)
+```lua
+-- print the first line of 'init.lua'
+if file.open("init.lua", "r") then
+  print(file.read('\n'))
+  file.close()
+end
+```
 
+#### Example (object model)
 ```lua
--- remove "foo.lua" from file system.
-file.remove("foo.lua")
+-- print the first 5 bytes of 'init.lua'
+fd = file.open("init.lua", "r")
+if fd then
+  print(fd:read(5))
+  fd:close(); fd = nil
+end
 ```
+
 #### See also
-[`file.open()`](#fileopen)
+- [`file.open()`](#fileopen)
+- [`file.readline()` / `file.obj:readline()`](#filereadline)
 
-## file.rename()
+## file.readline()
+## file.obj:readline()
 
-Renames a file. If a file is currently open, it will be closed first.
+Read the next line from the open file. Lines are defined as zero or more bytes ending with a EOL ('\n') byte. If the next line is longer than 1024, this function only returns the first 1024 bytes.
 
 #### Syntax
-`file.rename(oldname, newname)`
+`file.readline()`
+
+`fd:readline()`
 
 #### Parameters
-- `oldname` old file name
-- `newname` new file name
+none
 
 #### Returns
-`true` on success, `false` on error.
-
-#### Example
+File content in string, line by line, including EOL('\n'). Return `nil` when EOF.
 
+#### Example (basic model)
 ```lua
--- rename file 'temp.lua' to 'init.lua'.
-file.rename("temp.lua","init.lua")
+-- print the first line of 'init.lua'
+if file.open("init.lua", "r") then
+  print(file.readline())
+  file.close()
+end
 ```
 
+#### See also
+- [`file.open()`](#fileopen)
+- [`file.close()` / `file.obj:close()`](#fileclose)
+- [`file.read()` / `file.obj:read()`](#fileread)
+
+
 ## file.seek()
+## file.obj:seek()
+
 Sets and gets the file position, measured from the beginning of the file, to the position given by offset plus a base specified by the string whence.
 
 #### Syntax
 `file.seek([whence [, offset]])`
 
+`fd:seek([whence [, offset]])`
+
 #### Parameters
 - `whence`
 	- "set": base is position 0 (beginning of the file)
@@ -416,7 +487,7 @@ If no parameters are given, the function simply returns the current file offset.
 #### Returns
 the resulting file position, or `nil` on error
 
-#### Example
+#### Example (basic model)
 ```lua
 if file.open("init.lua", "r") then
   -- skip the first 5 bytes of the file
@@ -429,19 +500,22 @@ end
 [`file.open()`](#fileopen)
 
 ## file.write()
+## file.obj:write()
 
 Write a string to the open file.
 
 #### Syntax
 `file.write(string)`
 
+`fd:write(string)`
+
 #### Parameters
 `string` content to be write to file
 
 #### Returns
 `true` if the write is ok, `nil` on error
 
-#### Example
+#### Example (basic model)
 ```lua
 -- open 'init.lua' in 'a+' mode
 if file.open("init.lua", "a+") then
@@ -451,24 +525,38 @@ if file.open("init.lua", "a+") then
 end
 ```
 
+#### Example (object model)
+```lua
+-- open 'init.lua' in 'a+' mode
+fd = file.open("init.lua", "a+")
+if fd then
+  -- write 'foo bar' to the end of the file
+  fd:write('foo bar')
+  fd:close()
+end
+```
+
 #### See also
 - [`file.open()`](#fileopen)
-- [`file.writeline()`](#filewriteline)
+- [`file.writeline()` / `file.obj:writeline()`](#filewriteline)
 
 ## file.writeline()
+## file.obj:writeline()
 
 Write a string to the open file and append '\n' at the end.
 
 #### Syntax
 `file.writeline(string)`
 
+`fd:writeline(string)`
+
 #### Parameters
 `string` content to be write to file
 
 #### Returns
 `true` if write ok, `nil` on error
 
-#### Example
+#### Example (basic model)
 ```lua
 -- open 'init.lua' in 'a+' mode
 if file.open("init.lua", "a+") then
@@ -480,4 +568,4 @@ end
 
 #### See also
 - [`file.open()`](#fileopen)
-- [`file.readline()`](#filereadline)
+- [`file.readline()` / `file.obj:readline()`](#filereadline)

+ 9 - 8
docs/en/modules/gpio.md

@@ -69,28 +69,29 @@ gpio.read(0)
 
 ## gpio.serout()
 
-Serialize output based on a sequence of delay-times in µs. After each delay, the pin is toggled. After the last repeat and last delay the pin is not toggled.
+Serialize output based on a sequence of delay-times in µs. After each delay, the pin is toggled. After the last cycle and last delay the pin is not toggled.
 
 The function works in two modes: 
 * synchronous - for sub-50 µs resolution, restricted to max. overall duration,
 * asynchrounous - synchronous operation with less granularity but virtually unrestricted duration.
 
-Whether the asynchronous mode is chosen is defined by presence of the `callback` parameter. If present and is of function type the function goes asynchronous the callback function is invoked when sequence finishes. If the parameter is numeric the function still goes asynchronous but no callback is invoked when done.
+Whether the asynchronous mode is chosen is defined by presence of the `callback` parameter. If present and is of function type the function goes asynchronous and the callback function is invoked when sequence finishes. If the parameter is numeric the function still goes asynchronous but no callback is invoked when done.
 
-For asynchronous version minimum delay time should not be shorter than 50 μs and maximum delay time is 0x7fffff μs (~8.3 seconds).
-In this mode the function does not block the stack and returns immediately before the output sequence is finalized. HW timer inf `FRC1_SOURCE` mode is used to change the states.
+For the asynchronous version, the minimum delay time should not be shorter than 50 μs and maximum delay time is 0x7fffff μs (~8.3 seconds).
+In this mode the function does not block the stack and returns immediately before the output sequence is finalized. HW timer `FRC1_SOURCE` mode is used to change the states. As there is only a single hardware timer, there
+are restrictions on which modules can be used at the same time. An error will be raised if the timer is already in use.
 
-Note that the synchronous variant (no or nil `callback` parameter) function blocks the stach and as such any use of it must adhere to the SDK guidelines (also explained [here](https://nodemcu.readthedocs.io/en/dev/en/extn-developer-faq/#extension-developer-faq)). Failure to do so may lead to WiFi issues or outright to crashes/reboots. Shortly it means that sum of all delay times multiplied by the number of repeats should not exceed 15 ms.
+Note that the synchronous variant (no or nil `callback` parameter) function blocks the stack and as such any use of it must adhere to the SDK guidelines (also explained [here](../extn-developer-faq/#extension-developer-faq)). Failure to do so may lead to WiFi issues or outright to crashes/reboots. In short it means that the sum of all delay times multiplied by the number of cycles should not exceed 15 ms.
 
 #### Syntax
-`gpio.serout(pin, start_level, delay_times [, repeat_num[, callback]])`
+`gpio.serout(pin, start_level, delay_times [, cycle_num[, callback]])`
 
 #### Parameters
 - `pin`  pin to use, IO index
 - `start_level` level to start on, either `gpio.HIGH` or `gpio.LOW`
 - `delay_times` an array of delay times in µs between each toggle of the gpio pin. 
-- `repeat_num` an optional number of times to run through the sequence.
-- `callback` an optional callback function or number, if present the function ruturns immediately and goes asynchronous.
+- `cycle_num` an optional number of times to run through the sequence. (default is 1)
+- `callback` an optional callback function or number, if present the function returns immediately and goes asynchronous.
 
 
 #### Returns

+ 40 - 31
docs/en/modules/net.md

@@ -135,15 +135,21 @@ Listen on port from IP address.
 
 #### Example
 ```lua
- -- 30s time out for a inactive client
-sv = net.createServer(net.TCP, 30)
 -- server listens on 80, if data received, print data to console and send "hello world" back to caller
-sv:listen(80, function(c)
-  c:on("receive", function(c, pl) 
-    print(pl)
+-- 30s time out for a inactive client
+sv = net.createServer(net.TCP, 30)
+
+function receiver(sck, data)
+  print(data)
+  sck:close()
+end
+
+if sv then
+  sv:listen(80, function(conn)
+    conn:on("receive", receiver)
+    conn:send("hello world")
   end)
-  c:send("hello world")
-end)
+end
 ```
 
 #### See also
@@ -303,32 +309,35 @@ Multiple consecutive `send()` calls aren't guaranteed to work (and often don't)
 #### Example
 ```lua
 srv = net.createServer(net.TCP)
-srv:listen(80, function(conn)
-  conn:on("receive", function(sck, req)
-    local response = {}
-
-    -- if you're sending back HTML over HTTP you'll want something like this instead
-    -- local response = {"HTTP/1.0 200 OK\r\nServer: NodeMCU on ESP8266\r\nContent-Type: text/html\r\n\r\n"}
-
-    response[#response + 1] = "lots of data"
-    response[#response + 1] = "even more data"
-    response[#response + 1] = "e.g. content read from a file"
-    
-	 -- sends and removes the first element from the 'response' table
-    local function send(sk)
-      if #response > 0
-        then sk:send(table.remove(response, 1))
-      else
-        sk:close()
-        response = nil
-      end
+
+function receiver(sck, data)
+  local response = {}
+
+  -- if you're sending back HTML over HTTP you'll want something like this instead
+  -- local response = {"HTTP/1.0 200 OK\r\nServer: NodeMCU on ESP8266\r\nContent-Type: text/html\r\n\r\n"}
+
+  response[#response + 1] = "lots of data"
+  response[#response + 1] = "even more data"
+  response[#response + 1] = "e.g. content read from a file"
+
+  -- sends and removes the first element from the 'response' table
+  local function send(localSocket)
+    if #response > 0
+    then localSocket:send(table.remove(response, 1))
+    else
+      localSocket:close()
+      response = nil
     end
+  end
 
-    -- triggers the send() function again once the first chunk of data was sent
-    sck:on("sent", send)
-    
-    send(sck)
-  end)
+  -- triggers the send() function again once the first chunk of data was sent
+  sck:on("sent", send)
+
+  send(sck)
+end
+
+srv:listen(80, function(conn)
+  conn:on("receive", receiver)
 end)
 ```
 If you do not or can not keep all the data you send back in memory at one time (remember that `response` is an aggregation) you may use explicit callbacks instead of building up a table like so:

+ 1 - 1
docs/en/modules/ow.md

@@ -84,7 +84,7 @@ Reads multi bytes.
 
 #### Parameters
 - `pin` 1~12, I/O index
-- `size` number of bytes to be read from slave device
+- `size` number of bytes to be read from slave device (up to 256)
 
 #### Returns
 `string` bytes read from slave device

+ 1 - 1
docs/en/modules/rtctime.md

@@ -55,7 +55,7 @@ rtctime.dsleep(5000000, 4)
 For applications where it is necessary to take samples with high regularity, this function is useful. It provides an easy way to implement a "wake up on the next 5-minute boundary" scheme, without having to explicitly take into account how long the module has been active for etc before going back to sleep.
 
 #### Syntax
-`rtctime.dsleep(aligned_us, minsleep_us [, option])`
+`rtctime.dsleep_aligned(aligned_us, minsleep_us [, option])`
 
 #### Parameters
 - `aligned_us` boundary interval in microseconds

+ 1 - 1
docs/en/modules/sntp.md

@@ -12,7 +12,7 @@ When compiled together with the [rtctime](rtctime.md) module it also offers seam
 
 Attempts to obtain time synchronization. 
 
-For best results you may want to to call this periodically in order to compensate for internal clock drift. As stated in the [rtctime](rtctime.md) module documentation it's advisable to sync time after deep sleep and it's necessary to sync after module reset (add it to [`init.lua`](upload.md#initlua) after WiFi initialization).
+For best results you may want to to call this periodically in order to compensate for internal clock drift. As stated in the [rtctime](rtctime.md) module documentation it's advisable to sync time after deep sleep and it's necessary to sync after module reset (add it to [`init.lua`](../upload.md#initlua) after WiFi initialization).
 
 #### Syntax
 `sntp.sync([server_ip], [callback], [errcallback])`

+ 45 - 0
docs/en/modules/somfy.md

@@ -0,0 +1,45 @@
+# Somfy module
+| Since  | Origin / Contributor  | Maintainer  | Source  |
+| :----- | :-------------------- | :---------- | :------ |
+| 2016-09-27 | [vsky279](https://github.com/vsky279) | [vsky279](https://github.com/vsky279) | [somfy.c](../../../app/modules/somfy.c)|
+
+This module provides a simple interface to control Somfy blinds via an RF transmitter (433.42 MHz). It is based on [Nickduino Somfy Remote Arduino skecth](https://github.com/Nickduino/Somfy_Remote). 
+
+The hardware used is the standard 433 MHz RF transmitter. Unfortunately these chips are usually transmitting at he frequency of 433.92MHz so the crystal resonator should be replaced with the 433.42 MHz resonator though some reporting that it is working even with the original crystal.
+
+To understand details of the Somfy protocol please refer to [Somfy RTS protocol](https://pushstack.wordpress.com/somfy-rts-protocol/) and also discussion [here](https://forum.arduino.cc/index.php?topic=208346.0).
+
+The module is using hardware timer so it cannot be used at the same time with other NodeMCU modules using the hardware timer, i.e. `sigma delta`, `pcm`, `perf`, or `pwm` modules.
+
+## somfy.sendcommand()
+
+Builds an frame defined by Somfy protocol and sends it to the RF transmitter.
+
+#### Syntax
+`somfy.sendcommand(pin, remote_address, command, rolling_code, repeat_count, call_back)`
+
+#### Parameters
+- `pin` GPIO pin the RF transmitter is connected to.
+- `remote_address` address of the remote control. The device to be controlled is programmed with the addresses of the remote controls it should listen to.
+- `command` command to be transmitted. Can be one of `somfy.SOMFY_UP`, `somfy.SOMFY_DOWN`, `somfy.SOMFY_PROG`, `somfy.SOMFY_STOP`
+- `rolling_code` The rolling code is increased every time a button is pressed. The receiver only accepts command if the rolling code is above the last received code and is not to far ahead of the last received code. This window is in the order of a 100 big. The rolling code needs to be stored in the EEPROM (i.e. filesystem) to survive the ESP8266 reset.
+- `repeat_count` how many times the command is repeated
+- `call_back` a function to be called after the command is transmitted. Allows chaining commands to set the blinds to a defined position.
+
+My original remote is [TELIS 4 MODULIS RTS](https://www.somfy.co.uk/products/1810765/telis-4-modulis-rts). This remote is working with the additional info - additional 56 bits that follow data (shortening the Inter-frame gap). It seems that the scrumbling alhorithm has not been revealed yet.
+
+When I send the `somfy.DOWN` command, repeating the frame twice (which seems to be the standard for a short button press), i.e. `repeat_count` equal to 2, the blinds go only 1 step down. This corresponds to the movement of the wheel on the original remote. The down button on the original remote sends also `somfy.DOWN` command but the additional info is different and this makes the blinds go full down. Fortunately it seems that repeating the frame 16 times makes the blinds go fully down.
+
+#### Returns  
+nil
+
+#### Example
+To start with controlling your Somfy blinds you need to:
+
+- Choose an arbitrary remote address (different from your existing remote) - `123` in this example
+- Choose a starting point for the rolling code. Any unsigned int works, 1 is a good start
+- Long-press the program button of your existing remote control until your blind goes up and down slightly
+- execute `somfy.sendcommand(4, 123, somfy.PROG, 1, 2)` - the blinds will react and your ESP8266 remote control is now registered
+- running `somfy.sendcommand(4, 123, somfy.DOWN, 2, 16)` - fully closes the blinds
+
+For more elaborated example please refer to [`somfy.lua`](../../../lua_examples/somfy.lua).

+ 4 - 0
docs/en/modules/spi.md

@@ -6,6 +6,10 @@
 All transactions for sending and receiving are most-significant-bit first and least-significant last.
 For technical details of the underlying hardware refer to [metalphreak's ESP8266 HSPI articles](http://d.av.id.au/blog/tag/hspi/).
 
+!!! note
+
+	The ESP hardware provides two SPI busses, with IDs 0, and 1, which map to pins generally labelled SPI and HSPI. If you are using any kind of development board which provides flash, then bus ID 0 (SPI) is almost certainly used for communicating with the flash chip. You probably want to choose bus ID 1 (HSPI) for your communication, as you will have uncontended use of it.
+
 ## High Level Functions
 The high level functions provide a send & receive API for half- and
 full-duplex mode. Sent and received data items are restricted to 1 - 32 bit

+ 27 - 1
docs/en/modules/u8g.md

@@ -8,7 +8,7 @@ U8glib is a graphics library developed at [olikraus/u8glib](https://github.com/o
 I²C and SPI mode:
 
 - sh1106_128x64
-- ssd1306 - 128x64 and 64x48 variants
+- ssd1306 - 128x32, 128x64, and 64x48 variants
 - ssd1309_128x64
 - ssd1327_96x96_gr
 - uc1611 - dogm240 and dogxl240 variants
@@ -107,6 +107,7 @@ Initialize a display via I²C.
 The init sequence would insert delays to match the display specs. These can destabilize the overall system if wifi service is blocked for too long. It is therefore advisable to disable such delays unless the specific use case can exclude wifi traffic while initializing the display driver.
 
 - `u8g.sh1106_128x64_i2c()`
+- `u8g.ssd1306_128x32_i2c()`
 - `u8g.ssd1306_128x64_i2c()`
 - `u8g.ssd1306_64x48_i2c()`
 - `u8g.ssd1309_128x64_i2c()`
@@ -146,6 +147,7 @@ The init sequence would insert delays to match the display specs. These can dest
 - `u8g.pcd8544_84x48_hw_spi()`
 - `u8g.pcf8812_96x65_hw_spi()`
 - `u8g.sh1106_128x64_hw_spi()`
+- `u8g.ssd1306_128x32_hw_spi()`
 - `u8g.ssd1306_128x64_hw_spi()`
 - `u8g.ssd1306_64x48_hw_spi()`
 - `u8g.ssd1309_128x64_hw_spi()`
@@ -202,6 +204,30 @@ disp = u8g.ssd1306_128x64_hw_spi(cs, dc, res)
 #### See also
 [I²C Display Drivers](#i2c-display-drivers)
 
+## u8g.fb_rle
+Initialize a virtual display that provides run-length encoded framebuffer contents to a Lua callback.
+
+The callback function can be used to process the framebuffer line by line. It's called with either `nil` as parameter to indicate the start of a new frame or with a string containing a line of the framebuffer with run-length encoding. First byte in the string specifies how many pairs of (x, len) follow, while each pair defines the start (leftmost x-coordinate) and length of a sequence of lit pixels. All other pixels in the line are dark.
+
+```lua
+n = struct.unpack("B", rle_line)
+print(n.." pairs")
+for i = 0,n-1 do
+  print(string.format("  x: %d len: %d", struct.unpack("BB", rle_line, 1+1 + i*2)))
+end
+```
+
+#### Syntax
+`u8g.fb_rle(cb_fn, width, height)`
+
+#### Parameters
+- `cb_fn([rle_line])` callback function. `rle_line` is a string containing a run-length encoded framebuffer line, or `nil` to indicate start of frame.
+- `width` of display. Must be a multiple of 8, less than or equal to 248.
+- `height` of display. Must be a multiple of 8, less than or equal to 248.
+
+#### Returns
+u8g display object
+
 ___
 
 ## Constants

+ 21 - 4
docs/en/modules/websocket.md

@@ -7,10 +7,6 @@ A websocket *client* module that implements [RFC6455](https://tools.ietf.org/htm
 
 The implementation supports fragmented messages, automatically respondes to ping requests and periodically pings if the server isn't communicating.
 
-!!! note
-
-    Currently, it is **not** possible to change the request headers, most notably the user agent.
-
 **SSL/TLS support**
 
 Take note of constraints documented in the [net module](net.md). 
@@ -70,6 +66,27 @@ ws = nil -- fully dispose the client as lua will now gc it
 ```
 
 
+## websocket.client:config(params)
+
+Configures websocket client instance.
+
+#### Syntax
+`websocket:config(params)`
+
+#### Parameters
+- `params` table with configuration parameters. Following keys are recognized:
+  - `headers` table of extra request headers affecting every request
+
+#### Returns
+`nil`
+
+#### Example
+```lua
+ws = websocket.createClient()
+ws:config({headers={['User-Agent']='NodeMCU'}})
+```
+
+
 ## websocket.client:connect()
 
 Attempts to estabilish a websocket connection to the given URL.

+ 448 - 112
docs/en/modules/wifi.md

@@ -24,6 +24,23 @@ Gets the current WiFi channel.
 #### Returns
 current WiFi channel
 
+## wifi.getdefaultmode()
+
+Gets default WiFi operation mode.
+
+#### Syntax
+`wifi.getdefaultmode()`
+
+#### Parameters
+`nil`
+
+#### Returns
+The WiFi mode, as one of the `wifi.STATION`, `wifi.SOFTAP`, `wifi.STATIONAP` or `wifi.NULLMODE` constants.
+
+#### See also
+[`wifi.getmode()`](#wifigetmode)
+[`wifi.setmode()`](#wifisetmode)
+
 ## wifi.getmode()
 
 Gets WiFi operation mode.
@@ -38,6 +55,7 @@ Gets WiFi operation mode.
 The WiFi mode, as one of the `wifi.STATION`, `wifi.SOFTAP`, `wifi.STATIONAP` or `wifi.NULLMODE` constants.
 
 #### See also
+[`wifi.getdefaultmode()`](#wifigetdefaultmode)
 [`wifi.setmode()`](#wifisetmode)
 
 ## wifi.getphymode()
@@ -67,18 +85,20 @@ Configures the WiFi mode to use. NodeMCU can run in one of four WiFi modes:
 
 When using the combined Station + AP mode, the same channel will be used for both networks as the radio can only listen on a single channel.
 
-NOTE: WiFi Mode configuration will be retained until changed even if device is turned off. 
+NOTE: WiFi Mode configuration will be retained until changed even if device is turned off.
 
 #### Syntax
-`wifi.setmode(mode)`
+`wifi.setmode(mode[, save])`
 
 #### Parameters
-`mode` value should be one of
-
-- `wifi.STATION` for when the device is connected to a WiFi router. This is often done to give the device access to the Internet.
-- `wifi.SOFTAP` for when the device is acting *only* as an access point. This will allow you to see the device in the list of WiFi networks (unless you hide the SSID, of course). In this mode your computer can connect to the device, creating a local area network. Unless you change the value, the NodeMCU device will be given a local IP address of 192.168.4.1 and assign your computer the next available IP address, such as 192.168.4.2.
-- `wifi.STATIONAP` is the combination of `wifi.STATION` and `wifi.SOFTAP`. It allows you to create a local WiFi connection *and* connect to another WiFi router.
-- `wifi.NULLMODE` to switch off WiFi
+- `mode` value should be one of
+ - `wifi.STATION` for when the device is connected to a WiFi router. This is often done to give the device access to the Internet.
+ - `wifi.SOFTAP` for when the device is acting *only* as an access point. This will allow you to see the device in the list of WiFi networks (unless you hide the SSID, of course). In this mode your computer can connect to the device, creating a local area network. Unless you change the value, the NodeMCU device will be given a local IP address of 192.168.4.1 and assign your computer the next available IP address, such as 192.168.4.2.
+ - `wifi.STATIONAP` is the combination of `wifi.STATION` and `wifi.SOFTAP`. It allows you to create a local WiFi connection *and* connect to another WiFi router.
+ - `wifi.NULLMODE` changing WiFi mode to NULL_MODE will put wifi into a low power state similar to MODEM_SLEEP, provided `wifi.nullmodesleep(false)` has not been called.
+- `save` choose whether or not to save wifi mode to flash
+   - `true` WiFi mode configuration **will** be retained through power cycle. (Default)
+   - `false` WiFi mode configuration **will not** be retained through power cycle.
 
 #### Returns
 current mode after setup
@@ -90,11 +110,12 @@ wifi.setmode(wifi.STATION)
 
 #### See also
 [`wifi.getmode()`](#wifigetmode)
+[`wifi.getdefaultmode()`](#wifigetdefaultmode)
 
 ## wifi.setphymode()
 
 Sets WiFi physical mode.
- 
+
 - `wifi.PHYMODE_B`
     802.11b, more range, low Transfer rate, more current draw
 - `wifi.PHYMODE_G`
@@ -116,7 +137,7 @@ Information from the Espressif datasheet v4.3
 `wifi.setphymode(mode)`
 
 #### Parameters
-`mode` one of the following 
+`mode` one of the following
 
 - `wifi.PHYMODE_B`
 - `wifi.PHYMODE_G`
@@ -130,39 +151,23 @@ physical mode after setup
 
 ## wifi.nullmodesleep()
 
-Configures whether or not WiFi automatically goes to sleep in NULL_MODE. Enabled by default. 
-
-#### Syntax
-`wifi.nullmodesleep(enable)`
-
-#### Parameters
-- `enable` 
-  - true: Enable WiFi auto sleep in NULL_MODE. (Default setting)
-  - false: Disable WiFi auto sleep in NULL_MODE. 
-
-#### Returns
-Current/new NULL_MODE sleep setting.
-
-## wifi.sleeptype()
+Configures whether or not WiFi automatically goes to sleep in NULL_MODE. Enabled by default.
 
-Configures the WiFi modem sleep type.
+	!!! note
+		This function **does not** store it's setting in flash, if auto sleep in NULL_MODE is not desired, `wifi.nullmodesleep(false)` must be called after powerup, restart, or wake from deep sleep.
 
 #### Syntax
-`wifi.sleeptype(type_wanted)`
+`wifi.nullmodesleep([enable])`
 
 #### Parameters
-`type_wanted` one of the following:
-
-- `wifi.NONE_SLEEP` to keep the modem on at all times
-- `wifi.LIGHT_SLEEP` to allow the modem to power down under some circumstances
-- `wifi.MODEM_SLEEP` to power down the modem as much as possible
+- `enable`
+  - `true` Enable WiFi auto sleep in NULL_MODE. (Default setting)
+  - `false` Disable WiFi auto sleep in NULL_MODE.
 
 #### Returns
-The actual sleep mode set, as one of `wifi.NONE_SLEEP`, `wifi.LIGHT_SLEEP` or `wifi.MODEM_SLEEP`.
-
-#### See also
-- [`node.dsleep()`](node.md#nodedsleep)
-- [`rtctime.dsleep()`](rtctime.md#rtctimedsleep)
+- `sleep_enabled` Current/New NULL_MODE sleep setting
+ - If `wifi.nullmodesleep()` is called with no arguments, current setting is returned.
+ - If `wifi.nullmodesleep()` is called with `enable` argument, confirmation of new setting is returned.
 
 ## wifi.startsmart()
 
@@ -241,61 +246,95 @@ wifi.sta.autoconnect(1)
 - [`wifi.sta.connect()`](#wifistaconnect)
 - [`wifi.sta.disconnect()`](#wifistadisconnect)
 
+## wifi.sta.changeap()
+
+Select Access Point from list returned by `wifi.sta.getapinfo()`
+
+#### Syntax
+`wifi.sta.changeap(ap_index)`
+
+#### Parameters
+`ap_index` Index of Access Point you would like to change to. (Range:1-5)
+ - Corresponds to index used by [`wifi.sta.getapinfo()`](#wifistagetapinfo) and [`wifi.sta.getapindex()`](#wifistagetapindex)
+
+#### Returns
+- `true`  Success
+- `false` Failure
+
+#### Example
+```lua
+wifi.sta.changeap(4)
+```
+
+#### See also
+- [`wifi.sta.getapinfo()`](#wifistagetapinfo)
+- [`wifi.sta.getapindex()`](#wifistagetapindex)
+
 ## wifi.sta.config()
 
 Sets the WiFi station configuration.
 
-NOTE: Station configuration will be retained until changed even if device is turned off. 
-
 #### Syntax
-`wifi.sta.config(ssid, password[, auto[, bssid]])`
+`wifi.sta.config(station_config)`
 
 #### Parameters
-
-- `ssid` string which is less than 32 bytes.
-- `password` string which is 8-64 or 0 bytes. Empty string indicates an open WiFi access point.
-- `auto` defaults to 1
-	- 0 to disable auto connect and remain disconnected from access point
-	- 1 to enable auto connect and connect to access point, hence with `auto=1` there's no need to call [`wifi.sta.connect()`](#wifistaconnect) later
-- `bssid` string that contains the MAC address of the access point (optional)
+- `station_config` table containing configuration data for station
+ - `ssid` string which is less than 32 bytes.
+ - `pwd` string which is 8-64 or 0 bytes. Empty string indicates an open WiFi access point.
+ - `auto` defaults to true
+	- `true` to enable auto connect and connect to access point, hence with `auto=true` there's no need to call [`wifi.sta.connect()`](#wifistaconnect)
+	- `false` to disable auto connect and remain disconnected from access point
+ - `bssid` string that contains the MAC address of the access point (optional)
 	- You can set BSSID if you have multiple access points with the same SSID.
  	- Note: if you set BSSID for a specific SSID and would like to configure station to connect to the same SSID only without the BSSID requirement, you MUST first configure to station to a different SSID first, then connect to the desired SSID
  	- The following formats are valid:
-		- "DE-C1-A5-51-F1-ED"
+		- "DE:C1:A5:51:F1:ED"
 		- "AC-1D-1C-B1-0B-22"
 		- "DE AD BE EF 7A C0"
+ - `save` Save station configuration to flash. 
+   - `true` configuration **will** be retained through power cycle. 
+   - `false` configuration **will not** be retained through power cycle. (Default)
 
 #### Returns
-`nil`
+- `true`  Success
+- `false` Failure
 
 #### Example
 
 ```lua
--- Connect to access point automatically when in range, `auto` defaults to 1
-wifi.sta.config("myssid", "password")
-
--- Connect to Unsecured access point automatically when in range, `auto` defaults to 1
-wifi.sta.config("myssid", "")
-  
--- Connect to access point, User decides when to connect/disconnect to/from AP due to `auto=0`
-wifi.sta.config("myssid", "mypassword", 0)
-wifi.sta.connect()
--- ... do some WiFi stuff
-wifi.sta.disconnect()
-   
--- Connect to specific access point automatically when in range, `auto` defaults to 1
-wifi.sta.config("myssid", "mypassword", "12:34:56:78:90:12")
-
--- Connect to specific access point, User decides when to connect/disconnect to/from AP due to `auto=0`
-wifi.sta.config("myssid", "mypassword", 0, "12:34:56:78:90:12")
-wifi.sta.connect()
--- ... do some WiFi stuff
-wifi.sta.disconnect()
+--connect to Access Point (DO NOT save config to flash)
+station_cfg={}
+station_cfg.ssid="NODE-AABBCC"
+station_cfg.pwd="password"
+wifi.sta.config(station_cfg)
+
+--connect to Access Point (DO save config to flash)
+station_cfg={}
+station_cfg.ssid="NODE-AABBCC"
+station_cfg.pwd="password"
+station_cfg.save=true
+wifi.sta.config(station_cfg)
+
+--connect to Access Point with specific MAC address  
+station_cfg={}
+station_cfg.ssid="NODE-AABBCC"
+station_cfg.pwd="password"
+station_cfg.bssid="AA:BB:CC:DD:EE:FF"
+wifi.sta.config(station_cfg)
+
+--configure station but don't connect to Access point
+station_cfg={}
+station_cfg.ssid="NODE-AABBCC"
+station_cfg.pwd="password"
+station_cfg.auto=false
+wifi.sta.config(station_cfg)
+
 ```
 
 #### See also
 - [`wifi.sta.connect()`](#wifistaconnect)
 - [`wifi.sta.disconnect()`](#wifistadisconnect)
+- [`wifi.sta.apinfo()`](#wifistaapinfo)
 
 ## wifi.sta.connect()
 
@@ -318,6 +357,10 @@ none
 
 Disconnects from AP in station mode.
 
+	!!! note
+		Please note that disconnecting from Access Point does not reduce power consumption.
+		If power saving is your goal, please refer to the description for `wifi.NULLMODE` in the function [`wifi.setmode()`](#wifisetmode) for more details.
+
 #### Syntax
 `wifi.sta.disconnect()`
 
@@ -339,14 +382,14 @@ Registers callbacks for WiFi station status events.
 - `wifi.sta.eventMonReg(wifi_status[, function([previous_state])])`
 
 ####  Parameters
-- `wifi_status` WiFi status you would like to set a callback for: 
+- `wifi_status` WiFi status you would like to set a callback for:
     - `wifi.STA_IDLE`
     - `wifi.STA_CONNECTING`
     - `wifi.STA_WRONGPWD`
     - `wifi.STA_APNOTFOUND`
     - `wifi.STA_FAIL`
     - `wifi.STA_GOTIP`
-- `function` callback function to perform when event occurs 
+- `function` callback function to perform when event occurs
 	- Note: leaving field blank unregisters callback.
 - `previous_state` previous wifi_state(0 - 5)
 
@@ -354,7 +397,7 @@ Registers callbacks for WiFi station status events.
 `nil`
 
 ####  Example
-```lua 
+```lua
 --register callback
 wifi.sta.eventMonReg(wifi.STA_IDLE, function() print("STATION_IDLE") end)
 wifi.sta.eventMonReg(wifi.STA_CONNECTING, function() print("STATION_CONNECTING") end)
@@ -362,16 +405,16 @@ wifi.sta.eventMonReg(wifi.STA_WRONGPWD, function() print("STATION_WRONG_PASSWORD
 wifi.sta.eventMonReg(wifi.STA_APNOTFOUND, function() print("STATION_NO_AP_FOUND") end)
 wifi.sta.eventMonReg(wifi.STA_FAIL, function() print("STATION_CONNECT_FAIL") end)
 wifi.sta.eventMonReg(wifi.STA_GOTIP, function() print("STATION_GOT_IP") end)
-  
+
 --register callback: use previous state
 wifi.sta.eventMonReg(wifi.STA_CONNECTING, function(previous_State)
-	if(previous_State==wifi.STA_GOTIP) then 
+	if(previous_State==wifi.STA_GOTIP) then
 		print("Station lost connection with access point\n\tAttempting to reconnect...")
 	else
 		print("STATION_CONNECTING")
 	end
 end)
-  
+
 --unregister callback
 wifi.sta.eventMonReg(wifi.STA_IDLE)
 ```
@@ -447,7 +490,7 @@ Scans AP list as a Lua table into callback function.
 
 #### Parameters
 - `cfg` table that contains scan configuration
-	- `ssid` SSID == nil, don't filter SSID 
+	- `ssid` SSID == nil, don't filter SSID
 	- `bssid` BSSID == nil, don't filter BSSID
 	- `channel` channel == 0, scan all channels, otherwise scan set channel (default is 0)
 	- `show_hidden` show_hidden == 1, get info for router with hidden SSID (default is 0)
@@ -479,7 +522,7 @@ function listap(t) -- (SSID : Authmode, RSSI, BSSID, Channel)
 	end
 end
 wifi.sta.getap(listap)
-	
+
 -- print AP list in new format
 function listap(t)
 	for k,v in pairs(t) do
@@ -507,8 +550,8 @@ function listap(t)
 	end
 end
 scan_cfg = {}
-scan_cfg.ssid = "myssid" 
-scan_cfg.bssid = "AA:AA:AA:AA:AA:AA" 
+scan_cfg.ssid = "myssid"
+scan_cfg.bssid = "AA:AA:AA:AA:AA:AA"
 scan_cfg.channel = 0
 scan_cfg.show_hidden = 1
 wifi.sta.getap(scan_cfg, 1, listap)
@@ -520,10 +563,10 @@ function listap(t)
 		print("CURRENT RSSI IS: "..rssi)
 	end
 end
-ssid, tmp, bssid_set, bssid=wifi.sta.getconfig() 
+ssid, tmp, bssid_set, bssid=wifi.sta.getconfig()
 
 scan_cfg = {}
-scan_cfg.ssid = ssid 
+scan_cfg.ssid = ssid
 if bssid_set == 1 then scan_cfg.bssid = bssid else scan_cfg.bssid = nil end
 scan_cfg.channel = wifi.getchannel()
 scan_cfg.show_hidden = 0
@@ -535,6 +578,88 @@ wifi.sta.getap(scan_cfg, 1, listap)
 #### See also
 [`wifi.sta.getip()`](#wifistagetip)
 
+## wifi.sta.getapindex()
+
+Get index of current Access Point stored in AP cache.
+
+
+#### Syntax
+`wifi.sta.getapindex()`
+
+#### Parameters
+none
+
+#### Returns
+`current_index` index of currently selected Access Point. (Range:1-5)
+
+#### Example
+```lua
+print("the index of the currently selected AP is: "..wifi.sta.getapindex())
+```
+
+#### See also
+- [`wifi.sta.getapindex()`](#wifistagetapindex)
+- [`wifi.sta.apinfo()`](#wifistaapinfo)
+- [`wifi.sta.apchange()`](#wifistaapchange)
+
+## wifi.sta.getapinfo()
+
+Get information of APs cached by ESP8266 station.
+
+!!! Note
+		Any Access Points configured with save disabled `wifi.sta.config({save=false})` will populate this list (appearing to overwrite APs stored in flash) until restart.
+
+#### Syntax
+`wifi.sta.getapinfo()`
+
+#### Parameters
+`nil`
+
+#### Returns
+- `ap_info`
+ - `qty` quantity of APs returned
+ - `1-5` index of AP. (the index corresponds to index used by [`wifi.sta.changeap()`](#wifistachangeap) and [`wifi.sta.getapindex()`](#wifistagetapindex))
+   - `ssid`  ssid of Access Point
+   - `pwd`	 Password for Access Point
+     - If no password was configured, the `pwd` field will be `nil`
+   - `bssid` MAC address of Access Point
+     - If no MAC address was configured, the `bssid` field will be `nil`
+
+
+#### Example
+```lua
+--print stored access point info
+do
+  for k,v in pairs(wifi.sta.getapinfo()) do
+    if (type(v)=="table") then
+      print(" "..k.." : "..type(v))
+      for k,v in pairs(v) do
+        print("\t\t"..k.." : "..v)
+      end
+    else
+      print(" "..k.." : "..v)
+    end
+  end
+end
+
+--print stored access point info(formatted)
+do
+  local x=wifi.sta.getapinfo()
+  local y=wifi.sta.getapindex()
+  print("\n Number of APs stored in flash:", x.qty)
+  print(string.format("  %-6s %-32s %-64s %-18s", "index:", "SSID:", "Password:", "BSSID:")) 
+  for i=1, (x.qty), 1 do
+    print(string.format(" %s%-6d %-32s %-64s %-18s",(i==y and ">" or " "), i, x[i].ssid, x[i].pwd and x[i].pwd or type(nil), x[i].bssid and x[i].bssid or type(nil)))
+  end
+end
+```
+
+#### See also
+- [`wifi.sta.getapindex()`](#wifistagetapindex)
+- [`wifi.sta.setaplimit()`](#wifistasetaplimit)
+- [`wifi.sta.changeap()`](#wifistachangeap)
+- [`wifi.sta.config()`](#wifistaconfig)
+
 ## wifi.sta.getbroadcast()
 
 Gets the broadcast address in station mode.
@@ -546,7 +671,7 @@ Gets the broadcast address in station mode.
 `nil`
 
 #### Returns
-broadcast address as string, for example "192.168.0.255", 
+broadcast address as string, for example "192.168.0.255",
 returns `nil` if IP address = "0.0.0.0".
 
 #### See also
@@ -554,23 +679,39 @@ returns `nil` if IP address = "0.0.0.0".
 
 ## wifi.sta.getconfig()
 
-Gets the WiFi station configuration. 
+Gets the WiFi station configuration.
 
 #### Syntax
 `wifi.sta.getconfig()`
 
 #### Parameters
-none
+- `return_table`
+ - `true` returns data in a table
+ - `false` returns data in the old format (default)
 
 #### Returns
-ssid, password, bssid_set, bssid
-
-Note: If bssid_set is equal to 0 then bssid is irrelevant 
+If `return_table` is `true`:
+- `config_table`
+ - `ssid` ssid of Access Point.
+ - `pwd` password to Access Point.
+   - If no password was configured, the `pwd` field will be `nil`
+ - `bssid` MAC address of Access Point
+   - If no MAC address was configured, the `bssid` field will be `nil`
+
+If `return_table` is `false`:
+- ssid, password, bssid_set, bssid   
+ - Note: If `bssid_set` is equal to `0` then `bssid` is irrelevant,
 
 #### Example
 
 ```lua
---Get current Station configuration
+--Get current Station configuration (NEW FORMAT)
+do
+local def_sta_config=wifi.sta.getconfig(true)
+print(string.format("\tDefault station config\n\tssid:\"%s\"\tpassword:\"%s\"%s", def_sta_config.ssid, def_sta_config.pwd, (type(def_sta_config.bssid)=="string" and "\tbssid:\""..def_sta_config.bssid.."\"" or "")))
+end
+
+--Get current Station configuration (OLD FORMAT)
 ssid, password, bssid_set, bssid=wifi.sta.getconfig()
 print("\nCurrent Station configuration:\nSSID : "..ssid
 .."\nPassword  : "..password
@@ -580,6 +721,55 @@ ssid, password, bssid_set, bssid=nil, nil, nil, nil
 ```
 
 #### See also
+- [`wifi.sta.getdefaultconfig()`](#wifistagetdefaultconfig)
+- [`wifi.sta.connect()`](#wifistaconnect)
+- [`wifi.sta.disconnect()`](#wifistadisconnect)
+
+## wifi.sta.getdefaultconfig()
+
+Gets the default WiFi station configuration stored in flash.
+
+#### Syntax
+`wifi.sta.getdefaultconfig(return_table)`
+
+#### Parameters
+- `return_table`
+ - `true` returns data in a table
+ - `false` returns data in the old format (default)
+
+#### Returns
+If `return_table` is `true`:
+- `config_table`
+ - `ssid` ssid of Access Point.
+ - `pwd` password to Access Point.
+   - If no password was configured, the `pwd` field will be `nil`
+ - `bssid` MAC address of Access Point
+   - If no MAC address was configured, the `bssid` field will be `nil`
+
+If `return_table` is `false`:
+- ssid, password, bssid_set, bssid   
+ - Note: If `bssid_set` is equal to `0` then `bssid` is irrelevant,
+
+#### Example
+
+```lua
+--Get default Station configuration (NEW FORMAT)
+do
+  local def_sta_config=wifi.sta.getdefaultconfig(true)
+  print(string.format("\tDefault station config\n\tssid:\"%s\"\tpassword:\"%s\"%s", def_sta_config.ssid, def_sta_config.pwd, (type(def_sta_config.bssid)=="string" and "\tbssid:\""..def_sta_config.bssid.."\"" or "")))
+end
+
+--Get default Station configuration (OLD FORMAT)
+ssid, password, bssid_set, bssid=wifi.sta.getdefaultconfig()
+print("\nCurrent Station configuration:\nSSID : "..ssid
+.."\nPassword  : "..password
+.."\nBSSID_set  : "..bssid_set
+.."\nBSSID: "..bssid.."\n")
+ssid, password, bssid_set, bssid=nil, nil, nil, nil
+```
+
+#### See also
+- [`wifi.sta.getconfig()`](#wifistagetconfig)
 - [`wifi.sta.connect()`](#wifistaconnect)
 - [`wifi.sta.disconnect()`](#wifistadisconnect)
 
@@ -667,6 +857,32 @@ RSSI=wifi.sta.getrssi()
 print("RSSI is", RSSI)
 ```
 
+## wifi.sta.setaplimit()
+
+Set Maximum number of Access Points to store in flash.
+ - This value is written to flash
+
+!!! Attention
+		If 5 Access Points are stored and AP limit is set to 4, the AP at index 5 will remain until [`node.restore()`](node.md#noderestore) is called or AP limit is set to 5 and AP is overwritten.  
+
+#### Syntax
+`wifi.sta.setaplimit(qty)`
+
+#### Parameters
+`qty` Quantity of Access Points to store in flash. Range: 1-5 (Default: 5)
+
+#### Returns
+- `true`  Success
+- `false` Failure
+
+#### Example
+```lua
+wifi.sta.setaplimit(true)
+```
+
+#### See also
+- [`wifi.sta.getapinfo()`](#wifistagetapinfo)
+
 ## wifi.sta.sethostname()
 
 Sets station hostname.
@@ -732,6 +948,26 @@ print(wifi.sta.setmac("DE:AD:BE:EF:7A:C0"))
 #### See also
 [`wifi.sta.setip()`](#wifistasetip)
 
+## wifi.sta.sleeptype()
+
+Configures the WiFi modem sleep type to be used while station is connected to an Access Point.
+
+	!!! note
+		Does not apply to `wifi.SOFTAP`, `wifi.STATIONAP` or `wifi.NULLMODE`.
+
+#### Syntax
+`wifi.sta.sleeptype(type_wanted)`
+
+#### Parameters
+`type_wanted` one of the following:
+
+- `wifi.NONE_SLEEP` to keep the modem on at all times
+- `wifi.LIGHT_SLEEP` to allow the CPU to power down under some circumstances
+- `wifi.MODEM_SLEEP` to power down the modem as much as possible
+
+#### Returns
+The actual sleep mode set, as one of `wifi.NONE_SLEEP`, `wifi.LIGHT_SLEEP` or `wifi.MODEM_SLEEP`.
+
 ## wifi.sta.status()
 
 Gets the current status in station mode.
@@ -758,22 +994,26 @@ number: 0~5
 
 Sets SSID and password in AP mode. Be sure to make the password at least 8 characters long! If you don't it will default to *no* password and not set the SSID! It will still work as an access point but use a default SSID like e.g. NODE-9997C3.
 
-NOTE: SoftAP Configuration will be retained until changed even if device is turned off. 
-
 #### Syntax
 `wifi.ap.config(cfg)`
 
 #### Parameters
-- `ssid` SSID chars 1-32
-- `pwd` password chars 8-64
-- `auth` authentication method, one of `wifi.OPEN` (default), `wifi.WPA_PSK`, `wifi.WPA2_PSK`, `wifi.WPA_WPA2_PSK`
-- `channel` channel number 1-14 default = 6
-- `hidden` 0 = not hidden, 1 = hidden, default 0
-- `max` maximal number of connections 1-4 default=4
-- `beacon` beacon interval time in range 100-60000, default = 100
+- `cfg` table to hold configuration
+ - `ssid` SSID chars 1-32
+ - `pwd` password chars 8-64
+ - `auth` authentication method, one of `wifi.OPEN` (default), `wifi.WPA_PSK`, `wifi.WPA2_PSK`, `wifi.WPA_WPA2_PSK`
+ - `channel` channel number 1-14 default = 6
+ - `hidden` false = not hidden, true = hidden, default = false
+ - `max` maximum number of connections 1-4 default=4
+ - `beacon` beacon interval time in range 100-60000, default = 100
+ - `save` save configuration to flash.
+   - `true` configuration **will** be retained through power cycle. (Default)
+   - `false` configuration **will not** be retained through power cycle.
+ 
 
 #### Returns
-`nil`
+- `true`  Success
+- `false` Failure
 
 #### Example:
 ```lua
@@ -785,7 +1025,7 @@ NOTE: SoftAP Configuration will be retained until changed even if device is turn
 
 ## wifi.ap.deauth()
 
-Deauths (forcibly removes) a client from the ESP access point by sending a corresponding IEEE802.11 management packet (first) and removing the client from it's data structures (afterwards). 
+Deauths (forcibly removes) a client from the ESP access point by sending a corresponding IEEE802.11 management packet (first) and removing the client from it's data structures (afterwards).
 
 The IEEE802.11 reason code used is 2 for "Previous authentication no longer valid"(AUTH_EXPIRE).
 
@@ -803,11 +1043,11 @@ Returns true unless called while the ESP is in the STATION opmode
 ```lua
 allowed_mac_list={"18:fe:34:00:00:00", "18:fe:34:00:00:01"}
 
-wifi.eventmon.register(wifi.eventmon.AP_STACONNECTED, function(T) 
+wifi.eventmon.register(wifi.eventmon.AP_STACONNECTED, function(T)
   print("\n\tAP - STATION CONNECTED".."\n\tMAC: "..T.MAC.."\n\tAID: "..T.AID)
   if(allowed_mac_list~=nil) then
-    for _, v in pairs(allowed_mac_list) do 
-      if(v == T.MAC) then return end 
+    for _, v in pairs(allowed_mac_list) do
+      if(v == T.MAC) then return end
     end
   end
   wifi.ap.deauth(T.MAC)
@@ -831,7 +1071,7 @@ Gets broadcast address in AP mode.
 none
 
 #### Returns
-broadcast address in string, for example "192.168.0.255", 
+broadcast address in string, for example "192.168.0.255",
 returns `nil` if IP address = "0.0.0.0".
 
 #### Example
@@ -871,6 +1111,102 @@ for mac,ip in pairs(wifi.ap.getclient()) do
 end
 ```
 
+## wifi.ap.getconfig()
+
+Gets the current SoftAP configuration.
+
+#### Syntax
+`wifi.ap.getconfig(return_table)`
+
+#### Parameters
+- `return_table`
+ - `true` returns data in a table
+ - `false` returns data in the old format (default)
+
+#### Returns
+If `return_table` is true:
+- `config_table`
+ - `ssid` Network name
+ - `pwd` Password
+   - If no password was configured, the `pwd` field will be `nil`
+ - `auth` Authentication Method (`wifi.OPEN`, `wifi.WPA_PSK`, `wifi.WPA2_PSK` or `wifi.WPA_WPA2_PSK`)
+ - `channel` Channel number
+ - `hidden` `false` = not hidden, `true` = hidden
+ - `max` Maximum number of client connections
+ - `beacon` Beacon interval
+
+If `return_table` is false:
+ssid, password   
+	Note: If bssid_set is equal to 0 then bssid is irrelevant
+
+#### Example
+
+```lua
+--Get SoftAP configuration table (NEW FORMAT)
+do
+  print("\n  Current SoftAP configuration:")
+  for k,v in pairs(wifi.ap.getconfig(true)) do
+      print("   "..k.." :",v)
+  end
+end
+
+--Get current SoftAP configuration (OLD FORMAT)
+do
+  local ssid, password=wifi.ap.getconfig()
+  print("\n  Current SoftAP configuration:\n   SSID : "..ssid..
+    "\n   Password  :",password)
+  ssid, password=nil, nil
+end
+```
+
+## wifi.ap.getdefaultconfig()
+
+Gets the default SoftAP configuration stored in flash.
+
+#### Syntax
+`wifi.ap.getdefaultconfig(return_table)`
+
+#### Parameters
+- `return_table`
+ - `true` returns data in a table
+ - `false` returns data in the old format (default)
+
+#### Returns
+If `return_table` is true:
+- `config_table`
+ - `ssid` Network name
+ - `pwd` Password
+   - If no password was configured, the `pwd` field will be `nil`
+ - `auth` Authentication Method (`wifi.OPEN`, `wifi.WPA_PSK`, `wifi.WPA2_PSK` or `wifi.WPA_WPA2_PSK`)
+ - `channel` Channel number
+ - `hidden` `false` = not hidden, `true` = hidden
+ - `max` Maximum number of client connections
+ - `beacon` Beacon interval
+
+If `return_table` is false:
+ssid, password   
+	Note: If bssid_set is equal to 0 then bssid is irrelevant
+
+#### Example
+
+```lua
+--Get default SoftAP configuration table (NEW FORMAT)
+do
+  print("\n  Default SoftAP configuration:")
+  for k,v in pairs(wifi.ap.getdefaultconfig(true)) do
+      print("   "..k.." :",v)
+  end
+end
+
+--Get default SoftAP configuration (OLD FORMAT)
+do
+  local ssid, password=wifi.ap.getdefaultconfig()
+  print("\n  Default SoftAP configuration:\n   SSID : "..ssid..
+    "\n   Password  :",password)
+  ssid, password=nil, nil
+end
+```
+
 ## wifi.ap.getip()
 
 Gets IP address, netmask and gateway in AP mode.
@@ -1077,39 +1413,39 @@ T: Table returned by event.
 #### Example
 
 ```lua
- wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, function(T) 
+ wifi.eventmon.register(wifi.eventmon.STA_CONNECTED, function(T)
  print("\n\tSTA - CONNECTED".."\n\tSSID: "..T.SSID.."\n\tBSSID: "..
  T.BSSID.."\n\tChannel: "..T.channel)
  end)
- 
- wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, function(T) 
+
+ wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, function(T)
  print("\n\tSTA - DISCONNECTED".."\n\tSSID: "..T.SSID.."\n\tBSSID: "..
  T.BSSID.."\n\treason: "..T.reason)
  end)
 
- wifi.eventmon.register(wifi.eventmon.STA_AUTHMODE_CHANGE, Function(T) 
+ wifi.eventmon.register(wifi.eventmon.STA_AUTHMODE_CHANGE, Function(T)
  print("\n\tSTA - AUTHMODE CHANGE".."\n\told_auth_mode: "..
- T.old_auth_mode.."\n\tnew_auth_mode: "..T.new_auth_mode) 
+ T.old_auth_mode.."\n\tnew_auth_mode: "..T.new_auth_mode)
  end)
 
- wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function(T) 
+ wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function(T)
  print("\n\tSTA - GOT IP".."\n\tStation IP: "..T.IP.."\n\tSubnet mask: "..
  T.netmask.."\n\tGateway IP: "..T.gateway)
  end)
 
- wifi.eventmon.register(wifi.eventmon.STA_DHCP_TIMEOUT, function() 
+ wifi.eventmon.register(wifi.eventmon.STA_DHCP_TIMEOUT, function()
  print("\n\tSTA - DHCP TIMEOUT")
  end)
 
- wifi.eventmon.register(wifi.eventmon.AP_STACONNECTED, function(T) 
+ wifi.eventmon.register(wifi.eventmon.AP_STACONNECTED, function(T)
  print("\n\tAP - STATION CONNECTED".."\n\tMAC: "..T.MAC.."\n\tAID: "..T.AID)
  end)
 
- wifi.eventmon.register(wifi.eventmon.AP_STADISCONNECTED, function(T) 
+ wifi.eventmon.register(wifi.eventmon.AP_STADISCONNECTED, function(T)
  print("\n\tAP - STATION DISCONNECTED".."\n\tMAC: "..T.MAC.."\n\tAID: "..T.AID)
  end)
 
- wifi.eventmon.register(wifi.eventmon.AP_PROBEREQRECVED, function(T) 
+ wifi.eventmon.register(wifi.eventmon.AP_PROBEREQRECVED, function(T)
  print("\n\tAP - STATION DISCONNECTED".."\n\tMAC: ".. T.MAC.."\n\tRSSI: "..T.RSSI)
  end)
 ```

+ 131 - 3
docs/en/modules/ws2812.md

@@ -71,7 +71,7 @@ ws2812.write(nil, string.char(0, 255, 0, 0, 255, 0)) -- turn the two first RGB l
 For more advanced animations, it is useful to keep a "framebuffer" of the strip,
 interact with it and flush it to the strip.
 
-For this purpose, the ws2812 library offers a read/write buffer.
+For this purpose, the ws2812 library offers a read/write buffer. This buffer has a `__tostring` method so that it can be printed. This is useful for debugging.
 
 #### Example
 Led chaser with a RGBW strip
@@ -181,6 +181,92 @@ The number of given bytes must match the number of bytesPerLed of the buffer
 buffer:fill(0, 0, 0) -- fill the buffer with black for a RGB strip
 ```
 
+## ws2812.buffer:dump()
+Returns the contents of the buffer (the pixel values) as a string. This can then be saved to a file or sent over a network.
+
+#### Syntax
+`buffer:dump()`
+
+#### Returns
+A string containing the pixel values. 
+
+#### Example
+```lua
+local s = buffer:dump() 
+```
+
+## ws2812.buffer:replace()
+Inserts a string (or a buffer) into another buffer with an offset.
+The buffer must have the same number of colors per led or an error will be thrown.
+
+#### Syntax
+`buffer:replace(source[, offset])`
+
+#### Parameters
+ - `source` the pixel values to be set into the buffer. This is either a string or a buffer.
+ - `offset` the offset where the source is to be placed in the buffer. Default is 1. Negative values can be used.
+
+#### Returns
+`nil`
+
+#### Example
+```lua
+buffer:replace(anotherbuffer:dump()) -- copy one buffer into another via a string
+buffer:replace(anotherbuffer) -- copy one buffer into another
+newbuffer = buffer.sub(1)     -- make a copy of a buffer into a new buffer
+```
+
+## ws2812.buffer:mix()
+This is a general method that loads data into a buffer that is a linear combination of data from other buffers. It can be used to copy a buffer or,
+more usefully, do a cross fade. The pixel values are computed as integers and then range limited to [0, 255]. This means that negative
+factors work as expected, and that the order of combining buffers does not matter.
+
+#### Syntax
+`buffer:mix(factor1, buffer1, ...)`
+
+#### Parameters
+ - `factor1` This is the factor that the contents of `buffer1` are multiplied by. This factor is scaled by a factor of 256. Thus `factor1` value of 256 is a factor of 1.0.
+ - `buffer1` This is the source buffer. It must be of the same shape as the destination buffer.
+
+There can be any number of factor/buffer pairs.
+
+#### Returns
+`nil`
+
+#### Example
+```lua
+-- loads buffer with a crossfade between buffer1 and buffer2
+buffer:mix(256 - crossmix, buffer1, crossmix, buffer2)
+
+-- multiplies all values in buffer by 0.75
+-- This can be used in place of buffer:fade
+buffer:mix(192, buffer)
+```
+
+## ws2812.buffer:power()
+Computes the total energy requirement for the buffer. This is merely the total sum of all the pixel values (which assumes that each color in each
+pixel consumes the same amount of power). A real WS2812 (or WS2811) has three constant current drivers of 20mA -- one for each of R, G and B. The
+pulse width modulation will cause the *average* current to scale linearly with pixel value. 
+
+#### Syntax
+`buffer:power()`
+
+#### Returns
+An integer which is the sum of all the pixel values.
+
+#### Example
+```lua
+-- Dim the buffer to no more than the PSU can provide
+local psu_current_ma = 1000
+local led_current_ma = 20
+local led_sum = psu_current_ma * 255 / led_current_ma
+
+local p = buffer:power()
+if p > led_sum then
+  buffer:mix(256 * led_sum / p, buffer) -- power is now limited
+end
+```
+
 ## ws2812.buffer:fade()
 Fade in or out. Defaults to out. Multiply or divide each byte of each led with/by the given value. Useful for a fading effect. 
 
@@ -200,14 +286,17 @@ buffer:fade(2)
 buffer:fade(2, ws2812.FADE_IN)
 ```
 ## ws2812.buffer:shift()
-Shift the content of the buffer in positive or negative direction. This allows simple animation effects.
+Shift the content of (a piece of) the buffer in positive or negative direction. This allows simple animation effects. A slice of the buffer can be specified by using the 
+standard start and end offset Lua notation. Negative values count backwards from the end of the buffer.
 
 #### Syntax
-`buffer:shift(value [, mode])`
+`buffer:shift(value [, mode[, i[, j]]])`
 
 #### Parameters
  - `value` number of pixels by which to rotate the buffer. Positive values rotate forwards, negative values backwards. 
  - `mode` is the shift mode to use. Can be one of `ws2812.SHIFT_LOGICAL` or `ws2812.SHIFT_CIRCULAR`. In case of SHIFT\_LOGICAL, the freed pixels are set to 0 (off). In case of SHIFT\_CIRCULAR, the buffer is treated like a ring buffer, inserting the pixels falling out on one end again on the other end. Defaults to SHIFT\_LOGICAL. 
+ - `i` is the first offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is 1.
+ - `j` is the last offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is -1.
 
 #### Returns
 `nil`
@@ -216,3 +305,42 @@ Shift the content of the buffer in positive or negative direction. This allows s
 ```lua
 buffer:shift(3)
 ```
+
+## ws2812.buffer:sub()
+This implements the extraction function like `string.sub`. The indexes are in leds and all the same rules apply.
+
+#### Syntax
+`buffer1:sub(i[, j])`
+
+#### Parameters
+ - `i` This is the start of the extracted data. Negative values can be used.
+ - `j` this is the end of the extracted data. Negative values can be used. The default is -1.
+
+#### Returns
+A buffer containing the extracted piece.
+
+#### Example
+```
+b = buffer:sub(1,10)
+```
+
+## ws2812.buffer:__concat()
+This implements the `..` operator to concatenate two buffers. They must have the same number of colors per led.
+
+#### Syntax
+`buffer1 .. buffer2`
+
+#### Parameters
+ - `buffer1` this is the start of the resulting buffer
+ - `buffer2` this is the end of the resulting buffer
+
+#### Returns
+The concatenated buffer.
+
+#### Example
+```
+ws2812.write(buffer1 .. buffer2)
+```
+
+
+

+ 145 - 0
lua_examples/somfy.lua

@@ -0,0 +1,145 @@
+-- Somfy module example (beside somfy module requires also CJSON module)
+-- The rolling code number is stored in the file somfy.cfg. A cached write of the somfy.cfg file is implemented in order to reduce the number of write to the EEPROM memory. Together with the logic of the file module it should allow long lasting operation.
+
+config_file = "somfy."
+-- somfy.cfg looks like
+-- {"window1":{"rc":1,"address":123},"window2":{"rc":1,"address":124}}
+
+local tmr_cache = tmr.create()
+local tmr_delay = tmr.create()
+
+pin = 4
+gpio.mode(pin, gpio.OUTPUT, gpio.PULLUP)
+
+function deepcopy(orig)
+    local orig_type = type(orig)
+    local copy
+    if orig_type == 'table' then
+        copy = {}
+        for orig_key, orig_value in next, orig, nil do
+            copy[deepcopy(orig_key)] = deepcopy(orig_value)
+        end
+        setmetatable(copy, deepcopy(getmetatable(orig)))
+    else -- number, string, boolean, etc
+        copy = orig
+    end
+    return copy
+end
+
+function readconfig()
+    local cfg, ok, ln
+    if file.exists(config_file.."cfg") then
+        print("Reading config from "..config_file.."cfg")
+        file.open(config_file.."cfg", "r+")
+        ln = file.readline()
+        file.close()
+    else
+        if file.exists(config_file.."bak") then
+            print("Reading config from "..config_file.."bak")
+            file.open(config_file.."bak", "r+")
+            ln = file.readline()
+            file.close()
+        end
+    end
+    if not ln then ln = "{}" end
+    print("Configuration: "..ln)
+    config = cjson.decode(ln)
+    config_saved = deepcopy(config)
+end
+
+function writeconfighard()
+    print("Saving config")
+    file.remove(config_file.."bak")
+    file.rename(config_file.."cfg", config_file.."bak")
+    file.open(config_file.."cfg", "w+")
+    local ok, cfg = pcall(cjson.encode, config)
+    if ok then
+        file.writeline(cfg)
+    else
+        print("Config not saved!")
+    end
+    file.close()
+
+    config_saved = deepcopy(config)
+end
+
+function writeconfig()
+    tmr.stop(tmr_cache)
+    local savenow = false
+    local savelater = false
+
+--print("Config: "..cjson.encode(config))
+--print("Config saved: "..cjson.encode(config))
+ 
+    local count = 0
+    for _ in pairs(config_saved) do count = count + 1 end
+    if count == 0 then
+        config_saved = readconfig()
+    end
+    for remote,cfg in pairs(config_saved) do
+        savelater = savelater or not config[remote] or config[remote].rc > cfg.rc
+        savenow = savenow or not config[remote] or config[remote].rc > cfg.rc + 10
+    end
+    savelater = savelater and not savenow
+    if savenow then
+        print("Saving config now!")
+        writeconfighard()
+    end
+    if savelater then
+        print("Saving config later")
+        tmr.alarm(tmr_cache, 65000, tmr.ALARM_SINGLE, writeconfighard)
+    end
+end
+
+--======================================================================================================--
+function down(remote, cb, par)
+    par = par or {}
+    print("down: ".. remote)
+    config[remote].rc=config[remote].rc+1
+    somfy.sendcommand(pin, config[remote].address, somfy.DOWN, config[remote].rc, 16, function() wait(100, cb, par) end)
+    writeconfig()
+end
+
+function up(remote, cb, par)
+    par = par or {}
+    print("up: ".. remote)
+    config[remote].rc=config[remote].rc+1
+    somfy.sendcommand(pin, config[remote].address, somfy.UP, config[remote].rc, 16, function() wait(100, cb, par) end)
+    writeconfig()
+end
+
+function downStep(remote, cb, par)
+    par = par or {}
+    print("downStep: ".. remote)
+    config[remote].rc=config[remote].rc+1
+    somfy.sendcommand(pin, config[remote].address, somfy.DOWN, config[remote].rc, 2,  function() wait(300, cb, par) end)
+    writeconfig()
+end
+
+function upStep(remote, cb, par)
+    par = par or {}
+    print("upStep: ".. remote)
+    config[remote].rc=config[remote].rc+1
+    somfy.sendcommand(pin, config[remote].address, somfy.UP, config[remote].rc, 2, function() wait(300, cb, par) end)
+    writeconfig()
+end
+
+function wait(ms, cb, par)
+    par = par or {}
+    print("wait: ".. ms)
+    if cb then tmr.alarm(tmr_delay, ms, tmr.ALARM_SINGLE, function () cb(unpack(par)) end) end
+end
+
+
+--======================================================================================================--
+if not config then readconfig() end
+if #config == 0 then -- somfy.cfg does not exist
+    config = cjson.decode([[{"window1":{"rc":1,"address":123},"window2":{"rc":1,"address":124}}]])
+    config_saved = deepcopy(config)
+end
+down('window1', 
+    wait, {60000, 
+    up, {'window1', 
+    wait, {9000, 
+    downStep, {'window1', downStep, {'window1', downStep, {'window1', downStep, {'window1', downStep, {'window1', downStep, {'window1', downStep, {'window1'
+}}}}}}}}}})

+ 6 - 0
sdk-overrides/include/user_interface.h

@@ -6,5 +6,11 @@
 bool wifi_softap_deauth(uint8 mac[6]);
 uint8 get_fpm_auto_sleep_flag(void);
 
+enum ext_flash_size_map {
+    FLASH_SIZE_32M_MAP_2048_2048 = 7,
+    FLASH_SIZE_64M_MAP = 8,
+    FLASH_SIZE_128M_MAP = 9
+};
+
 
 #endif /* SDK_OVERRIDES_INCLUDE_USER_INTERFACE_H_ */

+ 1 - 1
tools/pr-build.sh

@@ -21,7 +21,7 @@ cat user_config.h
 
 cd "$TRAVIS_BUILD_DIR"/ld || exit
 # increase irom0_0_seg size for all modules build
-sed -E -i.bak 's@(.*irom0_0_seg *:.*len *=) *[^,]*(.*)@\1 0xC0000\2@' nodemcu.ld
+sed -E -i.bak 's@(.*irom0_0_seg *:.*len *=) *[^,]*(.*)@\1 0xD0000\2@' nodemcu.ld
 cat nodemcu.ld
 
 # change to "root" directory no matter where the script was started from

+ 1 - 1
tools/spiffsimg/Makefile

@@ -2,7 +2,7 @@ SRCS=\
 	main.c \
   ../../app/spiffs/spiffs_cache.c  ../../app/spiffs/spiffs_check.c  ../../app/spiffs/spiffs_gc.c  ../../app/spiffs/spiffs_hydrogen.c  ../../app/spiffs/spiffs_nucleus.c
 
-CFLAGS=-g -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -I. -I../../app/spiffs -I../../app/include -DNODEMCU_SPIFFS_NO_INCLUDE --include spiffs_typedefs.h
+CFLAGS=-g -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -I. -I../../app/spiffs -I../../app/include -DNODEMCU_SPIFFS_NO_INCLUDE --include spiffs_typedefs.h -Ddbg_printf=printf
 
 spiffsimg: $(SRCS)
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@

Някои файлове не бяха показани, защото твърде много файлове са промени