From 5a7187ef79efc6333896a5869dcb9f2ab2316b82 Mon Sep 17 00:00:00 2001 From: Andrew McDonnell Date: Tue, 30 Jun 2015 22:58:42 +0930 Subject: [PATCH 01/53] Add reference on how to enable printf function --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 74b1372ad..e560b2910 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ more than 20 milliseconds is not recommended. By default the diagnostic output from WiFi libraries is disabled when you call ```Serial.begin```. To enable debug output again, call ```Serial.setDebugOutput(true);```. To redirect debug output to ```Serial1``` instead, call ```Serial1.setDebugOutput(true);```. +You also need to use ```Serial.setDebugOutput(true)``` to enable the Arduinio ```printf()``` function. + Both ```Serial``` and ```Serial1``` objects support 5, 6, 7, 8 data bits, odd (O), even (E), and no (N) parity, and 1 or 2 stop bits. To set the desired mode, call ```Serial.begin(baudrate, SERIAL_8N1);```, ```Serial.begin(baudrate, SERIAL_6E2);```, etc. #### Progmem #### From aad27017645b54c5901247831e25a07f7e2d022d Mon Sep 17 00:00:00 2001 From: Andrew McDonnell Date: Tue, 30 Jun 2015 23:02:19 +0930 Subject: [PATCH 02/53] Spelling mistake --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e560b2910..3a43e519b 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ more than 20 milliseconds is not recommended. By default the diagnostic output from WiFi libraries is disabled when you call ```Serial.begin```. To enable debug output again, call ```Serial.setDebugOutput(true);```. To redirect debug output to ```Serial1``` instead, call ```Serial1.setDebugOutput(true);```. -You also need to use ```Serial.setDebugOutput(true)``` to enable the Arduinio ```printf()``` function. +You also need to use ```Serial.setDebugOutput(true)``` to enable output from the Arduino ```printf()``` function. Both ```Serial``` and ```Serial1``` objects support 5, 6, 7, 8 data bits, odd (O), even (E), and no (N) parity, and 1 or 2 stop bits. To set the desired mode, call ```Serial.begin(baudrate, SERIAL_8N1);```, ```Serial.begin(baudrate, SERIAL_6E2);```, etc. From 6f63ad1ee4cb6a3470c29566635ee56ce7adad97 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 6 Jul 2015 08:02:24 +0300 Subject: [PATCH 03/53] Fix return value of WiFiClient::connect (#511) --- libraries/ESP8266WiFi/src/WiFiClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266WiFi/src/WiFiClient.cpp b/libraries/ESP8266WiFi/src/WiFiClient.cpp index 9117a4192..3b4a9b6f0 100644 --- a/libraries/ESP8266WiFi/src/WiFiClient.cpp +++ b/libraries/ESP8266WiFi/src/WiFiClient.cpp @@ -110,7 +110,7 @@ int WiFiClient::connect(IPAddress ip, uint16_t port) netif* interface = ip_route(&addr); if (!interface) { DEBUGV("no route to host\r\n"); - return 1; + return 0; } tcp_pcb* pcb = tcp_new(); From f1c914fff8ab275c0b347c8560e2e6b19b1cd0d3 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 6 Jul 2015 08:08:22 +0300 Subject: [PATCH 04/53] Fix strncat (#509) --- cores/esp8266/libc_replacements.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cores/esp8266/libc_replacements.c b/cores/esp8266/libc_replacements.c index 67ed0dbef..130ea7eaa 100644 --- a/cores/esp8266/libc_replacements.c +++ b/cores/esp8266/libc_replacements.c @@ -160,13 +160,12 @@ char* ICACHE_FLASH_ATTR strcat(char * dest, const char * src) { } char* ICACHE_FLASH_ATTR strncat(char * dest, const char * src, size_t n) { - uint32_t offset = strlen(dest); - for(uint32_t i = 0; i < n; i++) { - *(dest + i + offset) = *(src + i); - if(*(src + i) == 0x00) { - break; - } + size_t i; + size_t offset = strlen(dest); + for(i = 0; i < n && src[i]; i++) { + dest[i + offset] = src[i]; } + dest[i + offset] = 0; return dest; } From bd5187afc41a800fd94aeb23ee42a35ecc1048d9 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 6 Jul 2015 10:45:05 +0300 Subject: [PATCH 05/53] Use ROM functions directly instead of wrappers --- cores/esp8266/core_esp8266_noniso.c | 1 - cores/esp8266/libc_replacements.c | 32 ----------------------------- 2 files changed, 33 deletions(-) diff --git a/cores/esp8266/core_esp8266_noniso.c b/cores/esp8266/core_esp8266_noniso.c index eafe4fa72..f9ed06a98 100644 --- a/cores/esp8266/core_esp8266_noniso.c +++ b/cores/esp8266/core_esp8266_noniso.c @@ -27,7 +27,6 @@ #include "ets_sys.h" #define sprintf ets_sprintf -#define strcpy ets_strcpy int atoi(const char* s) { return (int) atol(s); diff --git a/cores/esp8266/libc_replacements.c b/cores/esp8266/libc_replacements.c index 130ea7eaa..e8e31afa4 100644 --- a/cores/esp8266/libc_replacements.c +++ b/cores/esp8266/libc_replacements.c @@ -87,38 +87,6 @@ int vsnprintf(char * buffer, size_t size, const char * format, va_list arg) { return ets_vsnprintf(buffer, size, format, arg); } -int memcmp(const void *s1, const void *s2, size_t n) { - return ets_memcmp(s1, s2, n); -} - -void* memcpy(void *dest, const void *src, size_t n) { - return ets_memcpy(dest, src, n); -} - -void* memset(void *s, int c, size_t n) { - return ets_memset(s, c, n); -} - -int strcmp(const char *s1, const char *s2) { - return ets_strcmp(s1, s2); -} - -char* strcpy(char *dest, const char *src) { - return ets_strcpy(dest, src); -} - -size_t strlen(const char *s) { - return ets_strlen(s); -} - -int strncmp(const char *s1, const char *s2, size_t len) { - return ets_strncmp(s1, s2, len); -} - -char* strncpy(char * dest, const char * src, size_t n) { - return ets_strncpy(dest, src, n); -} - size_t ICACHE_FLASH_ATTR strnlen(const char *s, size_t len) { // there is no ets_strnlen const char *cp; From 93f954d3638afa9d6b1717a5003f5f7fdbb6a11c Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 6 Jul 2015 12:20:20 +0300 Subject: [PATCH 06/53] Add missing header --- cores/esp8266/core_esp8266_noniso.c | 1 + 1 file changed, 1 insertion(+) diff --git a/cores/esp8266/core_esp8266_noniso.c b/cores/esp8266/core_esp8266_noniso.c index f9ed06a98..eddac25a5 100644 --- a/cores/esp8266/core_esp8266_noniso.c +++ b/cores/esp8266/core_esp8266_noniso.c @@ -23,6 +23,7 @@ */ #include +#include #include "stdlib_noniso.h" #include "ets_sys.h" From a82796f83faf8b8fef0d3763c33b9ebe4970c43a Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 6 Jul 2015 12:22:30 +0300 Subject: [PATCH 07/53] Revert a460efb --- libraries/ESP8266WiFi/src/lwip/ip_addr.h | 16 ---------------- libraries/ESP8266mDNS/ESP8266mDNS.cpp | 4 ++-- tools/sdk/include/ip_addr.h | 12 +++++++++--- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/libraries/ESP8266WiFi/src/lwip/ip_addr.h b/libraries/ESP8266WiFi/src/lwip/ip_addr.h index 2787d1285..cfc10f809 100644 --- a/libraries/ESP8266WiFi/src/lwip/ip_addr.h +++ b/libraries/ESP8266WiFi/src/lwip/ip_addr.h @@ -41,11 +41,9 @@ extern "C" { /* This is the aligned version of ip_addr_t, used as local variable, on the stack, etc. */ -#if !defined(IP2STR) struct ip_addr { u32_t addr; }; -#endif /* This is the packed version of ip_addr_t, used in network headers that are itself packed */ @@ -63,9 +61,7 @@ PACK_STRUCT_END /** ip_addr_t uses a struct for convenience only, so that the same defines can * operate both on ip_addr_t as well as on ip_addr_p_t. */ -#if !defined(IP2STR) typedef struct ip_addr ip_addr_t; -#endif typedef struct ip_addr_packed ip_addr_p_t; /* @@ -97,15 +93,11 @@ extern const ip_addr_t ip_addr_broadcast; #define IP_ADDR_BROADCAST ((ip_addr_t *)&ip_addr_broadcast) /** 255.255.255.255 */ -#if !defined(IPADDR_NONE) #define IPADDR_NONE ((u32_t)0xffffffffUL) -#endif /** 127.0.0.1 */ #define IPADDR_LOOPBACK ((u32_t)0x7f000001UL) /** 0.0.0.0 */ -#if !defined(IPADDR_ANY) #define IPADDR_ANY ((u32_t)0x00000000UL) -#endif /** 255.255.255.255 */ #define IPADDR_BROADCAST ((u32_t)0xffffffffUL) @@ -142,7 +134,6 @@ extern const ip_addr_t ip_addr_broadcast; #define IP_LOOPBACKNET 127 /* official! */ -#if !defined(IP4_ADDR) #if BYTE_ORDER == BIG_ENDIAN /** Set an IP address given by the four byte-parts */ #define IP4_ADDR(ipaddr, a,b,c,d) \ @@ -159,7 +150,6 @@ extern const ip_addr_t ip_addr_broadcast; ((u32_t)((b) & 0xff) << 8) | \ (u32_t)((a) & 0xff) #endif -#endif /** MEMCPY-like copying of IP addresses where addresses are known to be * 16-bit-aligned if the port is correctly configured (so a port could define @@ -227,7 +217,6 @@ u8_t ip4_addr_netmask_valid(u32_t netmask)ICACHE_FLASH_ATTR; ipaddr != NULL ? ip4_addr4_16(ipaddr) : 0)) /* Get one byte from the 4-byte address */ -#if !defined(IP2STR) #define ip4_addr1(ipaddr) (((u8_t*)(ipaddr))[0]) #define ip4_addr2(ipaddr) (((u8_t*)(ipaddr))[1]) #define ip4_addr3(ipaddr) (((u8_t*)(ipaddr))[2]) @@ -238,20 +227,16 @@ u8_t ip4_addr_netmask_valid(u32_t netmask)ICACHE_FLASH_ATTR; #define ip4_addr2_16(ipaddr) ((u16_t)ip4_addr2(ipaddr)) #define ip4_addr3_16(ipaddr) ((u16_t)ip4_addr3(ipaddr)) #define ip4_addr4_16(ipaddr) ((u16_t)ip4_addr4(ipaddr)) -#endif /** For backwards compatibility */ #define ip_ntoa(ipaddr) ipaddr_ntoa(ipaddr) -#if !defined(IP2STR) u32_t ipaddr_addr(const char *cp)ICACHE_FLASH_ATTR; -#endif int ipaddr_aton(const char *cp, ip_addr_t *addr)ICACHE_FLASH_ATTR; /** returns ptr to static buffer; not reentrant! */ char *ipaddr_ntoa(const ip_addr_t *addr)ICACHE_FLASH_ATTR; char *ipaddr_ntoa_r(const ip_addr_t *addr, char *buf, int buflen)ICACHE_FLASH_ATTR; -#if !defined(IP2STR) #define IP2STR(ipaddr) ip4_addr1_16(ipaddr), \ ip4_addr2_16(ipaddr), \ ip4_addr3_16(ipaddr), \ @@ -264,7 +249,6 @@ struct ip_info { struct ip_addr netmask; struct ip_addr gw; }; -#endif #ifdef __cplusplus } #endif diff --git a/libraries/ESP8266mDNS/ESP8266mDNS.cpp b/libraries/ESP8266mDNS/ESP8266mDNS.cpp index 640aa2754..3868a5c8b 100644 --- a/libraries/ESP8266mDNS/ESP8266mDNS.cpp +++ b/libraries/ESP8266mDNS/ESP8266mDNS.cpp @@ -32,7 +32,8 @@ License (MIT license): // - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt // - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt -#define LWIP_INTERNAL +#define LWIP_OPEN_SRC + #include "ESP8266mDNS.h" #include @@ -41,7 +42,6 @@ License (MIT license): extern "C" { #include "osapi.h" #include "ets_sys.h" - #include "ip_addr.h" #include "user_interface.h" } diff --git a/tools/sdk/include/ip_addr.h b/tools/sdk/include/ip_addr.h index 74ea7a2b2..fc488ea8f 100644 --- a/tools/sdk/include/ip_addr.h +++ b/tools/sdk/include/ip_addr.h @@ -10,11 +10,17 @@ struct ip_addr { typedef struct ip_addr ip_addr_t; struct ip_info { - ip_addr_t ip; - ip_addr_t netmask; - ip_addr_t gw; + struct ip_addr ip; + struct ip_addr netmask; + struct ip_addr gw; }; +#define IP4_ADDR(ipaddr, a,b,c,d) \ + (ipaddr)->addr = ((uint32)((d) & 0xff) << 24) | \ + ((uint32)((c) & 0xff) << 16) | \ + ((uint32)((b) & 0xff) << 8) | \ + (uint32)((a) & 0xff) + /** * Determine if two address are on the same network. * From dc9072b94b0e629ceca498647efffe0d47fe2c72 Mon Sep 17 00:00:00 2001 From: John Doe Date: Tue, 30 Jun 2015 23:48:09 +0300 Subject: [PATCH 08/53] Initial Upload From IDE For Test ONLY --- boards.txt | 22 ++++-- libraries/ESP8266mDNS/ESP8266mDNS.cpp | 8 ++- .../DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino | 50 ++++++++++++++ platform.txt | 9 +++ tools/espota.py | 67 +++++++++++++++++++ 5 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino create mode 100755 tools/espota.py diff --git a/boards.txt b/boards.txt index d90802e6d..f92c773b4 100644 --- a/boards.txt +++ b/boards.txt @@ -1,7 +1,9 @@ menu.UploadSpeed=Upload Speed menu.CpuFrequency=CPU Frequency menu.FlashSize=Flash Size +menu.FlashMode=Flash Mode menu.FlashFreq=Flash Frequency +menu.UploadTool=Upload Using ############################################################## generic.name=Generic ESP8266 Module @@ -23,11 +25,26 @@ generic.build.variant=generic generic.build.flash_mode=qio generic.build.spiffs_pagesize=256 +generic.menu.UploadTool.esptool=Serial +generic.menu.UploadTool.esptool.upload.tool=esptool +generic.menu.UploadTool.espota=OTA +generic.menu.UploadTool.espota.upload.tool=espota + generic.menu.CpuFrequency.80=80 MHz generic.menu.CpuFrequency.80.build.f_cpu=80000000L generic.menu.CpuFrequency.160=160 MHz generic.menu.CpuFrequency.160.build.f_cpu=160000000L +generic.menu.FlashFreq.40=40MHz +generic.menu.FlashFreq.40.build.flash_freq=40 +generic.menu.FlashFreq.80=80MHz +generic.menu.FlashFreq.80.build.flash_freq=80 + +generic.menu.FlashMode.dio=DIO +generic.menu.FlashMode.dio.build.flash_mode=dio +generic.menu.FlashMode.qio=QIO +generic.menu.FlashMode.qio.build.flash_mode=qio + generic.menu.UploadSpeed.115200=115200 generic.menu.UploadSpeed.115200.upload.speed=115200 generic.menu.UploadSpeed.9600=9600 @@ -117,11 +134,6 @@ generic.menu.FlashSize.4M.upload.maximum_size=1044464 # generic.menu.FlashSize.16M.build.spiffs_end=0x1000000 # generic.menu.FlashSize.16M.build.spiffs_blocksize=8192 -generic.menu.FlashFreq.40=40MHz -generic.menu.FlashFreq.40.build.flash_freq=40 -generic.menu.FlashFreq.80=80MHz -generic.menu.FlashFreq.80.build.flash_freq=80 - ############################################################## modwifi.name=Olimex MOD-WIFI-ESP8266(-DEV) diff --git a/libraries/ESP8266mDNS/ESP8266mDNS.cpp b/libraries/ESP8266mDNS/ESP8266mDNS.cpp index 3868a5c8b..6e50313a4 100644 --- a/libraries/ESP8266mDNS/ESP8266mDNS.cpp +++ b/libraries/ESP8266mDNS/ESP8266mDNS.cpp @@ -66,11 +66,16 @@ extern "C" { #define MDNS_TYPE_PTR 0x000C #define MDNS_TYPE_SRV 0x0021 #define MDNS_TYPE_TXT 0x0010 -#define MDNS_TYPE_NSEC 0x002F #define MDNS_CLASS_IN 0x0001 #define MDNS_CLASS_IN_FLUSH_CACHE 0x8001 +#define MDNS_ANSWERS_ALL 0x0F +#define MDNS_ANSWER_PTR 0x08 +#define MDNS_ANSWER_TXT 0x04 +#define MDNS_ANSWER_SRV 0x02 +#define MDNS_ANSWER_A 0x01 + #define _conn_read32() (((uint32_t)_conn->read() << 24) | ((uint32_t)_conn->read() << 16) | ((uint32_t)_conn->read() << 8) | _conn->read()) #define _conn_read16() (((uint16_t)_conn->read() << 8) | _conn->read()) #define _conn_read8() _conn->read() @@ -341,7 +346,6 @@ void MDNSResponder::_parsePacket(){ else if(currentType == MDNS_TYPE_PTR) os_printf(" PTR "); else if(currentType == MDNS_TYPE_SRV) os_printf(" SRV "); else if(currentType == MDNS_TYPE_TXT) os_printf(" TXT "); - else if(currentType == MDNS_TYPE_NSEC) os_printf(" NSEC "); else os_printf(" 0x%04X ", currentType); if(currentClass == MDNS_CLASS_IN) os_printf(" IN "); diff --git a/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino new file mode 100644 index 000000000..12f8cad82 --- /dev/null +++ b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino @@ -0,0 +1,50 @@ +#include +#include +#include + +const char* host = "esp8266-ota"; +const char* ssid = "**********"; +const char* pass = "**********"; +const uint16_t aport = 8266; + +WiFiUDP listener; + +void setup() { + Serial.begin(115200); + Serial.setDebugOutput(true); + Serial.println(""); + Serial.println("Arduino OTA Test"); + + Serial.printf("Sketch size: %u\n", ESP.getSketchSize()); + Serial.printf("Free size: %u\n", ESP.getFreeSketchSpace()); + + WiFi.begin(ssid, pass); + if(WiFi.waitForConnectResult() == WL_CONNECTED){ + MDNS.begin(host); + MDNS.addService("arduino", "tcp", aport); + listener.begin(aport); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } +} + +void loop() { + if (ota_cmd_listener.parsePacket()) { + IPAddress remote = listener.remoteIP(); + int cmd = listener.parseInt(); + int port = listener.parseInt(); + int sz = listener.parseInt(); + Serial.printf("Starting Update: cmd:%d, port:%d, size:%d\r\n", cmd, port, sz); + WiFiClient cl; + if (!cl.connect(remote, port)) { + Serial.println("Failed to connect"); + return; + } + listener.stop(); + if (!ESP.updateSketch(cl, sz)) { + Serial.println("Update failed"); + listener.begin(aport); + } + } + delay(100); +} diff --git a/platform.txt b/platform.txt index bf3b930b5..8437e509c 100644 --- a/platform.txt +++ b/platform.txt @@ -93,3 +93,12 @@ tools.esptool.upload.protocol=esp tools.esptool.upload.params.verbose=-vv tools.esptool.upload.params.quiet= tools.esptool.upload.pattern="{path}/{cmd}" {upload.verbose} -cd {upload.resetmethod} -cb {upload.speed} -cp "{serial.port}" -ca 0x00000 -cf "{build.path}/{build.project_name}.bin" + +tools.espota.cmd=python +tools.espota.cmd.windows=python.exe +tools.espota.path={runtime.platform.path}/tools + +tools.espota.upload.protocol=espota +tools.espota.upload.params.verbose= +tools.espota.upload.params.quiet= +tools.espota.upload.pattern="{cmd}" "{path}/espota.py" "{serial.port}" "{build.path}/{build.project_name}.bin" diff --git a/tools/espota.py b/tools/espota.py new file mode 100755 index 000000000..18aaec10e --- /dev/null +++ b/tools/espota.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# +# this script will push an OTA update to the ESP +# +# use it like: python ota_server.py +# +# on the ESP side you need code like this: https://gist.github.com/igrr/43d5c52328e955bb6b09 to handle the update +# + +import socket +import sys +import os + +def serve(remoteAddr, filename): + # Create a TCP/IP socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serverPort = 48266 + server_address = ('0.0.0.0', serverPort) + print >>sys.stderr, 'starting up on %s port %s' % server_address + sock.bind(server_address) + sock.listen(1) + + sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + remote_address = (remoteAddr, 8266) + content_size = os.path.getsize(filename) + print >>sys.stderr, 'upload size: %d' % content_size + message = '%d %d %d\n' % (0, serverPort, content_size) + print >>sys.stderr, 'sending invitation %s' % message + sent = sock2.sendto(message, remote_address) + sent = sock2.sendto(message, remote_address) + sent = sock2.sendto(message, remote_address) + + + while True: + # Wait for a connection + print >>sys.stderr, 'waiting for connection' + connection, client_address = sock.accept() + try: + print >>sys.stderr, 'connection from', client_address + + print >>sys.stderr, 'opening file %s' % filename + f = open(filename, "rb") + + while True: + chunk = f.read(4096) + if not chunk: + break + + print >>sys.stderr, 'sending %d' % len(chunk) + connection.sendall(chunk) + + print >>sys.stderr, 'done!' + return 0 + + finally: + connection.close() + f.close() + return 1 + +def main(args): + return serve(args[1], args[2]) + + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + From 83eeaa31e0e91dc7e758c889cbb849ce4af4a048 Mon Sep 17 00:00:00 2001 From: John Doe Date: Wed, 1 Jul 2015 00:06:40 +0300 Subject: [PATCH 09/53] typo --- .../examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino index 12f8cad82..ca611218d 100644 --- a/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino +++ b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino @@ -29,7 +29,7 @@ void setup() { } void loop() { - if (ota_cmd_listener.parsePacket()) { + if (listener.parsePacket()) { IPAddress remote = listener.remoteIP(); int cmd = listener.parseInt(); int port = listener.parseInt(); From 5efd9a1f81d6c96e575ccb50a0e0e52b65d9166c Mon Sep 17 00:00:00 2001 From: John Doe Date: Wed, 1 Jul 2015 01:46:34 +0300 Subject: [PATCH 10/53] Some print enhancements works on newer python as well --- tools/espota.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tools/espota.py b/tools/espota.py index 18aaec10e..2ad639584 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -7,6 +7,7 @@ # on the ESP side you need code like this: https://gist.github.com/igrr/43d5c52328e955bb6b09 to handle the update # +from __future__ import print_function import socket import sys import os @@ -16,16 +17,16 @@ def serve(remoteAddr, filename): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverPort = 48266 server_address = ('0.0.0.0', serverPort) - print >>sys.stderr, 'starting up on %s port %s' % server_address + print('starting up on %s port %s' % server_address, file=sys.stderr) sock.bind(server_address) sock.listen(1) sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) remote_address = (remoteAddr, 8266) content_size = os.path.getsize(filename) - print >>sys.stderr, 'upload size: %d' % content_size + print('upload size: %d' % content_size, file=sys.stderr) message = '%d %d %d\n' % (0, serverPort, content_size) - print >>sys.stderr, 'sending invitation %s' % message + print('sending invitation', file=sys.stderr) sent = sock2.sendto(message, remote_address) sent = sock2.sendto(message, remote_address) sent = sock2.sendto(message, remote_address) @@ -33,23 +34,25 @@ def serve(remoteAddr, filename): while True: # Wait for a connection - print >>sys.stderr, 'waiting for connection' + print('waiting...', file=sys.stderr) connection, client_address = sock.accept() try: - print >>sys.stderr, 'connection from', client_address + print('connection from', client_address, file=sys.stderr) - print >>sys.stderr, 'opening file %s' % filename + print('sending file %s\n' % filename, file=sys.stderr) f = open(filename, "rb") while True: chunk = f.read(4096) if not chunk: break - - print >>sys.stderr, 'sending %d' % len(chunk) + + sys.stderr.write('.') + sys.stderr.flush() + #print('sending %d' % len(chunk), file=sys.stderr) connection.sendall(chunk) - print >>sys.stderr, 'done!' + print('\ndone!', file=sys.stderr) return 0 finally: From 03a2b4808b240ac4b7825857dc74f3d31b0c3fdb Mon Sep 17 00:00:00 2001 From: John Doe Date: Wed, 1 Jul 2015 14:38:13 +0300 Subject: [PATCH 11/53] add 10 seconds timeout for waiting on ESP to start the update --- tools/espota.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/espota.py b/tools/espota.py index 2ad639584..f1f2baca6 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -26,16 +26,16 @@ def serve(remoteAddr, filename): content_size = os.path.getsize(filename) print('upload size: %d' % content_size, file=sys.stderr) message = '%d %d %d\n' % (0, serverPort, content_size) - print('sending invitation', file=sys.stderr) - sent = sock2.sendto(message, remote_address) - sent = sock2.sendto(message, remote_address) - sent = sock2.sendto(message, remote_address) - while True: # Wait for a connection + print('sending invitation', file=sys.stderr) + sent = sock2.sendto(message, remote_address) + sock.settimeout(10) print('waiting...', file=sys.stderr) connection, client_address = sock.accept() + sock.settimeout(None) + connection.settimeout(None) try: print('connection from', client_address, file=sys.stderr) From 2e08c5d7978915de40bd913fe3bf3794dfa5dec4 Mon Sep 17 00:00:00 2001 From: John Doe Date: Wed, 1 Jul 2015 15:38:30 +0300 Subject: [PATCH 12/53] Add an option to reboot on failed update --- cores/esp8266/Esp.cpp | 13 ++++++++++--- cores/esp8266/Esp.h | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index cda089b04..bf374814e 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -358,10 +358,12 @@ uint32_t EspClass::getFreeSketchSpace() { return freeSpaceEnd - freeSpaceStart; } -bool EspClass::updateSketch(Stream& in, uint32_t size) { +bool EspClass::updateSketch(Stream& in, uint32_t size, bool restartOnFail) { - if (size > getFreeSketchSpace()) + if (size > getFreeSketchSpace()){ + if(restartOnFail) ESP.restart(); return false; + } uint32_t usedSize = getSketchSize(); uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); @@ -374,8 +376,10 @@ bool EspClass::updateSketch(Stream& in, uint32_t size) { noInterrupts(); int rc = SPIEraseAreaEx(freeSpaceStart, roundedSize); interrupts(); - if (rc) + if (rc){ + if(restartOnFail) ESP.restart(); return false; + } #ifdef DEBUG_SERIAL DEBUG_SERIAL.println("erase done"); @@ -397,12 +401,14 @@ bool EspClass::updateSketch(Stream& in, uint32_t size) { #ifdef DEBUG_SERIAL DEBUG_SERIAL.println("stream read failed"); #endif + if(restartOnFail) ESP.restart(); return false; } if(addr == freeSpaceStart) { // check for valid first magic byte if(*((uint8 *) buffer.get()) != 0xE9) { + if(restartOnFail) ESP.restart(); return false; } } @@ -414,6 +420,7 @@ bool EspClass::updateSketch(Stream& in, uint32_t size) { #ifdef DEBUG_SERIAL DEBUG_SERIAL.println("write failed"); #endif + if(restartOnFail) ESP.restart(); return false; } diff --git a/cores/esp8266/Esp.h b/cores/esp8266/Esp.h index 80555d591..8021ed198 100644 --- a/cores/esp8266/Esp.h +++ b/cores/esp8266/Esp.h @@ -116,7 +116,7 @@ class EspClass { uint32_t getSketchSize(); uint32_t getFreeSketchSpace(); - bool updateSketch(Stream& in, uint32_t size); + bool updateSketch(Stream& in, uint32_t size, bool restartOnFail = false); String getResetInfo(); struct rst_info * getResetInfoPtr(); From d828312299457312222effbd8c93f9cb5058d8e8 Mon Sep 17 00:00:00 2001 From: John Doe Date: Wed, 1 Jul 2015 17:06:59 +0300 Subject: [PATCH 13/53] fix fail on slow streams --- cores/esp8266/Esp.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index bf374814e..a33cfa2a0 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -399,12 +399,18 @@ bool EspClass::updateSketch(Stream& in, uint32_t size, bool restartOnFail) { size_t rd = in.readBytes(buffer.get(), willRead); if (rd != willRead) { #ifdef DEBUG_SERIAL - DEBUG_SERIAL.println("stream read failed"); + DEBUG_SERIAL.printf("stream read less: %u/%u\n", rd, willRead); #endif - if(restartOnFail) ESP.restart(); - return false; + if(rd == 0){ //we got nothing from the client + //we should actually give it a bit of a chance to send us something + //connection could be slow ;) + if(restartOnFail) ESP.restart(); + return false; + } + //we at least got some data, lets write it to the flash + willRead = rd; } - + if(addr == freeSpaceStart) { // check for valid first magic byte if(*((uint8 *) buffer.get()) != 0xE9) { From 37bd383c59abba9394b9c9c04d6502182dc3996e Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 6 Jul 2015 12:29:56 +0300 Subject: [PATCH 14/53] Part of 7cd54a4 without IDE changes --- platform.txt | 3 ++- tools/espota.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/platform.txt b/platform.txt index 8437e509c..050140f03 100644 --- a/platform.txt +++ b/platform.txt @@ -93,6 +93,7 @@ tools.esptool.upload.protocol=esp tools.esptool.upload.params.verbose=-vv tools.esptool.upload.params.quiet= tools.esptool.upload.pattern="{path}/{cmd}" {upload.verbose} -cd {upload.resetmethod} -cb {upload.speed} -cp "{serial.port}" -ca 0x00000 -cf "{build.path}/{build.project_name}.bin" +tools.esptool.network.pattern="{cmd}" "{path}/espota.py" "{serial.port}" "{network.port}" "{build.path}/{build.project_name}.bin" tools.espota.cmd=python tools.espota.cmd.windows=python.exe @@ -101,4 +102,4 @@ tools.espota.path={runtime.platform.path}/tools tools.espota.upload.protocol=espota tools.espota.upload.params.verbose= tools.espota.upload.params.quiet= -tools.espota.upload.pattern="{cmd}" "{path}/espota.py" "{serial.port}" "{build.path}/{build.project_name}.bin" +tools.espota.upload.pattern="{cmd}" "{path}/espota.py" "{serial.port}" 8266 "{build.path}/{build.project_name}.bin" diff --git a/tools/espota.py b/tools/espota.py index f1f2baca6..af421b319 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -12,7 +12,7 @@ import socket import sys import os -def serve(remoteAddr, filename): +def serve(remoteAddr, remotePort, filename): # Create a TCP/IP socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverPort = 48266 @@ -22,7 +22,7 @@ def serve(remoteAddr, filename): sock.listen(1) sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - remote_address = (remoteAddr, 8266) + remote_address = (remoteAddr, remotePort) content_size = os.path.getsize(filename) print('upload size: %d' % content_size, file=sys.stderr) message = '%d %d %d\n' % (0, serverPort, content_size) @@ -61,7 +61,7 @@ def serve(remoteAddr, filename): return 1 def main(args): - return serve(args[1], args[2]) + return serve(args[1], args[2], args[3]) From 44ac799481256dcaf1fcd72503ecf073effe811b Mon Sep 17 00:00:00 2001 From: John Doe Date: Wed, 1 Jul 2015 23:56:53 +0300 Subject: [PATCH 15/53] bad command --- platform.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform.txt b/platform.txt index 050140f03..169dd96ad 100644 --- a/platform.txt +++ b/platform.txt @@ -93,7 +93,7 @@ tools.esptool.upload.protocol=esp tools.esptool.upload.params.verbose=-vv tools.esptool.upload.params.quiet= tools.esptool.upload.pattern="{path}/{cmd}" {upload.verbose} -cd {upload.resetmethod} -cb {upload.speed} -cp "{serial.port}" -ca 0x00000 -cf "{build.path}/{build.project_name}.bin" -tools.esptool.network.pattern="{cmd}" "{path}/espota.py" "{serial.port}" "{network.port}" "{build.path}/{build.project_name}.bin" +tools.esptool.network.pattern=python "{path}/espota.py" "{serial.port}" "{network.port}" "{build.path}/{build.project_name}.bin" tools.espota.cmd=python tools.espota.cmd.windows=python.exe From 86cf9b2c4fe102d396e586d055798f06c3be43d7 Mon Sep 17 00:00:00 2001 From: John Doe Date: Thu, 2 Jul 2015 00:03:31 +0300 Subject: [PATCH 16/53] need to be int --- tools/espota.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/espota.py b/tools/espota.py index af421b319..b98d610aa 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -22,7 +22,7 @@ def serve(remoteAddr, remotePort, filename): sock.listen(1) sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - remote_address = (remoteAddr, remotePort) + remote_address = (remoteAddr, int(remotePort)) content_size = os.path.getsize(filename) print('upload size: %d' % content_size, file=sys.stderr) message = '%d %d %d\n' % (0, serverPort, content_size) From 6f2069deac3efaa624ad99b5e4479ca09c32ef04 Mon Sep 17 00:00:00 2001 From: John Doe Date: Fri, 3 Jul 2015 13:59:11 +0300 Subject: [PATCH 17/53] New Update library, example, upload and more Proper error handling in the uploading python script Much faster OTA example sketch with better results New Update class that simplifies updating the firmware from any source Updated Esp.updateSketch() to use the new class --- cores/esp8266/Arduino.h | 1 + cores/esp8266/Esp.cpp | 106 +++------- cores/esp8266/Esp.h | 2 +- cores/esp8266/Updater.cpp | 190 ++++++++++++++++++ cores/esp8266/Updater.h | 119 +++++++++++ .../DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino | 78 +++++-- tools/espota.py | 80 +++++--- 7 files changed, 446 insertions(+), 130 deletions(-) create mode 100644 cores/esp8266/Updater.cpp create mode 100644 cores/esp8266/Updater.h diff --git a/cores/esp8266/Arduino.h b/cores/esp8266/Arduino.h index a715f5215..61e5711a4 100644 --- a/cores/esp8266/Arduino.h +++ b/cores/esp8266/Arduino.h @@ -233,6 +233,7 @@ void loop(void); #include "HardwareSerial.h" #include "Esp.h" +#include "Updater.h" #include "debug.h" #define min(a,b) ((a)<(b)?(a):(b)) diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index a33cfa2a0..4e0b75106 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -30,7 +30,7 @@ extern struct rst_info resetInfo; } -// #define DEBUG_SERIAL Serial +//#define DEBUG_SERIAL Serial /** @@ -358,96 +358,38 @@ uint32_t EspClass::getFreeSketchSpace() { return freeSpaceEnd - freeSpaceStart; } -bool EspClass::updateSketch(Stream& in, uint32_t size, bool restartOnFail) { - - if (size > getFreeSketchSpace()){ - if(restartOnFail) ESP.restart(); - return false; - } - - uint32_t usedSize = getSketchSize(); - uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); - uint32_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); - +bool EspClass::updateSketch(Stream& in, uint32_t size, bool restartOnFail, bool restartOnSuccess) { + if(!Update.begin(size)){ #ifdef DEBUG_SERIAL - DEBUG_SERIAL.printf("erase @0x%x size=0x%x\r\n", freeSpaceStart, roundedSize); + DEBUG_SERIAL.print("Update "); + Update.printError(DEBUG_SERIAL); #endif + if(restartOnFail) ESP.restart(); + return false; + } - noInterrupts(); - int rc = SPIEraseAreaEx(freeSpaceStart, roundedSize); - interrupts(); - if (rc){ - if(restartOnFail) ESP.restart(); - return false; - } - + if(Update.writeStream(in) != size){ #ifdef DEBUG_SERIAL - DEBUG_SERIAL.println("erase done"); + DEBUG_SERIAL.print("Update "); + Update.printError(DEBUG_SERIAL); #endif + if(restartOnFail) ESP.restart(); + return false; + } - uint32_t addr = freeSpaceStart; - uint32_t left = size; - - const uint32_t bufferSize = FLASH_SECTOR_SIZE; - std::unique_ptr buffer(new uint8_t[bufferSize]); - + if(!Update.end()){ #ifdef DEBUG_SERIAL - DEBUG_SERIAL.println("writing"); + DEBUG_SERIAL.print("Update "); + Update.printError(DEBUG_SERIAL); #endif - while (left > 0) { - size_t willRead = (left < bufferSize) ? left : bufferSize; - size_t rd = in.readBytes(buffer.get(), willRead); - if (rd != willRead) { -#ifdef DEBUG_SERIAL - DEBUG_SERIAL.printf("stream read less: %u/%u\n", rd, willRead); -#endif - if(rd == 0){ //we got nothing from the client - //we should actually give it a bit of a chance to send us something - //connection could be slow ;) - if(restartOnFail) ESP.restart(); - return false; - } - //we at least got some data, lets write it to the flash - willRead = rd; - } - - if(addr == freeSpaceStart) { - // check for valid first magic byte - if(*((uint8 *) buffer.get()) != 0xE9) { - if(restartOnFail) ESP.restart(); - return false; - } - } - - noInterrupts(); - rc = SPIWrite(addr, buffer.get(), willRead); - interrupts(); - if (rc) { -#ifdef DEBUG_SERIAL - DEBUG_SERIAL.println("write failed"); -#endif - if(restartOnFail) ESP.restart(); - return false; - } - - addr += willRead; - left -= willRead; -#ifdef DEBUG_SERIAL - DEBUG_SERIAL.print("."); -#endif - } + if(restartOnFail) ESP.restart(); + return false; + } #ifdef DEBUG_SERIAL - DEBUG_SERIAL.println("\r\nrestarting"); -#endif - eboot_command ebcmd; - ebcmd.action = ACTION_COPY_RAW; - ebcmd.args[0] = freeSpaceStart; - ebcmd.args[1] = 0x00000; - ebcmd.args[2] = size; - eboot_command_write(&ebcmd); - - ESP.restart(); - return true; // never happens + DEBUG_SERIAL.println("Update SUCCESS"); +#endif + if(restartOnSuccess) ESP.restart(); + return true; } diff --git a/cores/esp8266/Esp.h b/cores/esp8266/Esp.h index 8021ed198..8e66f8f88 100644 --- a/cores/esp8266/Esp.h +++ b/cores/esp8266/Esp.h @@ -116,7 +116,7 @@ class EspClass { uint32_t getSketchSize(); uint32_t getFreeSketchSpace(); - bool updateSketch(Stream& in, uint32_t size, bool restartOnFail = false); + bool updateSketch(Stream& in, uint32_t size, bool restartOnFail = false, bool restartOnSuccess = true); String getResetInfo(); struct rst_info * getResetInfoPtr(); diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp new file mode 100644 index 000000000..0009f2f4a --- /dev/null +++ b/cores/esp8266/Updater.cpp @@ -0,0 +1,190 @@ +#include "Updater.h" +#include "Arduino.h" +#include "eboot_command.h" +extern "C"{ + #include "mem.h" +} +#define DEBUG_UPDATER Serial + +extern "C" uint32_t _SPIFFS_start; + +UpdaterClass::UpdaterClass() : _error(0), _buffer(0), _bufferLen(0), _size(0), _startAddress(0), _currentAddress(0) {} + +bool UpdaterClass::begin(size_t size){ + if(_size > 0){ +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.println("already running"); +#endif + return false; + } + + if(size == 0){ + _error = UPDATE_ERROR_SIZE; +#ifdef DEBUG_UPDATER + printError(DEBUG_UPDATER); +#endif + return false; + } + + if(_buffer) os_free(_buffer); + + _bufferLen = 0; + _startAddress = 0; + _currentAddress = 0; + _size = 0; + _error = 0; + + uint32_t usedSize = ESP.getSketchSize(); + uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000; + uint32_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + + if(roundedSize > (freeSpaceEnd - freeSpaceStart)){ + _error = UPDATE_ERROR_SPACE; +#ifdef DEBUG_UPDATER + printError(DEBUG_UPDATER); +#endif + return false; + } + noInterrupts(); + int rc = SPIEraseAreaEx(freeSpaceStart, roundedSize); + interrupts(); + if (rc){ + _error = UPDATE_ERROR_ERASE; +#ifdef DEBUG_UPDATER + printError(DEBUG_UPDATER); +#endif + return false; + } + _startAddress = freeSpaceStart; + _currentAddress = _startAddress; + _size = size; + _buffer = (uint8_t*)os_malloc(FLASH_SECTOR_SIZE); + + return true; +} + +bool UpdaterClass::end(){ + if(_size == 0){ +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.println("no update"); +#endif + return false; + } + + if(_buffer) os_free(_buffer); + _bufferLen = 0; + + if(hasError() || !isFinished()){ +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.printf("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size); +#endif + _currentAddress = 0; + _startAddress = 0; + _size = 0; + return false; + } + + eboot_command ebcmd; + ebcmd.action = ACTION_COPY_RAW; + ebcmd.args[0] = _startAddress; + ebcmd.args[1] = 0x00000; + ebcmd.args[2] = _size; + eboot_command_write(&ebcmd); + + _currentAddress = 0; + _startAddress = 0; + _size = 0; + _error = UPDATE_ERROR_OK; + return true; +} + +bool UpdaterClass::_writeBuffer(){ + WDT_FEED(); + noInterrupts(); + int rc = SPIWrite(_currentAddress, _buffer, _bufferLen); + interrupts(); + if (rc) { + _error = UPDATE_ERROR_WRITE; +#ifdef DEBUG_UPDATER + printError(DEBUG_UPDATER); +#endif + return false; + } + _currentAddress += _bufferLen; + _bufferLen = 0; + return true; +} + +size_t UpdaterClass::write(uint8_t *data, size_t len){ + size_t left = len; + if(hasError()) + return 0; + + while((_bufferLen + left) > FLASH_SECTOR_SIZE){ + size_t toBuff = FLASH_SECTOR_SIZE - _bufferLen; + memcpy(_buffer + _bufferLen, data + (len - left), toBuff); + _bufferLen += toBuff; + if(!_writeBuffer()){ + return len - left; + } + left -= toBuff; + yield(); + } + //lets see whats left + memcpy(_buffer + _bufferLen, data + (len - left), left); + _bufferLen += left; + if(_bufferLen == remaining()){ + //we are at the end of the update, so should write what's left to flash + if(!_writeBuffer()){ + return len - left; + } + } + return len; +} + +size_t UpdaterClass::writeStream(Stream &data){ + size_t written = 0; + size_t toRead = 0; + if(hasError()) + return 0; + + while(remaining()){ + toRead = FLASH_SECTOR_SIZE - _bufferLen; + toRead = data.readBytes(_buffer + _bufferLen, toRead); + if(toRead == 0){ //Timeout + _error = UPDATE_ERROR_STREAM; +#ifdef DEBUG_UPDATER + printError(DEBUG_UPDATER); +#endif + return written; + } + _bufferLen += toRead; + if((_bufferLen == remaining() || _bufferLen == FLASH_SECTOR_SIZE) && !_writeBuffer()) + return written; + written += toRead; + yield(); + } + return written; +} + +void UpdaterClass::printError(Stream &out){ + out.printf("ERROR[%u]: ", _error); + if(_error == UPDATE_ERROR_OK){ + out.println("No Error"); + } else if(_error == UPDATE_ERROR_WRITE){ + out.println("Flash Write Failed"); + } else if(_error == UPDATE_ERROR_ERASE){ + out.println("Flash Erase Failed"); + } else if(_error == UPDATE_ERROR_SPACE){ + out.println("Not Enough Space"); + } else if(_error == UPDATE_ERROR_SIZE){ + out.println("Bad Size Given"); + } else if(_error == UPDATE_ERROR_STREAM){ + out.println("Stream Read Timeout"); + } else { + out.println("UNKNOWN"); + } +} + +UpdaterClass Update; diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h new file mode 100644 index 000000000..107706060 --- /dev/null +++ b/cores/esp8266/Updater.h @@ -0,0 +1,119 @@ +#ifndef ESP8266UPDATER_H +#define ESP8266UPDATER_H + +#include "Arduino.h" +#include "flash_utils.h" + +#define UPDATE_ERROR_OK 0 +#define UPDATE_ERROR_WRITE 1 +#define UPDATE_ERROR_ERASE 2 +#define UPDATE_ERROR_SPACE 3 +#define UPDATE_ERROR_SIZE 4 +#define UPDATE_ERROR_STREAM 5 + +class UpdaterClass { + public: + UpdaterClass(); + /* + Call this to check and erase the space needed for the update + Will return false if there is not enough space + Or the erase of the flash failed + */ + bool begin(size_t size); + + /* + Writes a buffer to the flash and increments the address + Returns the amount written + */ + size_t write(uint8_t *data, size_t len); + + /* + Writes the remaining bytes from the Sream to the flash + Uses readBytes() and sets UPDATE_ERROR_STREAM on timeout + Returns the bytes written + Should be equal to the remaining bytes when called + Usable for slow streams like Serial + */ + size_t writeStream(Stream &data); + + /* + If all bytes are written + this call will write the config to eboot + and return true + If there is already an update running but is not finished + or there is an error + this will clear everything and return false + the last error is available through getError() + */ + bool end(); + + /* + Prints the last error to an output stream + */ + void printError(Stream &out); + + //Helpers + uint8_t getError(){ return _error; } + void clearError(){ _error = UPDATE_ERROR_OK; } + bool hasError(){ return _error != UPDATE_ERROR_OK; } + bool isRunning(){ return _size > 0; } + bool isFinished(){ return hasError()?true:(_currentAddress == (_startAddress + _size)); } + size_t size(){ return _size; } + size_t progress(){ return _currentAddress - _startAddress; } + size_t remaining(){ return hasError()?0:(size() - progress()); } + + /* + Template to write from objects that expose + available() and read(uint8_t*, size_t) methods + faster than the readStream method + writes only what is available + */ + template + size_t write(T &data){ + size_t written = 0; + if(hasError()) + return 0; + size_t available = data.available(); + while(available){ + if((_bufferLen + available) > remaining()){ + available = (remaining() - _bufferLen); + } + if((_bufferLen + available) > FLASH_SECTOR_SIZE){ + size_t toBuff = FLASH_SECTOR_SIZE - _bufferLen; + data.read(_buffer + _bufferLen, toBuff); + _bufferLen += toBuff; + if(!_writeBuffer()) + return written; + written += toBuff; + } else { + data.read(_buffer + _bufferLen, available); + _bufferLen += available; + written += available; + if(_bufferLen == remaining()){ + if(!_writeBuffer()){ + return written; + } + } + } + if(remaining() == 0) + return written; + yield(); + available = data.available(); + } + return written; + } + + private: + uint8_t *_buffer; + uint8_t _error; + size_t _bufferLen; + size_t _size; + uint32_t _startAddress; + uint32_t _currentAddress; + + bool _writeBuffer(); +}; + +extern UpdaterClass Update; + +#endif \ No newline at end of file diff --git a/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino index ca611218d..8ceb2be8a 100644 --- a/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino +++ b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino @@ -7,11 +7,12 @@ const char* ssid = "**********"; const char* pass = "**********"; const uint16_t aport = 8266; -WiFiUDP listener; +WiFiServer TelnetServer(aport); +WiFiClient Telnet; +WiFiUDP OTA; void setup() { Serial.begin(115200); - Serial.setDebugOutput(true); Serial.println(""); Serial.println("Arduino OTA Test"); @@ -22,29 +23,74 @@ void setup() { if(WiFi.waitForConnectResult() == WL_CONNECTED){ MDNS.begin(host); MDNS.addService("arduino", "tcp", aport); - listener.begin(aport); + OTA.begin(aport); + TelnetServer.begin(); + TelnetServer.setNoDelay(true); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } } void loop() { - if (listener.parsePacket()) { - IPAddress remote = listener.remoteIP(); - int cmd = listener.parseInt(); - int port = listener.parseInt(); - int sz = listener.parseInt(); - Serial.printf("Starting Update: cmd:%d, port:%d, size:%d\r\n", cmd, port, sz); - WiFiClient cl; - if (!cl.connect(remote, port)) { - Serial.println("Failed to connect"); + //OTA Sketch + if (OTA.parsePacket()) { + IPAddress remote = OTA.remoteIP(); + int cmd = OTA.parseInt(); + int port = OTA.parseInt(); + int size = OTA.parseInt(); + + Serial.print("Update Start: ip:"); + Serial.print(remote); + Serial.printf(", port:%d, size:%d\n", port, size); + uint32_t startTime = millis(); + + if(!Update.begin(size)){ + Serial.println("Update Begin Error"); return; } - listener.stop(); - if (!ESP.updateSketch(cl, sz)) { - Serial.println("Update failed"); - listener.begin(aport); + + WiFiClient client; + if (client.connect(remote, port)) { + + Serial.setDebugOutput(true); + while(!Update.isFinished()) Update.write(client); + Serial.setDebugOutput(false); + + if(Update.end()){ + client.println("OK"); + Serial.printf("Update Success: %u\nRebooting...\n", millis() - startTime); + ESP.restart(); + } else { + Update.printError(client); + Update.printError(Serial); + } + } else { + Serial.printf("Connect Failed: %u\n", millis() - startTime); } } + //IDE Monitor (connected to Serial) + if (TelnetServer.hasClient()){ + if (!Telnet || !Telnet.connected()){ + if(Telnet) Telnet.stop(); + Telnet = TelnetServer.available(); + } else { + WiFiClient toKill = TelnetServer.available(); + toKill.stop(); + } + } + if (Telnet && Telnet.connected() && Telnet.available()){ + while(Telnet.available()) + Serial.write(Telnet.read()); + } + if(Serial.available()){ + size_t len = Serial.available(); + uint8_t * sbuf = (uint8_t *)malloc(len); + Serial.readBytes(sbuf, len); + if (Telnet && Telnet.connected()){ + Telnet.write((uint8_t *)sbuf, len); + yield(); + } + free(sbuf); + } delay(100); } diff --git a/tools/espota.py b/tools/espota.py index b98d610aa..58305c6df 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -17,48 +17,66 @@ def serve(remoteAddr, remotePort, filename): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverPort = 48266 server_address = ('0.0.0.0', serverPort) - print('starting up on %s port %s' % server_address, file=sys.stderr) - sock.bind(server_address) - sock.listen(1) + print('Starting on %s:%s' % server_address, file=sys.stderr) + try: + sock.bind(server_address) + sock.listen(1) + except: + print('Socket Failed', file=sys.stderr) + return 1 - sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - remote_address = (remoteAddr, int(remotePort)) content_size = os.path.getsize(filename) - print('upload size: %d' % content_size, file=sys.stderr) + print('Upload size: %d' % content_size, file=sys.stderr) message = '%d %d %d\n' % (0, serverPort, content_size) - while True: - # Wait for a connection - print('sending invitation', file=sys.stderr) - sent = sock2.sendto(message, remote_address) + # Wait for a connection + print('Sending invitation to:', remoteAddr, file=sys.stderr) + sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + remote_address = (remoteAddr, int(remotePort)) + sent = sock2.sendto(message, remote_address) + sock2.close() + + print('Waiting for device...\n', file=sys.stderr) + try: sock.settimeout(10) - print('waiting...', file=sys.stderr) connection, client_address = sock.accept() sock.settimeout(None) connection.settimeout(None) + except: + print('No response from device', file=sys.stderr) + sock.close() + return 1 + + try: + f = open(filename, "rb") + sys.stderr.write('Uploading') + sys.stderr.flush() + while True: + chunk = f.read(4096) + if not chunk: break + sys.stderr.write('.') + sys.stderr.flush() + connection.sendall(chunk) + + print('\nWaiting for result...\n', file=sys.stderr) try: - print('connection from', client_address, file=sys.stderr) - - print('sending file %s\n' % filename, file=sys.stderr) - f = open(filename, "rb") - - while True: - chunk = f.read(4096) - if not chunk: - break - - sys.stderr.write('.') - sys.stderr.flush() - #print('sending %d' % len(chunk), file=sys.stderr) - connection.sendall(chunk) - - print('\ndone!', file=sys.stderr) - return 0 - - finally: + connection.settimeout(60) + data = connection.recv(32) + print('Result: %s' % data, file=sys.stderr) connection.close() f.close() - return 1 + return 0 + except: + print('Result: No Answer!', file=sys.stderr) + connection.close() + f.close() + return 1 + + finally: + connection.close() + f.close() + sock.close() + return 1 def main(args): return serve(args[1], args[2], args[3]) From b797287359f4fd76b6125fea29012a3f15fc9e92 Mon Sep 17 00:00:00 2001 From: John Doe Date: Fri, 3 Jul 2015 14:08:10 +0300 Subject: [PATCH 18/53] Catch lost connection while uploading --- tools/espota.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/espota.py b/tools/espota.py index 58305c6df..52ef3e95a 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -56,7 +56,14 @@ def serve(remoteAddr, remotePort, filename): if not chunk: break sys.stderr.write('.') sys.stderr.flush() - connection.sendall(chunk) + try: + connection.sendall(chunk) + except: + print('\nError Uploading', file=sys.stderr) + connection.close() + f.close() + sock.close() + return 1 print('\nWaiting for result...\n', file=sys.stderr) try: @@ -65,11 +72,13 @@ def serve(remoteAddr, remotePort, filename): print('Result: %s' % data, file=sys.stderr) connection.close() f.close() + sock.close() return 0 except: print('Result: No Answer!', file=sys.stderr) connection.close() f.close() + sock.close() return 1 finally: From bfbfd313159dcb4a8809cd7a64bfcbfc7112e02e Mon Sep 17 00:00:00 2001 From: John Doe Date: Fri, 3 Jul 2015 14:13:09 +0300 Subject: [PATCH 19/53] disable updater debug --- cores/esp8266/Updater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 0009f2f4a..740ec3202 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -4,7 +4,7 @@ extern "C"{ #include "mem.h" } -#define DEBUG_UPDATER Serial +//#define DEBUG_UPDATER Serial extern "C" uint32_t _SPIFFS_start; From d969115cda34b1e1cc52719586c9d085dfb6d6cc Mon Sep 17 00:00:00 2001 From: John Doe Date: Fri, 3 Jul 2015 14:19:23 +0300 Subject: [PATCH 20/53] protect the write method writing more than supposed to --- cores/esp8266/Updater.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 740ec3202..728700c2f 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -121,6 +121,9 @@ size_t UpdaterClass::write(uint8_t *data, size_t len){ if(hasError()) return 0; + if(len > remaining()) + len = remaining(); + while((_bufferLen + left) > FLASH_SECTOR_SIZE){ size_t toBuff = FLASH_SECTOR_SIZE - _bufferLen; memcpy(_buffer + _bufferLen, data + (len - left), toBuff); From ace974aede0489b70d9292f84b4dd05f736b56f1 Mon Sep 17 00:00:00 2001 From: John Doe Date: Fri, 3 Jul 2015 14:21:41 +0300 Subject: [PATCH 21/53] let's not wait too much :) telnet running here --- .../examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino index 8ceb2be8a..4320bbbba 100644 --- a/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino +++ b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino @@ -92,5 +92,5 @@ void loop() { } free(sbuf); } - delay(100); + delay(1); } From 342729906574279a4d012946cb64508976ce3077 Mon Sep 17 00:00:00 2001 From: John Doe Date: Fri, 3 Jul 2015 16:14:04 +0300 Subject: [PATCH 22/53] minor enchancement --- cores/esp8266/Updater.cpp | 2 ++ cores/esp8266/Updater.h | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 728700c2f..c957428ff 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -106,6 +106,7 @@ bool UpdaterClass::_writeBuffer(){ interrupts(); if (rc) { _error = UPDATE_ERROR_WRITE; + _currentAddress = (_startAddress + _size); #ifdef DEBUG_UPDATER printError(DEBUG_UPDATER); #endif @@ -157,6 +158,7 @@ size_t UpdaterClass::writeStream(Stream &data){ toRead = data.readBytes(_buffer + _bufferLen, toRead); if(toRead == 0){ //Timeout _error = UPDATE_ERROR_STREAM; + _currentAddress = (_startAddress + _size); #ifdef DEBUG_UPDATER printError(DEBUG_UPDATER); #endif diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index 107706060..ec5b0c4d9 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -57,10 +57,10 @@ class UpdaterClass { void clearError(){ _error = UPDATE_ERROR_OK; } bool hasError(){ return _error != UPDATE_ERROR_OK; } bool isRunning(){ return _size > 0; } - bool isFinished(){ return hasError()?true:(_currentAddress == (_startAddress + _size)); } + bool isFinished(){ return _currentAddress == (_startAddress + _size); } size_t size(){ return _size; } size_t progress(){ return _currentAddress - _startAddress; } - size_t remaining(){ return hasError()?0:(size() - progress()); } + size_t remaining(){ return _size - (_currentAddress - _startAddress); } /* Template to write from objects that expose @@ -116,4 +116,4 @@ class UpdaterClass { extern UpdaterClass Update; -#endif \ No newline at end of file +#endif From 0d969e97600c76b83bb0f8f41fe981ae629c1d57 Mon Sep 17 00:00:00 2001 From: John Doe Date: Fri, 3 Jul 2015 22:03:01 +0300 Subject: [PATCH 23/53] Fixes and HTTP Post update example Because eboot first erases the space required for the new sketch, and if the new sketch is larger then the old one, with the old way, part of the beginning of new sketch will be deleted. Therefore for now I opted to keep the max update size either half the available space for sketches or what's left from the first one, whichever is smaller. To be able to create a simple post mechanism for updates, I needed to have a way to write the new binary, without knowing it's final size, so I added an option to the end() method. Example in the WebServer examples. --- cores/esp8266/Esp.cpp | 2 +- cores/esp8266/Updater.cpp | 35 +++++++--- cores/esp8266/Updater.h | 4 +- .../examples/WebUpdate/WebUpdate.ino | 70 +++++++++++++++++++ 4 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 4e0b75106..3bec3830c 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -350,7 +350,7 @@ uint32_t EspClass::getFreeSketchSpace() { uint32_t usedSize = getSketchSize(); // round one sector up uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); - uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000; + uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000 - (5 * FLASH_SECTOR_SIZE); #ifdef DEBUG_SERIAL DEBUG_SERIAL.printf("usedSize=%u freeSpaceStart=%u freeSpaceEnd=%u\r\n", usedSize, freeSpaceStart, freeSpaceEnd); diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index c957428ff..194a21e9b 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -35,11 +35,14 @@ bool UpdaterClass::begin(size_t size){ _error = 0; uint32_t usedSize = ESP.getSketchSize(); - uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); - uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000; + uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000 - (5 * FLASH_SECTOR_SIZE); uint32_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + uint32_t freeSpaceStart = freeSpaceEnd - roundedSize; - if(roundedSize > (freeSpaceEnd - freeSpaceStart)){ + //new sketch can not be more then half the size or more than the free space + //this means that max sketch size is (1MB - 20KB) / 2 for flash 2MB and above + //and the current sketch should not be more than that either + if(freeSpaceStart < usedSize || roundedSize > (freeSpaceEnd/2)){ _error = UPDATE_ERROR_SPACE; #ifdef DEBUG_UPDATER printError(DEBUG_UPDATER); @@ -64,7 +67,7 @@ bool UpdaterClass::begin(size_t size){ return true; } -bool UpdaterClass::end(){ +bool UpdaterClass::end(bool evenIfRemaining){ if(_size == 0){ #ifdef DEBUG_UPDATER DEBUG_UPDATER.println("no update"); @@ -72,27 +75,39 @@ bool UpdaterClass::end(){ return false; } - if(_buffer) os_free(_buffer); - _bufferLen = 0; - - if(hasError() || !isFinished()){ + if(hasError() || (!isFinished() && !evenIfRemaining)){ #ifdef DEBUG_UPDATER DEBUG_UPDATER.printf("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size); #endif + if(_buffer) os_free(_buffer); + _bufferLen = 0; _currentAddress = 0; _startAddress = 0; _size = 0; return false; } + if(evenIfRemaining){ + if(_bufferLen > 0){ + _writeBuffer(); + } + _size = progress(); + } + if(_buffer) os_free(_buffer); + _bufferLen = 0; + _currentAddress = 0; + eboot_command ebcmd; ebcmd.action = ACTION_COPY_RAW; ebcmd.args[0] = _startAddress; ebcmd.args[1] = 0x00000; ebcmd.args[2] = _size; eboot_command_write(&ebcmd); - - _currentAddress = 0; + +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.printf("Staged: address:0x%08X, size:0x%08X\n", _startAddress, _size); +#endif + _startAddress = 0; _size = 0; _error = UPDATE_ERROR_OK; diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index ec5b0c4d9..df3e8fa38 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -40,12 +40,12 @@ class UpdaterClass { If all bytes are written this call will write the config to eboot and return true - If there is already an update running but is not finished + If there is already an update running but is not finished and !evenIfRemainanig or there is an error this will clear everything and return false the last error is available through getError() */ - bool end(); + bool end(bool evenIfRemaining = false); /* Prints the last error to an output stream diff --git a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino new file mode 100644 index 000000000..d72ab1345 --- /dev/null +++ b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +const char* host = "esp8266-webupdate"; +const char* ssid = "........"; +const char* password = "........"; + +ESP8266WebServer server(80); +const char* serverIndex = "
"; + +void setup(void){ + Serial.begin(115200); + Serial.println(); + Serial.println("Booting Sketch..."); + WiFi.mode(WIFI_AP_STA); + WiFi.begin(ssid, password); + if(WiFi.waitForConnectResult() == WL_CONNECTED){ + MDNS.begin(host); + server.on("/", HTTP_GET, [](){ + server.sendHeader("Connection", "close"); + server.sendHeader("Access-Control-Allow-Origin", "*"); + server.send(200, "text/html", serverIndex); + }); + server.onFileUpload([](){ + if(server.uri() != "/update") return; + HTTPUpload& upload = server.upload(); + if(upload.status == UPLOAD_FILE_START){ + Serial.setDebugOutput(true); + Serial.printf("Update: %s\n", upload.filename.c_str()); + uint32_t maxSketchSpace = ((ESP.getFreeSketchSpace() + ESP.getSketchSize()) / 2) & 0xFFFFF000; + if(!Update.begin(maxSketchSpace)){//start with max available size + Update.printError(Serial); + return; + } + } else if(upload.status == UPLOAD_FILE_WRITE){ + if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){ + Update.printError(Serial); + return; + } + } else if(upload.status == UPLOAD_FILE_END){ + if(Update.end(true)){ //true to set the size to the current progress + Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); + } else { + Update.printError(Serial); + } + Serial.setDebugOutput(false); + } + yield(); + }); + server.on("/update", HTTP_POST, [](){ + server.sendHeader("Connection", "close"); + server.sendHeader("Access-Control-Allow-Origin", "*"); + server.send(200, "text/plain", (Update.hasError())?"FAIL":"OK"); + ESP.restart(); + }); + server.begin(); + MDNS.addService("http", "tcp", 80); + + Serial.println("Ready! Open http:\/\/%s.local in your browser\n", host); + } else { + Serial.println("WiFi Failed"); + } +} + +void loop(void){ + server.handleClient(); + delay(1); +} From 7596ed07429a2aad01d27b046265478c6241117a Mon Sep 17 00:00:00 2001 From: John Doe Date: Fri, 3 Jul 2015 22:31:10 +0300 Subject: [PATCH 24/53] inlining and enchancements --- cores/esp8266/Updater.h | 17 +++++++++-------- .../examples/WebUpdate/WebUpdate.ino | 1 + .../DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino | 4 +++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index df3e8fa38..f8c02859f 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -44,6 +44,7 @@ class UpdaterClass { or there is an error this will clear everything and return false the last error is available through getError() + evenIfRemaining is helpfull when you update without knowing the final size first */ bool end(bool evenIfRemaining = false); @@ -53,14 +54,14 @@ class UpdaterClass { void printError(Stream &out); //Helpers - uint8_t getError(){ return _error; } - void clearError(){ _error = UPDATE_ERROR_OK; } - bool hasError(){ return _error != UPDATE_ERROR_OK; } - bool isRunning(){ return _size > 0; } - bool isFinished(){ return _currentAddress == (_startAddress + _size); } - size_t size(){ return _size; } - size_t progress(){ return _currentAddress - _startAddress; } - size_t remaining(){ return _size - (_currentAddress - _startAddress); } + inline uint8_t getError(){ return _error; } + inline void clearError(){ _error = UPDATE_ERROR_OK; } + inline bool hasError(){ return _error != UPDATE_ERROR_OK; } + inline bool isRunning(){ return _size > 0; } + inline bool isFinished(){ return _currentAddress == (_startAddress + _size); } + inline size_t size(){ return _size; } + inline size_t progress(){ return _currentAddress - _startAddress; } + inline size_t remaining(){ return _size - (_currentAddress - _startAddress); } /* Template to write from objects that expose diff --git a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino index d72ab1345..b411d234e 100644 --- a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino +++ b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino @@ -28,6 +28,7 @@ void setup(void){ HTTPUpload& upload = server.upload(); if(upload.status == UPLOAD_FILE_START){ Serial.setDebugOutput(true); + WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); uint32_t maxSketchSpace = ((ESP.getFreeSketchSpace() + ESP.getSketchSize()) / 2) & 0xFFFFF000; if(!Update.begin(maxSketchSpace)){//start with max available size diff --git a/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino index 4320bbbba..e7dc97795 100644 --- a/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino +++ b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino @@ -43,7 +43,9 @@ void loop() { Serial.print(remote); Serial.printf(", port:%d, size:%d\n", port, size); uint32_t startTime = millis(); - + + WiFiUDP::stopAll(); + if(!Update.begin(size)){ Serial.println("Update Begin Error"); return; From f3f500936d831be890a62a959e21ceff4fd246ef Mon Sep 17 00:00:00 2001 From: John Doe Date: Sat, 4 Jul 2015 00:27:13 +0300 Subject: [PATCH 25/53] make eboot erase/read/write sector by sector that makes possible having sketches with size up to the free size --- bootloaders/eboot/Makefile | 3 +- bootloaders/eboot/eboot.c | 56 ++++++++++++++++-------------------- bootloaders/eboot/eboot.elf | Bin 11233 -> 10064 bytes bootloaders/eboot/flash.c | 4 +-- bootloaders/eboot/flash.h | 2 +- cores/esp8266/Updater.cpp | 25 +++++++++------- tools/espota.py | 11 +++---- 7 files changed, 49 insertions(+), 52 deletions(-) diff --git a/bootloaders/eboot/Makefile b/bootloaders/eboot/Makefile index 0872ee35f..a465a76f6 100644 --- a/bootloaders/eboot/Makefile +++ b/bootloaders/eboot/Makefile @@ -1,4 +1,5 @@ -XTENSA_TOOLCHAIN ?= +XTENSA_TOOLCHAIN ?= ../../tools/xtensa-lx106-elf/bin/ +ESPTOOL ?= ../../tools/esptool BIN_DIR := ./ TARGET_DIR := ./ diff --git a/bootloaders/eboot/eboot.c b/bootloaders/eboot/eboot.c index 0b74fcdf0..ffa1238b3 100644 --- a/bootloaders/eboot/eboot.c +++ b/bootloaders/eboot/eboot.c @@ -76,38 +76,31 @@ int copy_raw(const uint32_t src_addr, const uint32_t dst_addr, const uint32_t size) { - ets_putc('\n'); - ets_putc('c'); - ets_putc('p'); - ets_putc('\n'); // require regions to be aligned if (src_addr & 0xfff != 0 || dst_addr & 0xfff != 0) { return 1; } - if (SPIEraseAreaEx(dst_addr, size)) { - return 2; - } - - const uint32_t buffer_size = 4096; + const uint32_t buffer_size = FLASH_SECTOR_SIZE; uint8_t buffer[buffer_size]; - - const uint32_t end = src_addr + size; + uint32_t left = ((size+buffer_size-1) & ~(buffer_size-1)); uint32_t saddr = src_addr; uint32_t daddr = dst_addr; - uint32_t left = size; - while (saddr < end) { - uint32_t will_copy = (left < buffer_size) ? left : buffer_size; - if (SPIRead(saddr, buffer, will_copy)) { - return 3; - } - if (SPIWrite(daddr, buffer, will_copy)) { - return 4; - } - saddr += will_copy; - daddr += will_copy; - left -= will_copy; + + while (left) { + if (SPIEraseSector(daddr/buffer_size)) { + return 2; + } + if (SPIRead(saddr, buffer, buffer_size)) { + return 3; + } + if (SPIWrite(daddr, buffer, buffer_size)) { + return 4; + } + saddr += buffer_size; + daddr += buffer_size; + left -= buffer_size; } return 0; @@ -123,14 +116,16 @@ void main() if (eboot_command_read(&cmd)) { cmd.action = ACTION_LOAD_APP; cmd.args[0] = 0; - ets_putc('e'); + ets_putc('~'); } else { ets_putc('@'); } eboot_command_clear(); - + if (cmd.action == ACTION_COPY_RAW) { + ets_putc('c'); ets_putc('p'); ets_putc(':'); res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2]); + ets_putc((char)0x30+res); ets_putc('\n'); if (res == 0) { cmd.action = ACTION_LOAD_APP; cmd.args[0] = cmd.args[1]; @@ -138,15 +133,14 @@ void main() } if (cmd.action == ACTION_LOAD_APP) { - res = load_app_from_flash_raw(cmd.args[0]); + ets_putc('l'); ets_putc('d'); ets_putc('\n'); + res = load_app_from_flash_raw(cmd.args[0]); + //we will get to this only on load fail + ets_putc('e'); ets_putc(':'); ets_putc((char)0x30+res); ets_putc('\n'); } if (res) { - ets_putc('\n'); - ets_putc('#'); - ets_putc('0' + res); - ets_putc('\n'); - SWRST; + SWRST; } while(true){} diff --git a/bootloaders/eboot/eboot.elf b/bootloaders/eboot/eboot.elf index 97e25c14616a9f6e768711b46c6cafa6f62df21e..2a46a359c2621a4115be7aa19e2268a8659dbc78 100755 GIT binary patch delta 4192 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 J{k1>f{{z3*d8YsX literal 11233 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 #include #include @@ -46,4 +46,4 @@ int SPIEraseAreaEx(const uint32_t start, const uint32_t size) return 0; } - +*/ diff --git a/bootloaders/eboot/flash.h b/bootloaders/eboot/flash.h index ea8b65c1f..f762296cf 100644 --- a/bootloaders/eboot/flash.h +++ b/bootloaders/eboot/flash.h @@ -12,7 +12,7 @@ int SPIEraseBlock(uint32_t block); int SPIEraseSector(uint32_t sector); int SPIRead(uint32_t addr, void *dest, size_t size); int SPIWrite(uint32_t addr, void *src, size_t size); -int SPIEraseAreaEx(const uint32_t start, const uint32_t size); +//int SPIEraseAreaEx(const uint32_t start, const uint32_t size); #define FLASH_SECTOR_SIZE 0x1000 #define FLASH_BLOCK_SIZE 0x10000 diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 194a21e9b..aba181fe7 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -27,30 +27,33 @@ bool UpdaterClass::begin(size_t size){ } if(_buffer) os_free(_buffer); - _bufferLen = 0; _startAddress = 0; _currentAddress = 0; _size = 0; _error = 0; - uint32_t usedSize = ESP.getSketchSize(); - uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000 - (5 * FLASH_SECTOR_SIZE); + //size of current sketch rounded to a sector + uint32_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + //address of the end of the space available for sketch and update (5 sectors are for EEPROM and init data) + uint32_t updateEndAddress = (uint32_t)&_SPIFFS_start - 0x40200000 - (5 * FLASH_SECTOR_SIZE); + //size of the update rounded to a sector uint32_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); - uint32_t freeSpaceStart = freeSpaceEnd - roundedSize; + //address where we will start writing the update + uint32_t updateStartAddress = updateEndAddress - roundedSize; - //new sketch can not be more then half the size or more than the free space - //this means that max sketch size is (1MB - 20KB) / 2 for flash 2MB and above - //and the current sketch should not be more than that either - if(freeSpaceStart < usedSize || roundedSize > (freeSpaceEnd/2)){ + //make sure that the size of both sketches is less than the total space (updateEndAddress) + if(updateStartAddress < currentSketchSize){ _error = UPDATE_ERROR_SPACE; #ifdef DEBUG_UPDATER printError(DEBUG_UPDATER); #endif return false; } + + //erase the neede space noInterrupts(); - int rc = SPIEraseAreaEx(freeSpaceStart, roundedSize); + int rc = SPIEraseAreaEx(updateStartAddress, roundedSize); interrupts(); if (rc){ _error = UPDATE_ERROR_ERASE; @@ -59,7 +62,9 @@ bool UpdaterClass::begin(size_t size){ #endif return false; } - _startAddress = freeSpaceStart; + + //initialize + _startAddress = updateStartAddress; _currentAddress = _startAddress; _size = size; _buffer = (uint8_t*)os_malloc(FLASH_SECTOR_SIZE); diff --git a/tools/espota.py b/tools/espota.py index 52ef3e95a..dbd22eb45 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -1,11 +1,7 @@ #!/usr/bin/env python # # this script will push an OTA update to the ESP -# -# use it like: python ota_server.py -# -# on the ESP side you need code like this: https://gist.github.com/igrr/43d5c52328e955bb6b09 to handle the update -# +# use it like: python espota.py from __future__ import print_function import socket @@ -22,7 +18,7 @@ def serve(remoteAddr, remotePort, filename): sock.bind(server_address) sock.listen(1) except: - print('Socket Failed', file=sys.stderr) + print('Listen Failed', file=sys.stderr) return 1 content_size = os.path.getsize(filename) @@ -80,10 +76,11 @@ def serve(remoteAddr, remotePort, filename): f.close() sock.close() return 1 - + finally: connection.close() f.close() + sock.close() return 1 From 9d0a69042191843bc4de880e16870f4f3d849a6b Mon Sep 17 00:00:00 2001 From: John Doe Date: Sat, 4 Jul 2015 00:46:58 +0300 Subject: [PATCH 26/53] fix WebUpload example --- libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino index b411d234e..4962b35e8 100644 --- a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino +++ b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino @@ -30,15 +30,13 @@ void setup(void){ Serial.setDebugOutput(true); WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); - uint32_t maxSketchSpace = ((ESP.getFreeSketchSpace() + ESP.getSketchSize()) / 2) & 0xFFFFF000; + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if(!Update.begin(maxSketchSpace)){//start with max available size Update.printError(Serial); - return; } } else if(upload.status == UPLOAD_FILE_WRITE){ if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){ Update.printError(Serial); - return; } } else if(upload.status == UPLOAD_FILE_END){ if(Update.end(true)){ //true to set the size to the current progress @@ -59,7 +57,7 @@ void setup(void){ server.begin(); MDNS.addService("http", "tcp", 80); - Serial.println("Ready! Open http:\/\/%s.local in your browser\n", host); + Serial.printf("Ready! Open http://%s.local in your browser\n", host); } else { Serial.println("WiFi Failed"); } From 703ab8df641c0028245d400cf9d1b2d609571f13 Mon Sep 17 00:00:00 2001 From: John Doe Date: Sat, 4 Jul 2015 01:20:33 +0300 Subject: [PATCH 27/53] make Update erase/write sector by sector as well --- cores/esp8266/Updater.cpp | 26 +++++++++---------- .../examples/WebUpdate/WebUpdate.ino | 4 +++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index aba181fe7..e9ecd5d5d 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -51,18 +51,6 @@ bool UpdaterClass::begin(size_t size){ return false; } - //erase the neede space - noInterrupts(); - int rc = SPIEraseAreaEx(updateStartAddress, roundedSize); - interrupts(); - if (rc){ - _error = UPDATE_ERROR_ERASE; -#ifdef DEBUG_UPDATER - printError(DEBUG_UPDATER); -#endif - return false; - } - //initialize _startAddress = updateStartAddress; _currentAddress = _startAddress; @@ -122,7 +110,19 @@ bool UpdaterClass::end(bool evenIfRemaining){ bool UpdaterClass::_writeBuffer(){ WDT_FEED(); noInterrupts(); - int rc = SPIWrite(_currentAddress, _buffer, _bufferLen); + int rc = SPIEraseSector(_currentAddress/FLASH_SECTOR_SIZE); + interrupts(); + if (rc){ + _error = UPDATE_ERROR_ERASE; +#ifdef DEBUG_UPDATER + printError(DEBUG_UPDATER); +#endif + return false; + } + + WDT_FEED(); + noInterrupts(); + rc = SPIWrite(_currentAddress, _buffer, _bufferLen); interrupts(); if (rc) { _error = UPDATE_ERROR_WRITE; diff --git a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino index 4962b35e8..665dd6199 100644 --- a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino +++ b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino @@ -1,3 +1,7 @@ +/* + To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update +*/ + #include #include #include From 1741bc68b6e2f1df7cc9e97a6048d05fe51eb022 Mon Sep 17 00:00:00 2001 From: John Doe Date: Sat, 4 Jul 2015 01:46:51 +0300 Subject: [PATCH 28/53] speed :) and prevent write if we are not running --- cores/esp8266/Updater.cpp | 18 +++--------------- cores/esp8266/Updater.h | 5 ++--- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index e9ecd5d5d..1504b2e97 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -108,23 +108,11 @@ bool UpdaterClass::end(bool evenIfRemaining){ } bool UpdaterClass::_writeBuffer(){ - WDT_FEED(); noInterrupts(); int rc = SPIEraseSector(_currentAddress/FLASH_SECTOR_SIZE); + if(!rc) rc = SPIWrite(_currentAddress, _buffer, _bufferLen); interrupts(); if (rc){ - _error = UPDATE_ERROR_ERASE; -#ifdef DEBUG_UPDATER - printError(DEBUG_UPDATER); -#endif - return false; - } - - WDT_FEED(); - noInterrupts(); - rc = SPIWrite(_currentAddress, _buffer, _bufferLen); - interrupts(); - if (rc) { _error = UPDATE_ERROR_WRITE; _currentAddress = (_startAddress + _size); #ifdef DEBUG_UPDATER @@ -139,7 +127,7 @@ bool UpdaterClass::_writeBuffer(){ size_t UpdaterClass::write(uint8_t *data, size_t len){ size_t left = len; - if(hasError()) + if(hasError()||!isRunning()) return 0; if(len > remaining()) @@ -170,7 +158,7 @@ size_t UpdaterClass::write(uint8_t *data, size_t len){ size_t UpdaterClass::writeStream(Stream &data){ size_t written = 0; size_t toRead = 0; - if(hasError()) + if(hasError()||!isRunning()) return 0; while(remaining()){ diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index f8c02859f..e495ef0e7 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -15,9 +15,8 @@ class UpdaterClass { public: UpdaterClass(); /* - Call this to check and erase the space needed for the update + Call this to check the space needed for the update Will return false if there is not enough space - Or the erase of the flash failed */ bool begin(size_t size); @@ -72,7 +71,7 @@ class UpdaterClass { template size_t write(T &data){ size_t written = 0; - if(hasError()) + if(hasError()||!isRunning()) return 0; size_t available = data.available(); while(available){ From 5763dbba3b1ce1c6ff927054f03c664a73b42cce Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 6 Jul 2015 21:04:08 +0300 Subject: [PATCH 29/53] Code review --- bootloaders/eboot/Makefile | 2 +- bootloaders/eboot/eboot.c | 4 +-- bootloaders/eboot/eboot.elf | Bin 10064 -> 11229 bytes bootloaders/eboot/flash.c | 4 +-- bootloaders/eboot/flash.h | 2 +- cores/esp8266/Esp.cpp | 2 +- cores/esp8266/Updater.cpp | 70 +++++++++++++++++++----------------- cores/esp8266/Updater.h | 42 +++++++++++----------- 8 files changed, 67 insertions(+), 59 deletions(-) diff --git a/bootloaders/eboot/Makefile b/bootloaders/eboot/Makefile index a465a76f6..7a07d7615 100644 --- a/bootloaders/eboot/Makefile +++ b/bootloaders/eboot/Makefile @@ -7,7 +7,7 @@ TARGET_DIR := ./ TARGET_OBJ_FILES := \ eboot.o \ eboot_command.o \ - flash.o \ + TARGET_OBJ_PATHS := $(addprefix $(TARGET_DIR)/,$(TARGET_OBJ_FILES)) diff --git a/bootloaders/eboot/eboot.c b/bootloaders/eboot/eboot.c index ffa1238b3..cbc87044b 100644 --- a/bootloaders/eboot/eboot.c +++ b/bootloaders/eboot/eboot.c @@ -125,7 +125,7 @@ void main() if (cmd.action == ACTION_COPY_RAW) { ets_putc('c'); ets_putc('p'); ets_putc(':'); res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2]); - ets_putc((char)0x30+res); ets_putc('\n'); + ets_putc('0'+res); ets_putc('\n'); if (res == 0) { cmd.action = ACTION_LOAD_APP; cmd.args[0] = cmd.args[1]; @@ -136,7 +136,7 @@ void main() ets_putc('l'); ets_putc('d'); ets_putc('\n'); res = load_app_from_flash_raw(cmd.args[0]); //we will get to this only on load fail - ets_putc('e'); ets_putc(':'); ets_putc((char)0x30+res); ets_putc('\n'); + ets_putc('e'); ets_putc(':'); ets_putc('0'+res); ets_putc('\n'); } if (res) { diff --git a/bootloaders/eboot/eboot.elf b/bootloaders/eboot/eboot.elf index 2a46a359c2621a4115be7aa19e2268a8659dbc78..08536aa72a4c83c9788ce38dd40d440db9669dc0 100755 GIT binary patch delta 4449 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 Cv52?; delta 3463 zcmb_ee{fV+6~6cV+68tuyPKbELbCgIlij3{O&}zzrY6vYE|3;%QyPH^AqmrkBulfY zMPyiYrnM?+*E=|l#+eMA>R8jU%+{F>6BTG>l(92GDTY#~Rv8tcEeWWo)%82?hsm`6 zvNQYccfRkObI*P6?AiCbhiC48rebMg-rL#27&}~*)4xzQt1w1i6%4n&Q>r*0EX&O# zMUYqWcSJj{U-!qtDaMYjcIjusELQXty}l@xuJ3S3`W9!bqxoVXx<+yNP2)l#nsyh|l1fN7f@xA_g^vMA}LWDkffbS>Jew?RhjddipBzJXREuiQk{Y(=d zAl7x9_Zw=_ul5|CuaC#J)OR3})TK`j^Bttmb6=X@dxm(TwE*uD&uLNKBwo|vNcbPEjYq^-CL{U^u8uU7rubZNbzb6~*v@$nQ~{qNw&Xs*&ne3{LZ$MU5-_u!1ff*EhXjhY zj`%8aacY;vz9rSG0$^NELgfBhTPM2C`ceI7a&kD3{JJU=6?-fiSd0{bXh=F)euxXQ_OGgZ+v z=k0WmHSsJ}LACSEW{$1f-KR!w!OY@v0 z^2RHGpT*1`BK(%PMlt3O?=86w744qbbf`liiyfVg09);ut zuKcic5KTPY;6Xs< zG<)a>pyD)p$Z&C97D-oiJN3{qj?;3P-02|S!pbK;iTtjCu>ng!54qolFPBM>a)a&~ zSsZtDdg<}|ULj>UC72fTuGj-EN?1d`9^>b4B-G&J8Wq1H@-F=*C2606OT?(_8{R6>=c~#W7d1%YRi790>~&vFx&27tV|@Nhx!E;wm%nn$ z-qgU*cskxSl-WO?J`nGIaBE_9YioSn*x=)-(e$d3)aX!r&*Q1#!T3o3Kss|U{zQ6g zzb!ZRA2<-dCp|hoG&-J%Z|eVazu4#hBJax2`{%jyZ^kyyak0`Lxa0dYl=j~7ocsJO z;$rDO9?7pO+rYQQAIc1kW#V`K4y_O^%ge;iD_Zj#mj9NEV5~$WD%-V8>d?@xagnL~ zsJLC3$}&tcdYeFx8uO4Iof0!~6c~4x?S~KPGy;Z!-B@*eX$oR7c*0e{#2;J46Snyi zz@?VYSzr&t{|W_MfKg!y*MR|zq=bttz5r|&!7wtz79R!%H2euuaa1&oRV>Vj!w_Bx zO#Dt@6)WxAKbKty?uW3;5>5e=GoAPhaJj`_0tPgA!gg`j7x9E`z5x9HqA?C>xY5RK zz*v|wi|Xiwt_cQhNRt+eu>s+ifr$@d%wR+q!C_!P!>0vz#S2WE&<_xc@%RW6Pq8hF z{eersQ4vYttXyQb8WJD+@T&|8c&q;kj-0oHC9SW^9hSlggvXsf996BEBH%s zGgvCkDq?9!SEsP|qoO$!)=C`cJymQ6`4<=3L3}8%Z>#dYa564L0 zKfPVTVif%*R-F1@kt3A*P;Db#EPqfNm%zPI`*B(Vb&c7DM0157;zljD&%n0-*l7RP zVAsqQ3ib|HVbg;i1ws1zu&Il$SvLJWGIpbbu2fM8Z2M)4{8!5O;n#$8-FDanhs6c# z_&|c;59o&sJ<@5%ABDX*-xTb)zL5VM`CEg #include #include @@ -46,4 +46,4 @@ int SPIEraseAreaEx(const uint32_t start, const uint32_t size) return 0; } -*/ + diff --git a/bootloaders/eboot/flash.h b/bootloaders/eboot/flash.h index f762296cf..ea8b65c1f 100644 --- a/bootloaders/eboot/flash.h +++ b/bootloaders/eboot/flash.h @@ -12,7 +12,7 @@ int SPIEraseBlock(uint32_t block); int SPIEraseSector(uint32_t sector); int SPIRead(uint32_t addr, void *dest, size_t size); int SPIWrite(uint32_t addr, void *src, size_t size); -//int SPIEraseAreaEx(const uint32_t start, const uint32_t size); +int SPIEraseAreaEx(const uint32_t start, const uint32_t size); #define FLASH_SECTOR_SIZE 0x1000 #define FLASH_BLOCK_SIZE 0x10000 diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 3bec3830c..4e0b75106 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -350,7 +350,7 @@ uint32_t EspClass::getFreeSketchSpace() { uint32_t usedSize = getSketchSize(); // round one sector up uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); - uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000 - (5 * FLASH_SECTOR_SIZE); + uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000; #ifdef DEBUG_SERIAL DEBUG_SERIAL.printf("usedSize=%u freeSpaceStart=%u freeSpaceEnd=%u\r\n", usedSize, freeSpaceStart, freeSpaceEnd); diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 1504b2e97..4d9dca9e2 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -1,14 +1,30 @@ #include "Updater.h" #include "Arduino.h" #include "eboot_command.h" -extern "C"{ - #include "mem.h" -} + //#define DEBUG_UPDATER Serial extern "C" uint32_t _SPIFFS_start; -UpdaterClass::UpdaterClass() : _error(0), _buffer(0), _bufferLen(0), _size(0), _startAddress(0), _currentAddress(0) {} +UpdaterClass::UpdaterClass() +: _error(0) +, _buffer(0) +, _bufferLen(0) +, _size(0) +, _startAddress(0) +, _currentAddress(0) +{ +} + +void UpdaterClass::_reset() { + if (_buffer) + delete[] _buffer; + _buffer = 0; + _bufferLen = 0; + _startAddress = 0; + _currentAddress = 0; + _size = 0; +} bool UpdaterClass::begin(size_t size){ if(_size > 0){ @@ -26,17 +42,13 @@ bool UpdaterClass::begin(size_t size){ return false; } - if(_buffer) os_free(_buffer); - _bufferLen = 0; - _startAddress = 0; - _currentAddress = 0; - _size = 0; + _reset(); _error = 0; //size of current sketch rounded to a sector uint32_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); - //address of the end of the space available for sketch and update (5 sectors are for EEPROM and init data) - uint32_t updateEndAddress = (uint32_t)&_SPIFFS_start - 0x40200000 - (5 * FLASH_SECTOR_SIZE); + //address of the end of the space available for sketch and update + uint32_t updateEndAddress = (uint32_t)&_SPIFFS_start - 0x40200000; //size of the update rounded to a sector uint32_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); //address where we will start writing the update @@ -55,7 +67,7 @@ bool UpdaterClass::begin(size_t size){ _startAddress = updateStartAddress; _currentAddress = _startAddress; _size = size; - _buffer = (uint8_t*)os_malloc(FLASH_SECTOR_SIZE); + _buffer = new uint8_t[FLASH_SECTOR_SIZE]; return true; } @@ -72,11 +84,8 @@ bool UpdaterClass::end(bool evenIfRemaining){ #ifdef DEBUG_UPDATER DEBUG_UPDATER.printf("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size); #endif - if(_buffer) os_free(_buffer); - _bufferLen = 0; - _currentAddress = 0; - _startAddress = 0; - _size = 0; + + _reset(); return false; } @@ -86,9 +95,6 @@ bool UpdaterClass::end(bool evenIfRemaining){ } _size = progress(); } - if(_buffer) os_free(_buffer); - _bufferLen = 0; - _currentAddress = 0; eboot_command ebcmd; ebcmd.action = ACTION_COPY_RAW; @@ -100,19 +106,19 @@ bool UpdaterClass::end(bool evenIfRemaining){ #ifdef DEBUG_UPDATER DEBUG_UPDATER.printf("Staged: address:0x%08X, size:0x%08X\n", _startAddress, _size); #endif - - _startAddress = 0; - _size = 0; - _error = UPDATE_ERROR_OK; + + _reset(); return true; } bool UpdaterClass::_writeBuffer(){ noInterrupts(); int rc = SPIEraseSector(_currentAddress/FLASH_SECTOR_SIZE); - if(!rc) rc = SPIWrite(_currentAddress, _buffer, _bufferLen); + if (!rc) { + rc = SPIWrite(_currentAddress, _buffer, _bufferLen); + } interrupts(); - if (rc){ + if (rc) { _error = UPDATE_ERROR_WRITE; _currentAddress = (_startAddress + _size); #ifdef DEBUG_UPDATER @@ -125,15 +131,15 @@ bool UpdaterClass::_writeBuffer(){ return true; } -size_t UpdaterClass::write(uint8_t *data, size_t len){ +size_t UpdaterClass::write(uint8_t *data, size_t len) { size_t left = len; - if(hasError()||!isRunning()) + if(hasError() || !isRunning()) return 0; if(len > remaining()) len = remaining(); - while((_bufferLen + left) > FLASH_SECTOR_SIZE){ + while((_bufferLen + left) > FLASH_SECTOR_SIZE) { size_t toBuff = FLASH_SECTOR_SIZE - _bufferLen; memcpy(_buffer + _bufferLen, data + (len - left), toBuff); _bufferLen += toBuff; @@ -155,13 +161,13 @@ size_t UpdaterClass::write(uint8_t *data, size_t len){ return len; } -size_t UpdaterClass::writeStream(Stream &data){ +size_t UpdaterClass::writeStream(Stream &data) { size_t written = 0; size_t toRead = 0; - if(hasError()||!isRunning()) + if(hasError() || !isRunning()) return 0; - while(remaining()){ + while(remaining()) { toRead = FLASH_SECTOR_SIZE - _bufferLen; toRead = data.readBytes(_buffer + _bufferLen, toRead); if(toRead == 0){ //Timeout diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index e495ef0e7..be1a04dd6 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -27,7 +27,7 @@ class UpdaterClass { size_t write(uint8_t *data, size_t len); /* - Writes the remaining bytes from the Sream to the flash + Writes the remaining bytes from the Stream to the flash Uses readBytes() and sets UPDATE_ERROR_STREAM on timeout Returns the bytes written Should be equal to the remaining bytes when called @@ -53,32 +53,33 @@ class UpdaterClass { void printError(Stream &out); //Helpers - inline uint8_t getError(){ return _error; } - inline void clearError(){ _error = UPDATE_ERROR_OK; } - inline bool hasError(){ return _error != UPDATE_ERROR_OK; } - inline bool isRunning(){ return _size > 0; } - inline bool isFinished(){ return _currentAddress == (_startAddress + _size); } - inline size_t size(){ return _size; } - inline size_t progress(){ return _currentAddress - _startAddress; } - inline size_t remaining(){ return _size - (_currentAddress - _startAddress); } + uint8_t getError(){ return _error; } + void clearError(){ _error = UPDATE_ERROR_OK; } + bool hasError(){ return _error != UPDATE_ERROR_OK; } + bool isRunning(){ return _size > 0; } + bool isFinished(){ return _currentAddress == (_startAddress + _size); } + size_t size(){ return _size; } + size_t progress(){ return _currentAddress - _startAddress; } + size_t remaining(){ return _size - (_currentAddress - _startAddress); } /* Template to write from objects that expose available() and read(uint8_t*, size_t) methods - faster than the readStream method + faster than the writeStream method writes only what is available */ template size_t write(T &data){ size_t written = 0; - if(hasError()||!isRunning()) + if (hasError() || !isRunning()) return 0; + size_t available = data.available(); - while(available){ - if((_bufferLen + available) > remaining()){ - available = (remaining() - _bufferLen); + while(available) { + if(_bufferLen + available > remaining()){ + available = remaining() - _bufferLen; } - if((_bufferLen + available) > FLASH_SECTOR_SIZE){ + if(_bufferLen + available > FLASH_SECTOR_SIZE) { size_t toBuff = FLASH_SECTOR_SIZE - _bufferLen; data.read(_buffer + _bufferLen, toBuff); _bufferLen += toBuff; @@ -89,8 +90,8 @@ class UpdaterClass { data.read(_buffer + _bufferLen, available); _bufferLen += available; written += available; - if(_bufferLen == remaining()){ - if(!_writeBuffer()){ + if(_bufferLen == remaining()) { + if(!_writeBuffer()) { return written; } } @@ -104,14 +105,15 @@ class UpdaterClass { } private: + void _reset(); + bool _writeBuffer(); + uint8_t *_buffer; - uint8_t _error; size_t _bufferLen; size_t _size; uint32_t _startAddress; uint32_t _currentAddress; - - bool _writeBuffer(); + uint8_t _error; }; extern UpdaterClass Update; From 4fdba1b635871611fcb13b6baf4908b80b7cb70c Mon Sep 17 00:00:00 2001 From: John Doe Date: Sat, 4 Jul 2015 22:55:48 +0300 Subject: [PATCH 30/53] Add SSDP Library and let Print::printf to handle longer strings --- cores/esp8266/Print.cpp | 4 +- libraries/ESP8266SSDP/ESP8266SSDP.cpp | 320 ++++++++++++++++++++++++ libraries/ESP8266SSDP/ESP8266SSDP.h | 100 ++++++++ libraries/ESP8266SSDP/README.md | 22 ++ libraries/ESP8266SSDP/examples/SSDP.ino | 57 +++++ libraries/ESP8266SSDP/keywords.txt | 53 ++++ 6 files changed, 554 insertions(+), 2 deletions(-) create mode 100644 libraries/ESP8266SSDP/ESP8266SSDP.cpp create mode 100644 libraries/ESP8266SSDP/ESP8266SSDP.h create mode 100644 libraries/ESP8266SSDP/README.md create mode 100644 libraries/ESP8266SSDP/examples/SSDP.ino create mode 100644 libraries/ESP8266SSDP/keywords.txt diff --git a/cores/esp8266/Print.cpp b/cores/esp8266/Print.cpp index efd6b9f17..06c51e0c1 100644 --- a/cores/esp8266/Print.cpp +++ b/cores/esp8266/Print.cpp @@ -47,8 +47,8 @@ size_t ICACHE_FLASH_ATTR Print::write(const uint8_t *buffer, size_t size) { size_t Print::printf(const char *format, ...) { va_list arg; va_start(arg, format); - char temp[256]; - size_t len = ets_vsnprintf(temp, 256, format, arg); + char temp[1460]; + size_t len = ets_vsnprintf(temp, 1460, format, arg); len = print(temp); va_end(arg); return len; diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp new file mode 100644 index 000000000..1077f66fb --- /dev/null +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -0,0 +1,320 @@ +/* +ESP8266 Simple Service Discovery +Copyright (c) 2015 Hristo Gochkov + +Original (Arduino) version by Filippo Sallemi, July 23, 2014. +Can be found at: https://github.com/nomadnt/uSSDP + +License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#include "ESP8266SSDP.h" + +extern "C" { + #include "ip_addr.h" + #include "user_interface.h" + #include "mem.h" +} +#include "lwip/igmp.h" + +#define SSDP_INTERVAL 1200 +#define SSDP_PORT 1900 +#define SSDP_METHOD_SIZE 10 +#define SSDP_URI_SIZE 2 +#define SSDP_BUFFER_SIZE 64 + +static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); + +const char* _ssdp_response_template = + "HTTP/1.1 200 OK\r\n" + "EXT:\r\n" + "ST: upnp:rootdevice\r\n"; + +const char* _ssdp_notify_template = + "NOTIFY * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "NT: upnp:rootdevice\r\n" + "NTS: ssdp:alive\r\n"; + +const char* _ssdp_packet_template = + "%s" // _ssdp_response_template / _ssdp_notify_template + "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL + "SERVER: Arduino/1.0 UPNP/1.1 %s/%u.%u\r\n" // _modelName, _modelNumber->major, _modelNumber->minor + "USN: uuid:%s-%02X%02X%02X%02X%02X%02X\r\n" // _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5] + "LOCATION: http://%u.%u.%u.%u/ssdp/schema.xml\r\n" // WiFi.localIP() + "\r\n"; + +const char* _ssdp_schema_template = + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/xml\r\n" + "Connection: close\r\n" + "Access-Control-Allow-Origin: *\r\n" + "\r\n" + "" + "" + "" + "1" + "0" + "" + "" + "urn:schemas-upnp-org:device:Basic:1" + "%s" + "%s" + "%s" + "%s" + "%u.%u" + "%s" + "%s" + "%s" + "%s-%02X%02X%02X%02X%02X%02X" //uuid:UUID + "" + "\r\n" + "\r\n"; + +SSDPClass::SSDPClass(){ + _base = (char*)os_malloc(SSDP_BASE_SIZE); + _mac = (byte*)os_malloc(6); + _friendlyName = (char*)os_malloc(SSDP_FRIENDLY_NAME_SIZE); + _presentationURL = (char*)os_malloc(SSDP_PRESENTATION_URL_SIZE); + _serialNumber = (char*)os_malloc(SSDP_SERIAL_NUMBER_SIZE); + _modelName = (char*)os_malloc(SSDP_MODEL_NAME_SIZE); + _modelNumber = (ssdp_version_t*)os_malloc(SSDP_MODEL_VERSION_SIZE); + _modelURL = (char*)os_malloc(SSDP_MODEL_URL_SIZE); + _manufacturer = (char*)os_malloc(SSDP_MANUFACTURER_SIZE); + _manufacturerURL = (char*)os_malloc(SSDP_MANUFACTURER_URL_SIZE); + + _modelNumber->major = 0; + _modelNumber->minor = 0; + _friendlyName[0] = '\0'; + _presentationURL[0] = '\0'; + _serialNumber[0] = '\0'; + _modelName[0] = '\0'; + _modelURL[0] = '\0'; + _manufacturer[0] = '\0'; + _manufacturerURL[0] = '\0'; + _pending = false; +} + +SSDPClass::~SSDPClass(){ + os_free(_base); + os_free(_mac); + os_free(_friendlyName); + os_free(_presentationURL); + os_free(_serialNumber); + os_free(_modelName); + os_free(_modelNumber); + os_free(_modelURL); + os_free(_manufacturer); + os_free(_manufacturerURL); + _pending = false; +} + +void SSDPClass::begin(char *base){ + ip_addr_t ifaddr; + ip_addr_t multicast_addr; + + _pending = false; + strcpy(_base, base); + + WiFi.macAddress(_mac); + ifaddr.addr = WiFi.localIP(); + multicast_addr.addr = (uint32_t) SSDP_MULTICAST_ADDR; + igmp_joingroup(&ifaddr, &multicast_addr); + + _server.begin(SSDP_PORT); +} + +void SSDPClass::send(ssdp_method_t method){ +#ifdef DEBUG_SSDP + if(method == NONE){ + DEBUG_SSDP.print("Sending Response to "); + DEBUG_SSDP.print(_server.remoteIP()); + DEBUG_SSDP.print(":"); + DEBUG_SSDP.println(_server.remotePort()); + }else if(method == NOTIFY){ + DEBUG_SSDP.println("Sending Notify to 239.255.255.250:1900"); + } +#endif + + if(method == NONE){ + _server.beginPacket(_server.remoteIP(), _server.remotePort()); + } else { + _server.beginPacket(SSDP_MULTICAST_ADDR, SSDP_PORT); + } + + uint32_t ip = WiFi.localIP(); + + _server.printf(_ssdp_packet_template, + (method == NONE)?_ssdp_response_template:_ssdp_notify_template, + SSDP_INTERVAL, + _modelName, _modelNumber->major, _modelNumber->minor, + _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5], + (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), (uint8_t)((ip >> 24) & 0xFF) + ); + + _server.endPacket(); +} + +void SSDPClass::schema(WiFiClient client){ + client.printf(_ssdp_schema_template, + _friendlyName, + _presentationURL, + _serialNumber, + _modelName, + _modelNumber->major, _modelNumber->minor, + _modelURL, + _manufacturer, + _manufacturerURL, + _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5] + ); +} + +uint8_t SSDPClass::update(){ + if(!_pending && _server.parsePacket() > 0){ + ssdp_method_t method = NONE; + + typedef enum {METHOD, URI, PROTO, KEY, VALUE, ABORT} states; + states state = METHOD; + + typedef enum {START, MAN, ST, MX} headers; + headers header = START; + + uint8_t cursor = 0; + uint8_t cr = 0; + + char buffer[SSDP_BUFFER_SIZE] = {0}; + + while(_server.available() > 0){ + char c = _server.read(); + + (c == '\r' || c == '\n') ? cr++ : cr = 0; + + switch(state){ + case METHOD: + if(c == ' '){ + if(strcmp(buffer, "M-SEARCH") == 0) method = SEARCH; + else if(strcmp(buffer, "NOTIFY") == 0) method = NOTIFY; + + if(method == NONE) state = ABORT; + else state = URI; + cursor = 0; + + }else if(cursor < SSDP_METHOD_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + break; + case URI: + if(c == ' '){ + if(strcmp(buffer, "*")) state = ABORT; + else state = PROTO; + cursor = 0; + }else if(cursor < SSDP_URI_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + break; + case PROTO: + if(cr == 2){ state = KEY; cursor = 0; } + break; + case KEY: + if(cr == 4){ _pending = true; _process_time = millis(); } + else if(c == ' '){ cursor = 0; state = VALUE; } + else if(c != '\r' && c != '\n' && c != ':' && cursor < SSDP_BUFFER_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + break; + case VALUE: + if(cr == 2){ + switch(header){ + case MAN: +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf("MAN: %s\n", (char *)buffer); +#endif + break; + case ST: + if(strcmp(buffer, "ssdp:all")){ + state = ABORT; +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf("REJECT: %s\n", (char *)buffer); +#endif + } + break; + case MX: + _delay = random(0, atoi(buffer)) * 1000L; + break; + } + + if(state != ABORT){ state = KEY; header = START; cursor = 0; } + }else if(c != '\r' && c != '\n'){ + if(header == START){ + if(strncmp(buffer, "MA", 2) == 0) header = MAN; + else if(strcmp(buffer, "ST") == 0) header = ST; + else if(strcmp(buffer, "MX") == 0) header = MX; + } + + if(cursor < SSDP_BUFFER_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + } + break; + case ABORT: + _pending = false; _delay = 0; + break; + } + } + + _server.flush(); + } + + if(_pending && (millis() - _process_time) > _delay){ + _pending = false; _delay = 0; + send(NONE); + }else if(_notify_time == 0 || (millis() - _notify_time) > (SSDP_INTERVAL * 1000L)){ + _notify_time = millis(); + send(NOTIFY); + } +} + +void SSDPClass::setName(char *name){ + strcpy(_friendlyName, name); +} + +void SSDPClass::setURL(char *url){ + strcpy(_presentationURL, url); +} + +void SSDPClass::setSerialNumber(char *serialNumber){ + strcpy(_serialNumber, serialNumber); +} + +void SSDPClass::setModelName(char *name){ + strcpy(_modelName, name); +} + +void SSDPClass::setModelNumber(uint8_t major, uint8_t minor){ + _modelNumber->major = major; + _modelNumber->minor = minor; +} + +void SSDPClass::setModelURL(char *url){ + strcpy(_modelURL, url); +} + +void SSDPClass::setManufacturer(char *name){ + strcpy(_manufacturer, name); +} + +void SSDPClass::setManufacturerURL(char *url){ + strcpy(_manufacturerURL, url); +} + +SSDPClass SSDP; diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.h b/libraries/ESP8266SSDP/ESP8266SSDP.h new file mode 100644 index 000000000..765809179 --- /dev/null +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -0,0 +1,100 @@ +/* +ESP8266 Simple Service Discovery +Copyright (c) 2015 Hristo Gochkov + +Original (Arduino) version by Filippo Sallemi, July 23, 2014. +Can be found at: https://github.com/nomadnt/uSSDP + +License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifndef ESP8266SSDP_H +#define ESP8266SSDP_H + +#include +#include +#include + +#define DEBUG_SSDP Serial + +#define SSDP_BASE_SIZE 24 +#define SSDP_FRIENDLY_NAME_SIZE 32 +#define SSDP_SERIAL_NUMBER_SIZE 32 +#define SSDP_PRESENTATION_URL_SIZE 32 +#define SSDP_MODEL_NAME_SIZE 32 +#define SSDP_MODEL_URL_SIZE 32 +#define SSDP_MODEL_VERSION_SIZE 2 +#define SSDP_MANUFACTURER_SIZE 32 +#define SSDP_MANUFACTURER_URL_SIZE 32 + +typedef enum { + NONE, + SEARCH, + NOTIFY +} ssdp_method_t; + +typedef struct { + uint8_t major; + uint8_t minor; +} ssdp_version_t; + + +class SSDPClass{ + public: + SSDPClass(); + ~SSDPClass(); + + void begin(char *base); + uint8_t update(); + void send(ssdp_method_t method); + void schema(WiFiClient client); + + void setName(char *name); + void setURL(char *url); + void setSerialNumber(char *serialNumber); + void setModelName(char *name); + void setModelNumber(uint8_t major, uint8_t minor); + void setModelURL(char *url); + void setManufacturer(char *name); + void setManufacturerURL(char *url); + + private: + WiFiUDP _server; + bool _pending; + unsigned short _delay; + unsigned long _process_time; + unsigned long _notify_time; + + uint8_t *_mac; + char *_base; + char *_friendlyName; + char *_serialNumber; + char *_presentationURL; + char *_manufacturer; + char *_manufacturerURL; + char *_modelName; + char *_modelURL; + ssdp_version_t *_modelNumber; +}; + +extern SSDPClass SSDP; + +#endif diff --git a/libraries/ESP8266SSDP/README.md b/libraries/ESP8266SSDP/README.md new file mode 100644 index 000000000..31d353052 --- /dev/null +++ b/libraries/ESP8266SSDP/README.md @@ -0,0 +1,22 @@ +ESP8266 Simple Service Discovery +Copyright (c) 2015 Hristo Gochkov +Original (Arduino) version by Filippo Sallemi, July 23, 2014. +Can be found at: https://github.com/nomadnt/uSSDP + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/libraries/ESP8266SSDP/examples/SSDP.ino b/libraries/ESP8266SSDP/examples/SSDP.ino new file mode 100644 index 000000000..fa7753622 --- /dev/null +++ b/libraries/ESP8266SSDP/examples/SSDP.ino @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +const char* ssid = "************"; +const char* password = "***********"; + +ESP8266WebServer HTTP(80); + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println("Booting Sketch..."); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if(WiFi.waitForConnectResult() == WL_CONNECTED){ + HTTP.on("/", HTTP_GET, [](){ + HTTP.sendHeader("Connection", "close"); + HTTP.sendHeader("Access-Control-Allow-Origin", "*"); + HTTP.send(200, "text/plain", "Hello World!"); + }); + HTTP.on("/ssdp/schema.xml", HTTP_GET, [](){ + SSDP.schema(HTTP.client()); + }); + HTTP.begin(); + + byte mac[6]; + char base[24]; + WiFi.macAddress(mac); + sprintf(base, "esp8266x-%02x%02x-%02x%02x-%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + Serial.printf("Starting SSDP: %s\n", base); + + SSDP.begin((char*)base); + SSDP.setName((char*)"ESP8266"); + SSDP.setSerialNumber((char*)"A0123456789"); + SSDP.setURL((char*)"/"); + SSDP.setModelName((char*)"ESP-12e"); + SSDP.setModelNumber(1, 0); + SSDP.setModelURL((char*)"http://12e.espressif.com"); + SSDP.setManufacturer((char*)"Espressif"); + SSDP.setManufacturerURL((char*)"http://espressif.com"); + + Serial.printf("Ready!\n"); + } else { + Serial.printf("WiFi Failed\n"); + while(1) delay(100); + } +} + +void loop() { + HTTP.handleClient(); + SSDP.update(); + delay(1); +} \ No newline at end of file diff --git a/libraries/ESP8266SSDP/keywords.txt b/libraries/ESP8266SSDP/keywords.txt new file mode 100644 index 000000000..505ddf4df --- /dev/null +++ b/libraries/ESP8266SSDP/keywords.txt @@ -0,0 +1,53 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP8266SSDP KEYWORD1 +SSDP KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +update KEYWORD2 +send KEYWORD2 +schema KEYWORD2 +setName KEYWORD2 +setURL KEYWORD2 +setSerialNumber KEYWORD2 +setModelName KEYWORD2 +setModelNumber KEYWORD2 +setModelURL KEYWORD2 +setManufacturer KEYWORD2 +setManufacturerURL KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### +SSDP_INTERVAL LITERAL1 +SSDP_PORT LITERAL1 +SSDP_METHOD_SIZE LITERAL1 +SSDP_URI_SIZE LITERAL1 +SSDP_BUFFER_SIZE LITERAL1 +SSDP_BASE_SIZE LITERAL1 +SSDP_FRIENDLY_NAME_SIZE LITERAL1 +SSDP_SERIAL_NUMBER_SIZE LITERAL1 +SSDP_PRESENTATION_URL_SIZE LITERAL1 +SSDP_MODEL_NAME_SIZE LITERAL1 +SSDP_MODEL_URL_SIZE LITERAL1 +SSDP_MODEL_VERSION_SIZE LITERAL1 +SSDP_MANUFACTURER_SIZE LITERAL1 +SSDP_MANUFACTURER_URL_SIZE LITERAL1 +SEARCH LITERAL1 +NOTIFY LITERAL1 +BASIC LITERAL1 +MANAGEABLE LITERAL1 +SOLARPROTECTIONBLIND LITERAL1 +DIGITALSECURITYCAMERA LITERAL1 +HVAC LITERAL1 +LIGHTINGCONTROL LITERAL1 From 9cb80528c71f5cf7902af31d93c9968f4f2fce3d Mon Sep 17 00:00:00 2001 From: John Doe Date: Sat, 4 Jul 2015 23:16:00 +0300 Subject: [PATCH 31/53] enable long model versions --- libraries/ESP8266SSDP/ESP8266SSDP.cpp | 18 ++++++++---------- libraries/ESP8266SSDP/ESP8266SSDP.h | 12 +++--------- libraries/ESP8266SSDP/examples/SSDP.ino | 4 ++-- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp index 1077f66fb..60bde31ed 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.cpp +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -57,7 +57,7 @@ const char* _ssdp_notify_template = const char* _ssdp_packet_template = "%s" // _ssdp_response_template / _ssdp_notify_template "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL - "SERVER: Arduino/1.0 UPNP/1.1 %s/%u.%u\r\n" // _modelName, _modelNumber->major, _modelNumber->minor + "SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber "USN: uuid:%s-%02X%02X%02X%02X%02X%02X\r\n" // _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5] "LOCATION: http://%u.%u.%u.%u/ssdp/schema.xml\r\n" // WiFi.localIP() "\r\n"; @@ -80,7 +80,7 @@ const char* _ssdp_schema_template = "%s" "%s" "%s" - "%u.%u" + "%s" "%s" "%s" "%s" @@ -96,13 +96,12 @@ SSDPClass::SSDPClass(){ _presentationURL = (char*)os_malloc(SSDP_PRESENTATION_URL_SIZE); _serialNumber = (char*)os_malloc(SSDP_SERIAL_NUMBER_SIZE); _modelName = (char*)os_malloc(SSDP_MODEL_NAME_SIZE); - _modelNumber = (ssdp_version_t*)os_malloc(SSDP_MODEL_VERSION_SIZE); + _modelNumber = (char*)os_malloc(SSDP_MODEL_VERSION_SIZE); _modelURL = (char*)os_malloc(SSDP_MODEL_URL_SIZE); _manufacturer = (char*)os_malloc(SSDP_MANUFACTURER_SIZE); _manufacturerURL = (char*)os_malloc(SSDP_MANUFACTURER_URL_SIZE); - _modelNumber->major = 0; - _modelNumber->minor = 0; + _modelNumber[0] = '\0'; _friendlyName[0] = '\0'; _presentationURL[0] = '\0'; _serialNumber[0] = '\0'; @@ -165,7 +164,7 @@ void SSDPClass::send(ssdp_method_t method){ _server.printf(_ssdp_packet_template, (method == NONE)?_ssdp_response_template:_ssdp_notify_template, SSDP_INTERVAL, - _modelName, _modelNumber->major, _modelNumber->minor, + _modelName, _modelNumber, _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5], (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), (uint8_t)((ip >> 24) & 0xFF) ); @@ -179,7 +178,7 @@ void SSDPClass::schema(WiFiClient client){ _presentationURL, _serialNumber, _modelName, - _modelNumber->major, _modelNumber->minor, + _modelNumber, _modelURL, _manufacturer, _manufacturerURL, @@ -300,9 +299,8 @@ void SSDPClass::setModelName(char *name){ strcpy(_modelName, name); } -void SSDPClass::setModelNumber(uint8_t major, uint8_t minor){ - _modelNumber->major = major; - _modelNumber->minor = minor; +void SSDPClass::setModelNumber(char *num){ + strcpy(_modelNumber, num); } void SSDPClass::setModelURL(char *url){ diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.h b/libraries/ESP8266SSDP/ESP8266SSDP.h index 765809179..04fb41fab 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.h +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -41,7 +41,7 @@ License (MIT license): #define SSDP_PRESENTATION_URL_SIZE 32 #define SSDP_MODEL_NAME_SIZE 32 #define SSDP_MODEL_URL_SIZE 32 -#define SSDP_MODEL_VERSION_SIZE 2 +#define SSDP_MODEL_VERSION_SIZE 32 #define SSDP_MANUFACTURER_SIZE 32 #define SSDP_MANUFACTURER_URL_SIZE 32 @@ -51,12 +51,6 @@ typedef enum { NOTIFY } ssdp_method_t; -typedef struct { - uint8_t major; - uint8_t minor; -} ssdp_version_t; - - class SSDPClass{ public: SSDPClass(); @@ -71,7 +65,7 @@ class SSDPClass{ void setURL(char *url); void setSerialNumber(char *serialNumber); void setModelName(char *name); - void setModelNumber(uint8_t major, uint8_t minor); + void setModelNumber(char *num); void setModelURL(char *url); void setManufacturer(char *name); void setManufacturerURL(char *url); @@ -92,7 +86,7 @@ class SSDPClass{ char *_manufacturerURL; char *_modelName; char *_modelURL; - ssdp_version_t *_modelNumber; + char *_modelNumber; }; extern SSDPClass SSDP; diff --git a/libraries/ESP8266SSDP/examples/SSDP.ino b/libraries/ESP8266SSDP/examples/SSDP.ino index fa7753622..236773abf 100644 --- a/libraries/ESP8266SSDP/examples/SSDP.ino +++ b/libraries/ESP8266SSDP/examples/SSDP.ino @@ -38,7 +38,7 @@ void setup() { SSDP.setSerialNumber((char*)"A0123456789"); SSDP.setURL((char*)"/"); SSDP.setModelName((char*)"ESP-12e"); - SSDP.setModelNumber(1, 0); + SSDP.setModelNumber((char*)"1.0"); SSDP.setModelURL((char*)"http://12e.espressif.com"); SSDP.setManufacturer((char*)"Espressif"); SSDP.setManufacturerURL((char*)"http://espressif.com"); @@ -54,4 +54,4 @@ void loop() { HTTP.handleClient(); SSDP.update(); delay(1); -} \ No newline at end of file +} From 3c54cb0a26ca797db47a16c0517a4ae38f87642a Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 01:36:43 +0300 Subject: [PATCH 32/53] generate UUID automatically based on chip ID and MAC address --- libraries/ESP8266SSDP/ESP8266SSDP.cpp | 41 +++++++++++++++++-------- libraries/ESP8266SSDP/ESP8266SSDP.h | 14 ++------- libraries/ESP8266SSDP/examples/SSDP.ino | 16 +++------- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp index 60bde31ed..4ad6e191b 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.cpp +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -41,6 +41,16 @@ extern "C" { #define SSDP_URI_SIZE 2 #define SSDP_BUFFER_SIZE 64 +#define SSDP_UUID_SIZE 37 +#define SSDP_FRIENDLY_NAME_SIZE 64 +#define SSDP_SERIAL_NUMBER_SIZE 32 +#define SSDP_PRESENTATION_URL_SIZE 128 +#define SSDP_MODEL_NAME_SIZE 64 +#define SSDP_MODEL_URL_SIZE 128 +#define SSDP_MODEL_VERSION_SIZE 32 +#define SSDP_MANUFACTURER_SIZE 64 +#define SSDP_MANUFACTURER_URL_SIZE 128 + static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); const char* _ssdp_response_template = @@ -58,7 +68,7 @@ const char* _ssdp_packet_template = "%s" // _ssdp_response_template / _ssdp_notify_template "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL "SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber - "USN: uuid:%s-%02X%02X%02X%02X%02X%02X\r\n" // _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5] + "USN: uuid:%s\r\n" // _uuid "LOCATION: http://%u.%u.%u.%u/ssdp/schema.xml\r\n" // WiFi.localIP() "\r\n"; @@ -84,14 +94,13 @@ const char* _ssdp_schema_template = "%s" "%s" "%s" - "%s-%02X%02X%02X%02X%02X%02X" //uuid:UUID + "%s" "" "\r\n" "\r\n"; SSDPClass::SSDPClass(){ - _base = (char*)os_malloc(SSDP_BASE_SIZE); - _mac = (byte*)os_malloc(6); + _uuid = (char*)os_malloc(SSDP_UUID_SIZE); _friendlyName = (char*)os_malloc(SSDP_FRIENDLY_NAME_SIZE); _presentationURL = (char*)os_malloc(SSDP_PRESENTATION_URL_SIZE); _serialNumber = (char*)os_malloc(SSDP_SERIAL_NUMBER_SIZE); @@ -101,6 +110,7 @@ SSDPClass::SSDPClass(){ _manufacturer = (char*)os_malloc(SSDP_MANUFACTURER_SIZE); _manufacturerURL = (char*)os_malloc(SSDP_MANUFACTURER_URL_SIZE); + _uuid[0] = '\0'; _modelNumber[0] = '\0'; _friendlyName[0] = '\0'; _presentationURL[0] = '\0'; @@ -113,8 +123,7 @@ SSDPClass::SSDPClass(){ } SSDPClass::~SSDPClass(){ - os_free(_base); - os_free(_mac); + os_free(_uuid); os_free(_friendlyName); os_free(_presentationURL); os_free(_serialNumber); @@ -126,18 +135,26 @@ SSDPClass::~SSDPClass(){ _pending = false; } -void SSDPClass::begin(char *base){ +void SSDPClass::begin(){ ip_addr_t ifaddr; ip_addr_t multicast_addr; _pending = false; - strcpy(_base, base); - WiFi.macAddress(_mac); ifaddr.addr = WiFi.localIP(); multicast_addr.addr = (uint32_t) SSDP_MULTICAST_ADDR; igmp_joingroup(&ifaddr, &multicast_addr); - + + uint8_t mac[6]; + WiFi.macAddress(mac); + uint32_t chipId = ESP.getChipId(); + sprintf(_uuid, "38323636-4558-%04X-%04X-%02X%02X%02X%02X%02X%02X", + (chipId >> 16) & 0xFFFF, chipId & 0xFFFF, + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf("SSDP UUID: %s\n", (char *)_uuid); +#endif _server.begin(SSDP_PORT); } @@ -165,7 +182,7 @@ void SSDPClass::send(ssdp_method_t method){ (method == NONE)?_ssdp_response_template:_ssdp_notify_template, SSDP_INTERVAL, _modelName, _modelNumber, - _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5], + _uuid, (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), (uint8_t)((ip >> 24) & 0xFF) ); @@ -182,7 +199,7 @@ void SSDPClass::schema(WiFiClient client){ _modelURL, _manufacturer, _manufacturerURL, - _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5] + _uuid ); } diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.h b/libraries/ESP8266SSDP/ESP8266SSDP.h index 04fb41fab..e2d8b3b97 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.h +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -35,16 +35,6 @@ License (MIT license): #define DEBUG_SSDP Serial -#define SSDP_BASE_SIZE 24 -#define SSDP_FRIENDLY_NAME_SIZE 32 -#define SSDP_SERIAL_NUMBER_SIZE 32 -#define SSDP_PRESENTATION_URL_SIZE 32 -#define SSDP_MODEL_NAME_SIZE 32 -#define SSDP_MODEL_URL_SIZE 32 -#define SSDP_MODEL_VERSION_SIZE 32 -#define SSDP_MANUFACTURER_SIZE 32 -#define SSDP_MANUFACTURER_URL_SIZE 32 - typedef enum { NONE, SEARCH, @@ -56,7 +46,7 @@ class SSDPClass{ SSDPClass(); ~SSDPClass(); - void begin(char *base); + void begin(); uint8_t update(); void send(ssdp_method_t method); void schema(WiFiClient client); @@ -78,7 +68,7 @@ class SSDPClass{ unsigned long _notify_time; uint8_t *_mac; - char *_base; + char *_uuid; char *_friendlyName; char *_serialNumber; char *_presentationURL; diff --git a/libraries/ESP8266SSDP/examples/SSDP.ino b/libraries/ESP8266SSDP/examples/SSDP.ino index 236773abf..6c94836e9 100644 --- a/libraries/ESP8266SSDP/examples/SSDP.ino +++ b/libraries/ESP8266SSDP/examples/SSDP.ino @@ -11,29 +11,23 @@ ESP8266WebServer HTTP(80); void setup() { Serial.begin(115200); Serial.println(); - Serial.println("Booting Sketch..."); + Serial.println("Starting WiFi..."); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if(WiFi.waitForConnectResult() == WL_CONNECTED){ + + Serial.printf("Starting HTTP...\n"); HTTP.on("/", HTTP_GET, [](){ - HTTP.sendHeader("Connection", "close"); - HTTP.sendHeader("Access-Control-Allow-Origin", "*"); HTTP.send(200, "text/plain", "Hello World!"); }); HTTP.on("/ssdp/schema.xml", HTTP_GET, [](){ SSDP.schema(HTTP.client()); }); HTTP.begin(); - - byte mac[6]; - char base[24]; - WiFi.macAddress(mac); - sprintf(base, "esp8266x-%02x%02x-%02x%02x-%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - Serial.printf("Starting SSDP: %s\n", base); - - SSDP.begin((char*)base); + Serial.printf("Starting SSDP...\n"); + SSDP.begin(); SSDP.setName((char*)"ESP8266"); SSDP.setSerialNumber((char*)"A0123456789"); SSDP.setURL((char*)"/"); From bd6c4acfd87dc8844a0dac5360d130d2162a5c9d Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 12:40:59 +0300 Subject: [PATCH 33/53] Add ability to change schema url, http port and add base url --- libraries/ESP8266SSDP/ESP8266SSDP.cpp | 22 +++++++++++++++++++--- libraries/ESP8266SSDP/ESP8266SSDP.h | 5 ++++- libraries/ESP8266SSDP/examples/SSDP.ino | 22 ++++++++++++---------- libraries/ESP8266SSDP/keywords.txt | 2 ++ 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp index 4ad6e191b..f118fe951 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.cpp +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -42,6 +42,7 @@ extern "C" { #define SSDP_BUFFER_SIZE 64 #define SSDP_UUID_SIZE 37 +#define SSDP_SCHEMA_URL_SIZE 64 #define SSDP_FRIENDLY_NAME_SIZE 64 #define SSDP_SERIAL_NUMBER_SIZE 32 #define SSDP_PRESENTATION_URL_SIZE 128 @@ -69,7 +70,7 @@ const char* _ssdp_packet_template = "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL "SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber "USN: uuid:%s\r\n" // _uuid - "LOCATION: http://%u.%u.%u.%u/ssdp/schema.xml\r\n" // WiFi.localIP() + "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _shemaURL "\r\n"; const char* _ssdp_schema_template = @@ -84,6 +85,7 @@ const char* _ssdp_schema_template = "1" "0" "" + "http://%u.%u.%u.%u:%u/" // WiFi.localIP(), _port "" "urn:schemas-upnp-org:device:Basic:1" "%s" @@ -94,13 +96,14 @@ const char* _ssdp_schema_template = "%s" "%s" "%s" - "%s" + "uuid:%s" "" "\r\n" "\r\n"; SSDPClass::SSDPClass(){ _uuid = (char*)os_malloc(SSDP_UUID_SIZE); + _schemaURL = (char*)os_malloc(SSDP_SCHEMA_URL_SIZE); _friendlyName = (char*)os_malloc(SSDP_FRIENDLY_NAME_SIZE); _presentationURL = (char*)os_malloc(SSDP_PRESENTATION_URL_SIZE); _serialNumber = (char*)os_malloc(SSDP_SERIAL_NUMBER_SIZE); @@ -119,11 +122,14 @@ SSDPClass::SSDPClass(){ _modelURL[0] = '\0'; _manufacturer[0] = '\0'; _manufacturerURL[0] = '\0'; + sprintf(_schemaURL, "ssdp/schema.xml"); + _port = 80; _pending = false; } SSDPClass::~SSDPClass(){ os_free(_uuid); + os_free(_schemaURL); os_free(_friendlyName); os_free(_presentationURL); os_free(_serialNumber); @@ -183,14 +189,16 @@ void SSDPClass::send(ssdp_method_t method){ SSDP_INTERVAL, _modelName, _modelNumber, _uuid, - (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), (uint8_t)((ip >> 24) & 0xFF) + (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), (uint8_t)((ip >> 24) & 0xFF), _port, _schemaURL ); _server.endPacket(); } void SSDPClass::schema(WiFiClient client){ + uint32_t ip = WiFi.localIP(); client.printf(_ssdp_schema_template, + (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), (uint8_t)((ip >> 24) & 0xFF), _port, _friendlyName, _presentationURL, _serialNumber, @@ -300,6 +308,14 @@ uint8_t SSDPClass::update(){ } } +void SSDPClass::setSchemaURL(char *url){ + strcpy(_schemaURL, url); +} + +void SSDPClass::setHTTPPort(uint16_t port){ + _port = port; +} + void SSDPClass::setName(char *name){ strcpy(_friendlyName, name); } diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.h b/libraries/ESP8266SSDP/ESP8266SSDP.h index e2d8b3b97..aa696953b 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.h +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -53,6 +53,8 @@ class SSDPClass{ void setName(char *name); void setURL(char *url); + void setSchemaURL(char *url); + void setHTTPPort(uint16_t port); void setSerialNumber(char *serialNumber); void setModelName(char *name); void setModelNumber(char *num); @@ -67,7 +69,8 @@ class SSDPClass{ unsigned long _process_time; unsigned long _notify_time; - uint8_t *_mac; + uint16_t _port; + char *_schemaURL; char *_uuid; char *_friendlyName; char *_serialNumber; diff --git a/libraries/ESP8266SSDP/examples/SSDP.ino b/libraries/ESP8266SSDP/examples/SSDP.ino index 6c94836e9..2f2d66ee7 100644 --- a/libraries/ESP8266SSDP/examples/SSDP.ino +++ b/libraries/ESP8266SSDP/examples/SSDP.ino @@ -18,24 +18,26 @@ void setup() { if(WiFi.waitForConnectResult() == WL_CONNECTED){ Serial.printf("Starting HTTP...\n"); - HTTP.on("/", HTTP_GET, [](){ + HTTP.on("/index.html", HTTP_GET, [](){ HTTP.send(200, "text/plain", "Hello World!"); }); - HTTP.on("/ssdp/schema.xml", HTTP_GET, [](){ + HTTP.on("/description.xml", HTTP_GET, [](){ SSDP.schema(HTTP.client()); }); HTTP.begin(); Serial.printf("Starting SSDP...\n"); SSDP.begin(); - SSDP.setName((char*)"ESP8266"); - SSDP.setSerialNumber((char*)"A0123456789"); - SSDP.setURL((char*)"/"); - SSDP.setModelName((char*)"ESP-12e"); - SSDP.setModelNumber((char*)"1.0"); - SSDP.setModelURL((char*)"http://12e.espressif.com"); - SSDP.setManufacturer((char*)"Espressif"); - SSDP.setManufacturerURL((char*)"http://espressif.com"); + SSDP.setSchemaURL((char*)"description.xml"); + SSDP.setHTTPPort(80); + SSDP.setName((char*)"Philips hue clone"); + SSDP.setSerialNumber((char*)"001788102201"); + SSDP.setURL((char*)"index.html"); + SSDP.setModelName((char*)"Philips hue bridge 2012"); + SSDP.setModelNumber((char*)"929000226503"); + SSDP.setModelURL((char*)"http://www.meethue.com"); + SSDP.setManufacturer((char*)"Royal Philips Electronics"); + SSDP.setManufacturerURL((char*)"http://www.philips.com"); Serial.printf("Ready!\n"); } else { diff --git a/libraries/ESP8266SSDP/keywords.txt b/libraries/ESP8266SSDP/keywords.txt index 505ddf4df..40070d672 100644 --- a/libraries/ESP8266SSDP/keywords.txt +++ b/libraries/ESP8266SSDP/keywords.txt @@ -19,6 +19,8 @@ send KEYWORD2 schema KEYWORD2 setName KEYWORD2 setURL KEYWORD2 +setHTTPPort KEYWORD2 +setSchemaURL KEYWORD2 setSerialNumber KEYWORD2 setModelName KEYWORD2 setModelNumber KEYWORD2 From e34ae2d6f1820db63958cbc66b802c5043cbd299 Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 14:23:01 +0300 Subject: [PATCH 34/53] prep for icons --- libraries/ESP8266SSDP/ESP8266SSDP.cpp | 22 ++++++++++++++++++++-- libraries/ESP8266SSDP/ESP8266SSDP.h | 2 -- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp index f118fe951..99ee9c761 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.cpp +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -35,6 +35,8 @@ extern "C" { } #include "lwip/igmp.h" +//#define DEBUG_SSDP Serial + #define SSDP_INTERVAL 1200 #define SSDP_PORT 1900 #define SSDP_METHOD_SIZE 10 @@ -70,7 +72,7 @@ const char* _ssdp_packet_template = "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL "SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber "USN: uuid:%s\r\n" // _uuid - "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _shemaURL + "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL "\r\n"; const char* _ssdp_schema_template = @@ -98,6 +100,22 @@ const char* _ssdp_schema_template = "%s" "uuid:%s" "" +// "" +// "" +// "image/png" +// "48" +// "48" +// "24" +// "icon48.png" +// "" +// "" +// "image/png" +// "120" +// "120" +// "24" +// "icon120.png" +// "" +// "" "\r\n" "\r\n"; @@ -154,7 +172,7 @@ void SSDPClass::begin(){ uint8_t mac[6]; WiFi.macAddress(mac); uint32_t chipId = ESP.getChipId(); - sprintf(_uuid, "38323636-4558-%04X-%04X-%02X%02X%02X%02X%02X%02X", + sprintf(_uuid, "38323636-4558-%04x-%04x-%02x%02x%02x%02x%02x%02x", (chipId >> 16) & 0xFFFF, chipId & 0xFFFF, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.h b/libraries/ESP8266SSDP/ESP8266SSDP.h index aa696953b..efaba1ac7 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.h +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -33,8 +33,6 @@ License (MIT license): #include #include -#define DEBUG_SSDP Serial - typedef enum { NONE, SEARCH, From de70454a2a5eb275a0acf63763834c6bee1f0ee3 Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 19:15:13 +0300 Subject: [PATCH 35/53] send not needed to be public --- libraries/ESP8266SSDP/ESP8266SSDP.cpp | 6 +++--- libraries/ESP8266SSDP/ESP8266SSDP.h | 3 ++- libraries/ESP8266SSDP/keywords.txt | 1 - 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp index 99ee9c761..081ca0419 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.cpp +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -182,7 +182,7 @@ void SSDPClass::begin(){ _server.begin(SSDP_PORT); } -void SSDPClass::send(ssdp_method_t method){ +void SSDPClass::_send(ssdp_method_t method){ #ifdef DEBUG_SSDP if(method == NONE){ DEBUG_SSDP.print("Sending Response to "); @@ -319,10 +319,10 @@ uint8_t SSDPClass::update(){ if(_pending && (millis() - _process_time) > _delay){ _pending = false; _delay = 0; - send(NONE); + _send(NONE); }else if(_notify_time == 0 || (millis() - _notify_time) > (SSDP_INTERVAL * 1000L)){ _notify_time = millis(); - send(NOTIFY); + _send(NOTIFY); } } diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.h b/libraries/ESP8266SSDP/ESP8266SSDP.h index efaba1ac7..1a2528151 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.h +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -46,7 +46,6 @@ class SSDPClass{ void begin(); uint8_t update(); - void send(ssdp_method_t method); void schema(WiFiClient client); void setName(char *name); @@ -78,6 +77,8 @@ class SSDPClass{ char *_modelName; char *_modelURL; char *_modelNumber; + + void _send(ssdp_method_t method); }; extern SSDPClass SSDP; diff --git a/libraries/ESP8266SSDP/keywords.txt b/libraries/ESP8266SSDP/keywords.txt index 40070d672..a01ce3dba 100644 --- a/libraries/ESP8266SSDP/keywords.txt +++ b/libraries/ESP8266SSDP/keywords.txt @@ -15,7 +15,6 @@ SSDP KEYWORD1 begin KEYWORD2 update KEYWORD2 -send KEYWORD2 schema KEYWORD2 setName KEYWORD2 setURL KEYWORD2 From 49dc457fe54ad6a57cf4e20636c39956f72a9edc Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 7 Jul 2015 14:39:39 +0300 Subject: [PATCH 36/53] Add strlcpy implementation (#465) --- cores/esp8266/libc_replacements.c | 57 ++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/libc_replacements.c b/cores/esp8266/libc_replacements.c index e8e31afa4..84ba411e0 100644 --- a/cores/esp8266/libc_replacements.c +++ b/cores/esp8266/libc_replacements.c @@ -94,10 +94,6 @@ size_t ICACHE_FLASH_ATTR strnlen(const char *s, size_t len) { return (size_t)(cp - s); } -char* strstr(const char *haystack, const char *needle) { - return ets_strstr(haystack, needle); -} - char* ICACHE_FLASH_ATTR strchr(const char * str, int character) { while(1) { if(*str == 0x00) { @@ -443,3 +439,56 @@ int* ICACHE_FLASH_ATTR __errno(void) { return &errno_var; } + +/* + * begin newlib/string/strlcpy.c + * + * Copyright (c) 1998 Todd C. Miller + * 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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. + */ + +size_t ICACHE_FLASH_ATTR strlcpy(char* dst, const char* src, size_t size) { + const char *s = src; + size_t n = size; + + if (n != 0 && --n != 0) { + do { + if ((*dst++ = *s++) == 0) + break; + } while (--n != 0); + } + + if (n == 0) { + if (size != 0) + *dst = 0; + while (*s++); + } + + return(s - src - 1); +} +/* + * end of newlib/string/strlcpy.c + */ + From 7fbb4831dad0f0bb07f4b09bf8c42a819c437f15 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 7 Jul 2015 14:42:20 +0300 Subject: [PATCH 37/53] Use static allocation, add convenience overloads which take String --- libraries/ESP8266SSDP/ESP8266SSDP.cpp | 93 +++++++++------------------ libraries/ESP8266SSDP/ESP8266SSDP.h | 58 +++++++++++------ 2 files changed, 69 insertions(+), 82 deletions(-) diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp index 081ca0419..c381f7ff9 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.cpp +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -25,14 +25,14 @@ License (MIT license): THE SOFTWARE. */ - +#define LWIP_OPEN_SRC #include "ESP8266SSDP.h" extern "C" { - #include "ip_addr.h" #include "user_interface.h" #include "mem.h" } +#include "lwip/ip_addr.h" #include "lwip/igmp.h" //#define DEBUG_SSDP Serial @@ -43,31 +43,20 @@ extern "C" { #define SSDP_URI_SIZE 2 #define SSDP_BUFFER_SIZE 64 -#define SSDP_UUID_SIZE 37 -#define SSDP_SCHEMA_URL_SIZE 64 -#define SSDP_FRIENDLY_NAME_SIZE 64 -#define SSDP_SERIAL_NUMBER_SIZE 32 -#define SSDP_PRESENTATION_URL_SIZE 128 -#define SSDP_MODEL_NAME_SIZE 64 -#define SSDP_MODEL_URL_SIZE 128 -#define SSDP_MODEL_VERSION_SIZE 32 -#define SSDP_MANUFACTURER_SIZE 64 -#define SSDP_MANUFACTURER_URL_SIZE 128 - static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); -const char* _ssdp_response_template = +static const char* _ssdp_response_template = "HTTP/1.1 200 OK\r\n" "EXT:\r\n" "ST: upnp:rootdevice\r\n"; -const char* _ssdp_notify_template = +static const char* _ssdp_notify_template = "NOTIFY * HTTP/1.1\r\n" "HOST: 239.255.255.250:1900\r\n" "NT: upnp:rootdevice\r\n" "NTS: ssdp:alive\r\n"; -const char* _ssdp_packet_template = +static const char* _ssdp_packet_template = "%s" // _ssdp_response_template / _ssdp_notify_template "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL "SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber @@ -75,7 +64,7 @@ const char* _ssdp_packet_template = "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL "\r\n"; -const char* _ssdp_schema_template = +static const char* _ssdp_schema_template = "HTTP/1.1 200 OK\r\n" "Content-Type: text/xml\r\n" "Connection: close\r\n" @@ -120,17 +109,6 @@ const char* _ssdp_schema_template = "\r\n"; SSDPClass::SSDPClass(){ - _uuid = (char*)os_malloc(SSDP_UUID_SIZE); - _schemaURL = (char*)os_malloc(SSDP_SCHEMA_URL_SIZE); - _friendlyName = (char*)os_malloc(SSDP_FRIENDLY_NAME_SIZE); - _presentationURL = (char*)os_malloc(SSDP_PRESENTATION_URL_SIZE); - _serialNumber = (char*)os_malloc(SSDP_SERIAL_NUMBER_SIZE); - _modelName = (char*)os_malloc(SSDP_MODEL_NAME_SIZE); - _modelNumber = (char*)os_malloc(SSDP_MODEL_VERSION_SIZE); - _modelURL = (char*)os_malloc(SSDP_MODEL_URL_SIZE); - _manufacturer = (char*)os_malloc(SSDP_MANUFACTURER_SIZE); - _manufacturerURL = (char*)os_malloc(SSDP_MANUFACTURER_URL_SIZE); - _uuid[0] = '\0'; _modelNumber[0] = '\0'; _friendlyName[0] = '\0'; @@ -146,17 +124,6 @@ SSDPClass::SSDPClass(){ } SSDPClass::~SSDPClass(){ - os_free(_uuid); - os_free(_schemaURL); - os_free(_friendlyName); - os_free(_presentationURL); - os_free(_serialNumber); - os_free(_modelName); - os_free(_modelNumber); - os_free(_modelURL); - os_free(_manufacturer); - os_free(_manufacturerURL); - _pending = false; } void SSDPClass::begin(){ @@ -207,7 +174,7 @@ void SSDPClass::_send(ssdp_method_t method){ SSDP_INTERVAL, _modelName, _modelNumber, _uuid, - (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), (uint8_t)((ip >> 24) & 0xFF), _port, _schemaURL + IP2STR(&ip), _port, _schemaURL ); _server.endPacket(); @@ -216,7 +183,7 @@ void SSDPClass::_send(ssdp_method_t method){ void SSDPClass::schema(WiFiClient client){ uint32_t ip = WiFi.localIP(); client.printf(_ssdp_schema_template, - (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), (uint8_t)((ip >> 24) & 0xFF), _port, + IP2STR(&ip), _port, _friendlyName, _presentationURL, _serialNumber, @@ -259,14 +226,14 @@ uint8_t SSDPClass::update(){ else state = URI; cursor = 0; - }else if(cursor < SSDP_METHOD_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + } else if(cursor < SSDP_METHOD_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } break; case URI: if(c == ' '){ if(strcmp(buffer, "*")) state = ABORT; else state = PROTO; cursor = 0; - }else if(cursor < SSDP_URI_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + } else if(cursor < SSDP_URI_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } break; case PROTO: if(cr == 2){ state = KEY; cursor = 0; } @@ -298,7 +265,7 @@ uint8_t SSDPClass::update(){ } if(state != ABORT){ state = KEY; header = START; cursor = 0; } - }else if(c != '\r' && c != '\n'){ + } else if(c != '\r' && c != '\n'){ if(header == START){ if(strncmp(buffer, "MA", 2) == 0) header = MAN; else if(strcmp(buffer, "ST") == 0) header = ST; @@ -320,50 +287,50 @@ uint8_t SSDPClass::update(){ if(_pending && (millis() - _process_time) > _delay){ _pending = false; _delay = 0; _send(NONE); - }else if(_notify_time == 0 || (millis() - _notify_time) > (SSDP_INTERVAL * 1000L)){ + } else if(_notify_time == 0 || (millis() - _notify_time) > (SSDP_INTERVAL * 1000L)){ _notify_time = millis(); _send(NOTIFY); } } -void SSDPClass::setSchemaURL(char *url){ - strcpy(_schemaURL, url); +void SSDPClass::setSchemaURL(const char *url){ + strlcpy(_schemaURL, url, sizeof(_schemaURL)); } void SSDPClass::setHTTPPort(uint16_t port){ _port = port; } -void SSDPClass::setName(char *name){ - strcpy(_friendlyName, name); +void SSDPClass::setName(const char *name){ + strlcpy(_friendlyName, name, sizeof(_friendlyName)); } -void SSDPClass::setURL(char *url){ - strcpy(_presentationURL, url); +void SSDPClass::setURL(const char *url){ + strlcpy(_presentationURL, url, sizeof(_presentationURL)); } -void SSDPClass::setSerialNumber(char *serialNumber){ - strcpy(_serialNumber, serialNumber); +void SSDPClass::setSerialNumber(const char *serialNumber){ + strlcpy(_serialNumber, serialNumber, sizeof(_serialNumber)); } -void SSDPClass::setModelName(char *name){ - strcpy(_modelName, name); +void SSDPClass::setModelName(const char *name){ + strlcpy(_modelName, name, sizeof(_modelName)); } -void SSDPClass::setModelNumber(char *num){ - strcpy(_modelNumber, num); +void SSDPClass::setModelNumber(const char *num){ + strlcpy(_modelNumber, num, sizeof(_modelNumber)); } -void SSDPClass::setModelURL(char *url){ - strcpy(_modelURL, url); +void SSDPClass::setModelURL(const char *url){ + strlcpy(_modelURL, url, sizeof(_modelURL)); } -void SSDPClass::setManufacturer(char *name){ - strcpy(_manufacturer, name); +void SSDPClass::setManufacturer(const char *name){ + strlcpy(_manufacturer, name, sizeof(_manufacturer)); } -void SSDPClass::setManufacturerURL(char *url){ - strcpy(_manufacturerURL, url); +void SSDPClass::setManufacturerURL(const char *url){ + strlcpy(_manufacturerURL, url, sizeof(_manufacturerURL)); } SSDPClass SSDP; diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.h b/libraries/ESP8266SSDP/ESP8266SSDP.h index 1a2528151..44e8b4423 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.h +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -33,6 +33,17 @@ License (MIT license): #include #include +#define SSDP_UUID_SIZE 37 +#define SSDP_SCHEMA_URL_SIZE 64 +#define SSDP_FRIENDLY_NAME_SIZE 64 +#define SSDP_SERIAL_NUMBER_SIZE 32 +#define SSDP_PRESENTATION_URL_SIZE 128 +#define SSDP_MODEL_NAME_SIZE 64 +#define SSDP_MODEL_URL_SIZE 128 +#define SSDP_MODEL_VERSION_SIZE 32 +#define SSDP_MANUFACTURER_SIZE 64 +#define SSDP_MANUFACTURER_URL_SIZE 128 + typedef enum { NONE, SEARCH, @@ -48,16 +59,25 @@ class SSDPClass{ uint8_t update(); void schema(WiFiClient client); - void setName(char *name); - void setURL(char *url); - void setSchemaURL(char *url); + void setName(const String& name) { setName(name.c_str()); } + void setName(const char *name); + void setURL(const String& url) { setURL(url.c_str()); } + void setURL(const char *url); + void setSchemaURL(const String& url) { setSchemaURL(url.c_str()); } + void setSchemaURL(const char *url); + void setSerialNumber(const String& serialNumber) { setSerialNumber(serialNumber.c_str()); } + void setSerialNumber(const char *serialNumber); + void setModelName(const String& name) { setModelName(name.c_str()); } + void setModelName(const char *name); + void setModelNumber(const String& num) { setModelNumber(num.c_str()); } + void setModelNumber(const char *num); + void setModelURL(const String& url) { setModelURL(url.c_str()); } + void setModelURL(const char *url); + void setManufacturer(const String& name) { setManufacturer(name.c_str()); } + void setManufacturer(const char *name); + void setManufacturerURL(const String& url) { setManufacturerURL(url.c_str()); } + void setManufacturerURL(const char *url); void setHTTPPort(uint16_t port); - void setSerialNumber(char *serialNumber); - void setModelName(char *name); - void setModelNumber(char *num); - void setModelURL(char *url); - void setManufacturer(char *name); - void setManufacturerURL(char *url); private: WiFiUDP _server; @@ -67,16 +87,16 @@ class SSDPClass{ unsigned long _notify_time; uint16_t _port; - char *_schemaURL; - char *_uuid; - char *_friendlyName; - char *_serialNumber; - char *_presentationURL; - char *_manufacturer; - char *_manufacturerURL; - char *_modelName; - char *_modelURL; - char *_modelNumber; + char _schemaURL[SSDP_SCHEMA_URL_SIZE]; + char _uuid[SSDP_UUID_SIZE]; + char _friendlyName[SSDP_FRIENDLY_NAME_SIZE]; + char _serialNumber[SSDP_SERIAL_NUMBER_SIZE]; + char _presentationURL[SSDP_PRESENTATION_URL_SIZE]; + char _manufacturer[SSDP_MANUFACTURER_SIZE]; + char _manufacturerURL[SSDP_MANUFACTURER_URL_SIZE]; + char _modelName[SSDP_MODEL_NAME_SIZE]; + char _modelURL[SSDP_MODEL_URL_SIZE]; + char _modelNumber[SSDP_MODEL_VERSION_SIZE]; void _send(ssdp_method_t method); }; From 0c0892c54adf7cd56a9b5240ed186619853ab971 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 7 Jul 2015 14:43:19 +0300 Subject: [PATCH 38/53] Update example and move it to the right location --- .../ESP8266SSDP/examples/{ => SSDP}/SSDP.ino | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename libraries/ESP8266SSDP/examples/{ => SSDP}/SSDP.ino (66%) diff --git a/libraries/ESP8266SSDP/examples/SSDP.ino b/libraries/ESP8266SSDP/examples/SSDP/SSDP.ino similarity index 66% rename from libraries/ESP8266SSDP/examples/SSDP.ino rename to libraries/ESP8266SSDP/examples/SSDP/SSDP.ino index 2f2d66ee7..be4c0791e 100644 --- a/libraries/ESP8266SSDP/examples/SSDP.ino +++ b/libraries/ESP8266SSDP/examples/SSDP/SSDP.ino @@ -27,17 +27,17 @@ void setup() { HTTP.begin(); Serial.printf("Starting SSDP...\n"); - SSDP.begin(); - SSDP.setSchemaURL((char*)"description.xml"); + SSDP.setSchemaURL("description.xml"); SSDP.setHTTPPort(80); - SSDP.setName((char*)"Philips hue clone"); - SSDP.setSerialNumber((char*)"001788102201"); - SSDP.setURL((char*)"index.html"); - SSDP.setModelName((char*)"Philips hue bridge 2012"); - SSDP.setModelNumber((char*)"929000226503"); - SSDP.setModelURL((char*)"http://www.meethue.com"); - SSDP.setManufacturer((char*)"Royal Philips Electronics"); - SSDP.setManufacturerURL((char*)"http://www.philips.com"); + SSDP.setName("Philips hue clone"); + SSDP.setSerialNumber("001788102201"); + SSDP.setURL("index.html"); + SSDP.setModelName("Philips hue bridge 2012"); + SSDP.setModelNumber("929000226503"); + SSDP.setModelURL("http://www.meethue.com"); + SSDP.setManufacturer("Royal Philips Electronics"); + SSDP.setManufacturerURL("http://www.philips.com"); + SSDP.begin(); Serial.printf("Ready!\n"); } else { From 1b27b6760cc7514c8760d45b7f691545f5a22202 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 7 Jul 2015 17:39:13 +0300 Subject: [PATCH 39/53] Make SSDP event-driven --- libraries/ESP8266SSDP/ESP8266SSDP.cpp | 170 +++++++++++++------ libraries/ESP8266SSDP/ESP8266SSDP.h | 27 ++- libraries/ESP8266SSDP/examples/SSDP/SSDP.ino | 1 - 3 files changed, 140 insertions(+), 58 deletions(-) diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp index c381f7ff9..24997eee8 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.cpp +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -26,25 +26,36 @@ License (MIT license): */ #define LWIP_OPEN_SRC +#include #include "ESP8266SSDP.h" +#include "WiFiUdp.h" +#include "debug.h" extern "C" { + #include "osapi.h" + #include "ets_sys.h" #include "user_interface.h" - #include "mem.h" } -#include "lwip/ip_addr.h" -#include "lwip/igmp.h" -//#define DEBUG_SSDP Serial +#include "lwip/opt.h" +#include "lwip/udp.h" +#include "lwip/inet.h" +#include "lwip/igmp.h" +#include "lwip/mem.h" +#include "include/UdpContext.h" + +// #define DEBUG_SSDP Serial #define SSDP_INTERVAL 1200 #define SSDP_PORT 1900 #define SSDP_METHOD_SIZE 10 #define SSDP_URI_SIZE 2 #define SSDP_BUFFER_SIZE 64 - +#define SSDP_MULTICAST_TTL 1 static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); + + static const char* _ssdp_response_template = "HTTP/1.1 200 OK\r\n" "EXT:\r\n" @@ -108,7 +119,17 @@ static const char* _ssdp_schema_template = "\r\n" "\r\n"; -SSDPClass::SSDPClass(){ + +struct SSDPTimer { + ETSTimer timer; +}; + +SSDPClass::SSDPClass() : +_server(0), +_port(80), +_pending(false), +_timer(new SSDPTimer) +{ _uuid[0] = '\0'; _modelNumber[0] = '\0'; _friendlyName[0] = '\0'; @@ -119,65 +140,95 @@ SSDPClass::SSDPClass(){ _manufacturer[0] = '\0'; _manufacturerURL[0] = '\0'; sprintf(_schemaURL, "ssdp/schema.xml"); - _port = 80; - _pending = false; } SSDPClass::~SSDPClass(){ + delete _timer; } -void SSDPClass::begin(){ - ip_addr_t ifaddr; - ip_addr_t multicast_addr; - +bool SSDPClass::begin(){ _pending = false; - - ifaddr.addr = WiFi.localIP(); - multicast_addr.addr = (uint32_t) SSDP_MULTICAST_ADDR; - igmp_joingroup(&ifaddr, &multicast_addr); - - uint8_t mac[6]; - WiFi.macAddress(mac); + uint32_t chipId = ESP.getChipId(); - sprintf(_uuid, "38323636-4558-%04x-%04x-%02x%02x%02x%02x%02x%02x", - (chipId >> 16) & 0xFFFF, chipId & 0xFFFF, - mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] - ); + sprintf(_uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x", + (uint16_t) ((chipId >> 16) & 0xff), + (uint16_t) ((chipId >> 8) & 0xff), + (uint16_t) chipId & 0xff ); + #ifdef DEBUG_SSDP DEBUG_SSDP.printf("SSDP UUID: %s\n", (char *)_uuid); #endif - _server.begin(SSDP_PORT); + + if (_server) { + _server->unref(); + _server = 0; + } + + _server = new UdpContext; + _server->ref(); + + ip_addr_t ifaddr; + ifaddr.addr = WiFi.localIP(); + ip_addr_t multicast_addr; + multicast_addr.addr = (uint32_t) SSDP_MULTICAST_ADDR; + if (igmp_joingroup(&ifaddr, &multicast_addr) != ERR_OK ) { + DEBUGV("SSDP failed to join igmp group"); + return false; + } + + if (!_server->listen(*IP_ADDR_ANY, SSDP_PORT)) { + return false; + } + + _server->setMulticastInterface(ifaddr); + _server->setMulticastTTL(SSDP_MULTICAST_TTL); + _server->onRx(std::bind(&SSDPClass::_update, this)); + if (!_server->connect(multicast_addr, SSDP_PORT)) { + return false; + } + + _startTimer(); + + return true; } void SSDPClass::_send(ssdp_method_t method){ -#ifdef DEBUG_SSDP - if(method == NONE){ - DEBUG_SSDP.print("Sending Response to "); - DEBUG_SSDP.print(_server.remoteIP()); - DEBUG_SSDP.print(":"); - DEBUG_SSDP.println(_server.remotePort()); - }else if(method == NOTIFY){ - DEBUG_SSDP.println("Sending Notify to 239.255.255.250:1900"); - } -#endif - - if(method == NONE){ - _server.beginPacket(_server.remoteIP(), _server.remotePort()); - } else { - _server.beginPacket(SSDP_MULTICAST_ADDR, SSDP_PORT); - } - + char buffer[1460]; uint32_t ip = WiFi.localIP(); - _server.printf(_ssdp_packet_template, + int len = snprintf(buffer, sizeof(buffer), + _ssdp_packet_template, (method == NONE)?_ssdp_response_template:_ssdp_notify_template, SSDP_INTERVAL, _modelName, _modelNumber, _uuid, IP2STR(&ip), _port, _schemaURL ); - - _server.endPacket(); + + _server->append(buffer, len); + + ip_addr_t remoteAddr; + uint16_t remotePort; + if(method == NONE) { + remoteAddr.addr = _respondToAddr; + remotePort = _respondToPort; +#ifdef DEBUG_SSDP + DEBUG_SSDP.print("Sending Response to "); +#endif + } else { + remoteAddr.addr = SSDP_MULTICAST_ADDR; + remotePort = SSDP_PORT; +#ifdef DEBUG_SSDP + DEBUG_SSDP.println("Sending Notify to "); +#endif + } +#ifdef DEBUG_SSDP + DEBUG_SSDP.print(IPAddress(remoteAddr.addr)); + DEBUG_SSDP.print(":"); + DEBUG_SSDP.println(remotePort); +#endif + + _server->send(&remoteAddr, remotePort); } void SSDPClass::schema(WiFiClient client){ @@ -196,10 +247,13 @@ void SSDPClass::schema(WiFiClient client){ ); } -uint8_t SSDPClass::update(){ - if(!_pending && _server.parsePacket() > 0){ +void SSDPClass::_update(){ + if(!_pending && _server->next()) { ssdp_method_t method = NONE; + _respondToAddr = _server->getRemoteAddress(); + _respondToPort = _server->getRemotePort(); + typedef enum {METHOD, URI, PROTO, KEY, VALUE, ABORT} states; states state = METHOD; @@ -211,8 +265,8 @@ uint8_t SSDPClass::update(){ char buffer[SSDP_BUFFER_SIZE] = {0}; - while(_server.available() > 0){ - char c = _server.read(); + while(_server->getSize() > 0){ + char c = _server->read(); (c == '\r' || c == '\n') ? cr++ : cr = 0; @@ -280,8 +334,6 @@ uint8_t SSDPClass::update(){ break; } } - - _server.flush(); } if(_pending && (millis() - _process_time) > _delay){ @@ -291,6 +343,12 @@ uint8_t SSDPClass::update(){ _notify_time = millis(); _send(NOTIFY); } + + if (_pending) { + while (_server->next()) + _server->flush(); + } + } void SSDPClass::setSchemaURL(const char *url){ @@ -333,4 +391,16 @@ void SSDPClass::setManufacturerURL(const char *url){ strlcpy(_manufacturerURL, url, sizeof(_manufacturerURL)); } +void SSDPClass::_onTimerStatic(SSDPClass* self) { + self->_update(); +} + +void SSDPClass::_startTimer() { + ETSTimer* tm = &(_timer->timer); + const int interval = 1000; + os_timer_disarm(tm); + os_timer_setfn(tm, reinterpret_cast(&SSDPClass::_onTimerStatic), reinterpret_cast(this)); + os_timer_arm(tm, interval, 1 /* repeat */); +} + SSDPClass SSDP; diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.h b/libraries/ESP8266SSDP/ESP8266SSDP.h index 44e8b4423..8f6e280f3 100644 --- a/libraries/ESP8266SSDP/ESP8266SSDP.h +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -33,6 +33,8 @@ License (MIT license): #include #include +class UdpContext; + #define SSDP_UUID_SIZE 37 #define SSDP_SCHEMA_URL_SIZE 64 #define SSDP_FRIENDLY_NAME_SIZE 64 @@ -50,13 +52,16 @@ typedef enum { NOTIFY } ssdp_method_t; + +struct SSDPTimer; + class SSDPClass{ public: SSDPClass(); ~SSDPClass(); - void begin(); - uint8_t update(); + bool begin(); + void schema(WiFiClient client); void setName(const String& name) { setName(name.c_str()); } @@ -78,9 +83,19 @@ class SSDPClass{ void setManufacturerURL(const String& url) { setManufacturerURL(url.c_str()); } void setManufacturerURL(const char *url); void setHTTPPort(uint16_t port); - - private: - WiFiUDP _server; + + protected: + void _send(ssdp_method_t method); + void _update(); + void _startTimer(); + static void _onTimerStatic(SSDPClass* self); + + UdpContext* _server; + SSDPTimer* _timer; + + IPAddress _respondToAddr; + uint16_t _respondToPort; + bool _pending; unsigned short _delay; unsigned long _process_time; @@ -97,8 +112,6 @@ class SSDPClass{ char _modelName[SSDP_MODEL_NAME_SIZE]; char _modelURL[SSDP_MODEL_URL_SIZE]; char _modelNumber[SSDP_MODEL_VERSION_SIZE]; - - void _send(ssdp_method_t method); }; extern SSDPClass SSDP; diff --git a/libraries/ESP8266SSDP/examples/SSDP/SSDP.ino b/libraries/ESP8266SSDP/examples/SSDP/SSDP.ino index be4c0791e..a1f55a848 100644 --- a/libraries/ESP8266SSDP/examples/SSDP/SSDP.ino +++ b/libraries/ESP8266SSDP/examples/SSDP/SSDP.ino @@ -48,6 +48,5 @@ void setup() { void loop() { HTTP.handleClient(); - SSDP.update(); delay(1); } From 1be16c74c78054ca81811d58a993366a648ae247 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 7 Jul 2015 17:58:49 +0300 Subject: [PATCH 40/53] Remove update keyword --- libraries/ESP8266SSDP/keywords.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/ESP8266SSDP/keywords.txt b/libraries/ESP8266SSDP/keywords.txt index a01ce3dba..241d34145 100644 --- a/libraries/ESP8266SSDP/keywords.txt +++ b/libraries/ESP8266SSDP/keywords.txt @@ -14,7 +14,6 @@ SSDP KEYWORD1 ####################################### begin KEYWORD2 -update KEYWORD2 schema KEYWORD2 setName KEYWORD2 setURL KEYWORD2 From 198fbb88955d17faf1e2cfa23e780d38948c565f Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 14:27:24 +0300 Subject: [PATCH 41/53] travis update --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 282017bad..d4fa03347 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: true language: java jdk: - - openjdk6 + - oraclejdk8 script: - sudo apt-get update -qq From cab51097999896c303cd215322bed972b4c6f6ea Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 14:49:31 +0300 Subject: [PATCH 42/53] try dual OS build --- .travis.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d4fa03347..01f1e10ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,16 @@ sudo: true language: java +os: + - linux + - osx + jdk: - oraclejdk8 script: - - sudo apt-get update -qq - - sudo apt-get install -qq ant + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get update -qq; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install -qq ant; fi - pushd build - echo "" | ant dist - popd From 1694380caa6c4251e0d7cdbfa31d7ccc96b31ad1 Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 20:13:00 +0300 Subject: [PATCH 43/53] try to get plain json or string data --- libraries/ESP8266WebServer/src/Parsing.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266WebServer/src/Parsing.cpp b/libraries/ESP8266WebServer/src/Parsing.cpp index 40a58d24a..2db5ebcfe 100644 --- a/libraries/ESP8266WebServer/src/Parsing.cpp +++ b/libraries/ESP8266WebServer/src/Parsing.cpp @@ -108,7 +108,12 @@ bool ESP8266WebServer::_parseRequest(WiFiClient& client) { if (!isForm){ if (searchStr != "") searchStr += '&'; - searchStr += client.readStringUntil('\r'); + String bodyLine = client.readStringUntil('\r'); + if(bodyLine.startsWith('{') || bodyLine.startsWith('[') || bodyLine.indexOf('=') == -1){ + //plain post json or other data + searchStr += "plain"; + } + searchStr += bodyLine; client.readStringUntil('\n'); } _parseArguments(searchStr); From 3c819e998bdc940c878c9c8cb50987cbb6f91d47 Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 20:22:32 +0300 Subject: [PATCH 44/53] duh... --- libraries/ESP8266WebServer/src/Parsing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266WebServer/src/Parsing.cpp b/libraries/ESP8266WebServer/src/Parsing.cpp index 2db5ebcfe..e91a94f12 100644 --- a/libraries/ESP8266WebServer/src/Parsing.cpp +++ b/libraries/ESP8266WebServer/src/Parsing.cpp @@ -109,7 +109,7 @@ bool ESP8266WebServer::_parseRequest(WiFiClient& client) { if (!isForm){ if (searchStr != "") searchStr += '&'; String bodyLine = client.readStringUntil('\r'); - if(bodyLine.startsWith('{') || bodyLine.startsWith('[') || bodyLine.indexOf('=') == -1){ + if(bodyLine.startsWith("{") || bodyLine.startsWith("[") || bodyLine.indexOf('=') == -1){ //plain post json or other data searchStr += "plain"; } From f7212154a6028821599f85bba1c91af95dde2734 Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 21:19:22 +0300 Subject: [PATCH 45/53] missingg the equal sign --- libraries/ESP8266WebServer/src/Parsing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266WebServer/src/Parsing.cpp b/libraries/ESP8266WebServer/src/Parsing.cpp index e91a94f12..b3d722592 100644 --- a/libraries/ESP8266WebServer/src/Parsing.cpp +++ b/libraries/ESP8266WebServer/src/Parsing.cpp @@ -111,7 +111,7 @@ bool ESP8266WebServer::_parseRequest(WiFiClient& client) { String bodyLine = client.readStringUntil('\r'); if(bodyLine.startsWith("{") || bodyLine.startsWith("[") || bodyLine.indexOf('=') == -1){ //plain post json or other data - searchStr += "plain"; + searchStr += "plain="; } searchStr += bodyLine; client.readStringUntil('\n'); From cca89efbd2468b5a453b610706255c40105f3a6c Mon Sep 17 00:00:00 2001 From: John Doe Date: Sun, 5 Jul 2015 21:43:52 +0300 Subject: [PATCH 46/53] lets make it possible to parse multiline plain post --- libraries/ESP8266WebServer/src/Parsing.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266WebServer/src/Parsing.cpp b/libraries/ESP8266WebServer/src/Parsing.cpp index b3d722592..88877372a 100644 --- a/libraries/ESP8266WebServer/src/Parsing.cpp +++ b/libraries/ESP8266WebServer/src/Parsing.cpp @@ -109,12 +109,19 @@ bool ESP8266WebServer::_parseRequest(WiFiClient& client) { if (!isForm){ if (searchStr != "") searchStr += '&'; String bodyLine = client.readStringUntil('\r'); +#ifdef DEBUG + DEBUG_OUTPUT.print("Plain: "); + DEBUG_OUTPUT.println(bodyLine); +#endif if(bodyLine.startsWith("{") || bodyLine.startsWith("[") || bodyLine.indexOf('=') == -1){ //plain post json or other data searchStr += "plain="; + searchStr += bodyLine; + searchStr += client.readString(); + } else { + searchStr += bodyLine; + client.readStringUntil('\n'); } - searchStr += bodyLine; - client.readStringUntil('\n'); } _parseArguments(searchStr); if (isForm){ From 715d4ab722aa3cf75b43ceca84b68da9e37b4ffe Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Wed, 1 Jul 2015 17:53:49 +0200 Subject: [PATCH 47/53] disable DEBUG_HTTP_UPDATE --- libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h index 0456997b0..911a6a565 100644 --- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h +++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h @@ -29,7 +29,7 @@ #include #include -#define DEBUG_HTTP_UPDATE(...) Serial1.printf( __VA_ARGS__ ) +//#define DEBUG_HTTP_UPDATE(...) Serial1.printf( __VA_ARGS__ ) #ifndef DEBUG_HTTP_UPDATE #define DEBUG_HTTP_UPDATE(...) From ddf03fc92b8d1e2997b3ba65d039130197a6aa0f Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Wed, 1 Jul 2015 17:56:19 +0200 Subject: [PATCH 48/53] Pulldown only possible for in 16. ( see #478 ) rename define to INPUT_PULLDOWN_16 to make it clear --- cores/esp8266/Arduino.h | 2 +- cores/esp8266/core_esp8266_wiring_digital.c | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cores/esp8266/Arduino.h b/cores/esp8266/Arduino.h index 61e5711a4..c712bf756 100644 --- a/cores/esp8266/Arduino.h +++ b/cores/esp8266/Arduino.h @@ -48,7 +48,7 @@ void yield(void); //GPIO FUNCTIONS #define INPUT 0x00 #define INPUT_PULLUP 0x02 -#define INPUT_PULLDOWN 0x04 +#define INPUT_PULLDOWN_16 0x04 // PULLDOWN only possible for pin16 #define OUTPUT 0x01 #define OUTPUT_OPEN_DRAIN 0x03 #define WAKEUP_PULLUP 0x05 diff --git a/cores/esp8266/core_esp8266_wiring_digital.c b/cores/esp8266/core_esp8266_wiring_digital.c index aad2dfa3e..5654e5f12 100644 --- a/cores/esp8266/core_esp8266_wiring_digital.c +++ b/cores/esp8266/core_esp8266_wiring_digital.c @@ -44,14 +44,12 @@ extern void __pinMode(uint8_t pin, uint8_t mode) { GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) if(mode == OUTPUT_OPEN_DRAIN) GPC(pin) |= (1 << GPCD); GPES = (1 << pin); //Enable - } else if(mode == INPUT || mode == INPUT_PULLUP || mode == INPUT_PULLDOWN){ + } else if(mode == INPUT || mode == INPUT_PULLUP){ GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO GPEC = (1 << pin); //Disable GPC(pin) = (GPC(pin) & (0xF << GPCI)) | (1 << GPCD); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) if(mode == INPUT_PULLUP) { GPF(pin) |= (1 << GPFPU); // Enable Pullup - } else if(mode == INPUT_PULLDOWN) { - GPF(pin) |= (1 << GPFPD); // Enable Pulldown } } else if(mode == WAKEUP_PULLUP || mode == WAKEUP_PULLDOWN){ GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO @@ -67,8 +65,8 @@ extern void __pinMode(uint8_t pin, uint8_t mode) { } else if(pin == 16){ GPF16 = GP16FFS(GPFFS_GPIO(pin));//Set mode to GPIO GPC16 = 0; - if(mode == INPUT || mode == INPUT_PULLDOWN){ - if(mode == INPUT_PULLDOWN){ + if(mode == INPUT || mode == INPUT_PULLDOWN_16){ + if(mode == INPUT_PULLDOWN_16){ GPF16 |= (1 << GP16FPD);//Enable Pulldown } GP16E &= ~1; From 39883f5ea82004e6f7ba7784eec9c14ebdf9740e Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Wed, 1 Jul 2015 17:57:31 +0200 Subject: [PATCH 49/53] upate phy with values from SDK 1.1.2 (esp_init_data_default.bin) --- cores/esp8266/core_esp8266_phy.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/core_esp8266_phy.c b/cores/esp8266/core_esp8266_phy.c index c22272df7..cc624ade0 100644 --- a/cores/esp8266/core_esp8266_phy.c +++ b/cores/esp8266/core_esp8266_phy.c @@ -56,8 +56,8 @@ static uint8_t phy_init_data[128] = [26] = 225, // spur_freq_cfg, spur_freq=spur_freq_cfg/spur_freq_cfg_div [27] = 10, // spur_freq_cfg_div // each bit for 1 channel, 1 to select the spur_freq if in band, else 40 - [28] = 0, // spur_freq_en_h - [29] = 0, // spur_freq_en_l + [28] = 0xff, // spur_freq_en_h + [29] = 0xff, // spur_freq_en_l [30] = 0xf8, // Reserved, do not change [31] = 0, // Reserved, do not change @@ -86,7 +86,7 @@ static uint8_t phy_init_data[128] = // 2: 24MHz [48] = 1, - + // sdio_configure // 0: Auto by pin strapping @@ -165,7 +165,7 @@ static uint8_t phy_init_data[128] = // 0x8: -14db, // 0x4: -17.5, // 0x0: -23 - [94] = 0x0f, + [94] = 0x00, // lp_bb_att_ext @@ -226,7 +226,7 @@ static uint8_t phy_init_data[128] = // 3: auto measure frequency offset and correct it, bbpll is 160M, it only can correct + frequency offset. // 5: use 113 byte force_freq_offset to correct frequency offset, bbpll is 168M, it can correct + and - frequency offset. // 7: use 113 byte force_freq_offset to correct frequency offset, bbpll is 160M , it only can correct + frequency offset. - [112] = 0, + [112] = 3, // force_freq_offset // signed, unit is 8kHz From d4ddb66fc468f10e0b710e5cdbfb87641cf24032 Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Sat, 4 Jul 2015 09:45:05 +0200 Subject: [PATCH 50/53] update SDK to v1.2.0_15_07_03 --- cores/esp8266/Esp.cpp | 7 +++ libraries/ESP8266WiFi/src/ESP8266WiFi.cpp | 2 +- platform.txt | 2 +- tools/sdk/changelog.txt | 59 ++++++++++++++++++++++ tools/sdk/include/eagle_soc.h | 8 +-- tools/sdk/include/espnow.h | 47 +++++++++++++++++ tools/sdk/include/ets_sys.h | 13 +++-- tools/sdk/include/pwm.h | 34 +++++++++++++ tools/sdk/include/smartconfig.h | 2 +- tools/sdk/include/sntp.h | 4 ++ tools/sdk/include/user_interface.h | 28 ++++++++++ tools/sdk/lib/libat.a | Bin 0 -> 205586 bytes tools/sdk/lib/libcrypto.a | Bin 0 -> 55094 bytes tools/sdk/lib/libespnow.a | Bin 0 -> 26732 bytes tools/sdk/lib/libjson.a | Bin 12818 -> 12934 bytes tools/sdk/lib/liblwip.a | Bin 304810 -> 311536 bytes tools/sdk/lib/liblwip_536.a | Bin 0 -> 311524 bytes tools/sdk/lib/libmain.a | Bin 142350 -> 146252 bytes tools/sdk/lib/libnet80211.a | Bin 195978 -> 221490 bytes tools/sdk/lib/libpp.a | Bin 190304 -> 213568 bytes tools/sdk/lib/libpwm.a | Bin 0 -> 28298 bytes tools/sdk/lib/libsmartconfig.a | Bin 98570 -> 101094 bytes tools/sdk/lib/libssl.a | Bin 171696 -> 172970 bytes tools/sdk/lib/libupgrade.a | Bin 17350 -> 17358 bytes tools/sdk/lib/libwpa.a | Bin 124548 -> 127178 bytes tools/sdk/lib/libwps.a | Bin 0 -> 213960 bytes tools/sdk/version | 2 +- 27 files changed, 193 insertions(+), 15 deletions(-) create mode 100644 tools/sdk/include/espnow.h create mode 100644 tools/sdk/include/pwm.h create mode 100644 tools/sdk/lib/libat.a create mode 100644 tools/sdk/lib/libcrypto.a create mode 100644 tools/sdk/lib/libespnow.a create mode 100644 tools/sdk/lib/liblwip_536.a create mode 100644 tools/sdk/lib/libpwm.a create mode 100644 tools/sdk/lib/libwps.a diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 4e0b75106..cd3b16f6a 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -81,18 +81,25 @@ EspClass ESP; void EspClass::wdtEnable(uint32_t timeout_ms) { + /// This API can only be called if software watchdog is stopped + system_soft_wdt_restart(); } void EspClass::wdtEnable(WDTO_t timeout_ms) { + wdtEnable((uint32_t) timeout_ms); } void EspClass::wdtDisable(void) { + /// Please don’t stop software watchdog too long (less than 6 seconds), + /// otherwise it will trigger hardware watchdog reset. + system_soft_wdt_stop(); } void EspClass::wdtFeed(void) { + } void EspClass::deepSleep(uint32_t time_us, WakeMode mode) diff --git a/libraries/ESP8266WiFi/src/ESP8266WiFi.cpp b/libraries/ESP8266WiFi/src/ESP8266WiFi.cpp index f65907fc9..474f4f301 100644 --- a/libraries/ESP8266WiFi/src/ESP8266WiFi.cpp +++ b/libraries/ESP8266WiFi/src/ESP8266WiFi.cpp @@ -599,7 +599,7 @@ void ESP8266WiFiClass::beginSmartConfig() _smartConfigDone = false; //SC_TYPE_ESPTOUCH use ESPTOUCH for smartconfig, or use SC_TYPE_AIRKISS for AIRKISS - smartconfig_start(SC_TYPE_ESPTOUCH, reinterpret_cast(&ESP8266WiFiClass::_smartConfigCallback), 1); + smartconfig_start(reinterpret_cast(&ESP8266WiFiClass::_smartConfigCallback), 1); } void ESP8266WiFiClass::stopSmartConfig() diff --git a/platform.txt b/platform.txt index 169dd96ad..14af50165 100644 --- a/platform.txt +++ b/platform.txt @@ -24,7 +24,7 @@ compiler.S.flags=-c -g -x assembler-with-cpp -MMD compiler.c.elf.flags=-g -Os -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static "-L{compiler.sdk.path}/lib" "-L{compiler.sdk.path}/ld" "-T{build.flash_ld}" -Wl,-wrap,system_restart_local -Wl,-wrap,register_chipv6_phy compiler.c.elf.cmd=xtensa-lx106-elf-gcc -compiler.c.elf.libs=-lm -lgcc -lhal -lphy -lnet80211 -llwip -lwpa -lmain -lpp -lsmartconfig +compiler.c.elf.libs=-lm -lgcc -lhal -lphy -lnet80211 -llwip -lwpa -lmain -lpp -lsmartconfig -lwps compiler.cpp.cmd=xtensa-lx106-elf-g++ compiler.cpp.flags=-c -Os -g -mlongcalls -mtext-section-literals -fno-exceptions -fno-rtti -falign-functions=4 -std=c++11 -MMD diff --git a/tools/sdk/changelog.txt b/tools/sdk/changelog.txt index c9e1cdf11..5cd5b9cc2 100644 --- a/tools/sdk/changelog.txt +++ b/tools/sdk/changelog.txt @@ -1,3 +1,62 @@ +esp_iot_sdk_v1.2.0_15_07_03 Release Note +------------------------------------------- +Resolved Issues(Bugs below are eligible for Bug Bounty Program): +1.TLS server disconnect to ESP8266 may cause crash. [孙新虎] + +Optimization: +1.Update SmartConfig to version 2.4 , corresponding to ESPTOUCH APP v0.3.4 (https://github.com/EspressifApp/), delete parameter "sc_type type" in smartconfig_start, SmartConfig type can be got automatically. +2.Add parameter "sint16 freq_offset; " in structure "bss_info" to get AP's frequency offset. +3.Folder "ld" is updated, please use the latest one (\esp_iot_sdk_v1.2.0\ld ) +4.Add UDP transparent transmission example in documentation "4B-ESP8266__AT Command Examples" +5.Revise the scan issue that may cause Wi-Fi connection break. +6.Add ESP-NOW function, more details in "Add APIs" +7.Add WPS function,more details in "Add APIs" +8.Fixed a DNS fail issue with special router +9.Optimize espconn,revise issues below: +(1) enter sent callback late in UDP transmission +(2) TCP shakehand may fail issue +(3) SSL connection fail may cause crash +(4) optimize SSL error handler +10. Memory optimization + +Add APIs: +1.ESP-NOW APIs +esp_now_init: init ESP-NOW function +esp_now_deinit: deinit ESP-NOW function +esp_now_register_recv_cb: register ESP-NOW receive callback +esp_now_unregister_recv_cb: unregister ESP-NOW receive callback +esp_now_send: send ESP-NOW packet +esp_now_add_peer: add an ESP-NOW peer +esp_now_del_peer: delete an ESP-NOW peer +esp_now_set_self_role: set ESP-NOW role of device itself +esp_now_get_self_role: get ESP-NOW role of device itself +esp_now_set_peer_role: set ESP-NOW role about another device +esp_now_get_peer_role: get ESP-NOW role about another device +esp_now_set_peer_key: set ESP-NOW key of a device +esp_now_get_peer_key: get ESP-NOW key of a device + +2. WPS APIs +wifi_wps_enable : enable WPS function +wifi_wps_disable: disable WPS function +wifi_wps_start: start WPS communication +wifi_set_wps_cb: set WPS callback + +3.software watchdog APIs +system_soft_wdt_stop: stop software watchdog +system_soft_wdt_restart: restart software watchdog + +4.sntp_get_timezone: get SNTP timezone + +AT_v0.30 Release Note: +Note: For AT firmware to support FOTA, flash size need to be 1024KB or more than that. + +1.Command "AT+CWSTARTSMART" need not parameter any more, SmartConfig type can be got automatically. +2.AP's frequency offset can be got by command "AT+CWLAP" +3.Memory optimization + + + + esp_iot_sdk_1.1.2_15_06_25_p2 Release Note ------------------------------------------- diff --git a/tools/sdk/include/eagle_soc.h b/tools/sdk/include/eagle_soc.h index 71e32dc2e..04e033067 100644 --- a/tools/sdk/include/eagle_soc.h +++ b/tools/sdk/include/eagle_soc.h @@ -300,11 +300,11 @@ #define PIN_PULLUP_DIS(PIN_NAME) CLEAR_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLUP) #define PIN_PULLUP_EN(PIN_NAME) SET_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLUP) -#define PIN_PULLDWN_DIS(PIN_NAME) CLEAR_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLDWN) -#define PIN_PULLDWN_EN(PIN_NAME) SET_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLDWN) #define PIN_FUNC_SELECT(PIN_NAME, FUNC) do { \ - CLEAR_PERI_REG_MASK(PIN_NAME, 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