diff --git a/.travis.yml b/.travis.yml index 282017bad..01f1e10ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,16 @@ sudo: true language: java +os: + - linux + - osx + jdk: - - openjdk6 + - 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 diff --git a/README.md b/README.md index 74b1372ad..3a43e519b 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 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. #### Progmem #### 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/bootloaders/eboot/Makefile b/bootloaders/eboot/Makefile index 0872ee35f..7a07d7615 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 := ./ @@ -6,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 0b74fcdf0..cbc87044b 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('0'+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('0'+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 97e25c146..08536aa72 100755 Binary files a/bootloaders/eboot/eboot.elf and b/bootloaders/eboot/eboot.elf differ diff --git a/bootloaders/eboot/flash.c b/bootloaders/eboot/flash.c index f90e25b34..8359e168b 100644 --- a/bootloaders/eboot/flash.c +++ b/bootloaders/eboot/flash.c @@ -4,7 +4,7 @@ * Redistribution and use is permitted according to the conditions of the * 3-clause BSD license to be found in the LICENSE file. */ - + #include #include #include diff --git a/cores/esp8266/Arduino.h b/cores/esp8266/Arduino.h index 59135a034..c712bf756 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 7971f1fd3..cd3b16f6a 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 /** @@ -365,83 +365,38 @@ uint32_t EspClass::getFreeSketchSpace() { return freeSpaceEnd - freeSpaceStart; } -bool EspClass::updateSketch(Stream& in, uint32_t size) { - - if (size > getFreeSketchSpace()) - 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) - 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.println("stream read failed"); -#endif - return false; - } - - if(addr == freeSpaceStart) { - // check for valid first magic byte - if(*((uint8 *) buffer.get()) != 0xE9) { - return false; - } - } - - noInterrupts(); - rc = SPIWrite(addr, buffer.get(), willRead); - interrupts(); - if (rc) { -#ifdef DEBUG_SERIAL - DEBUG_SERIAL.println("write failed"); -#endif - 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 80555d591..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 updateSketch(Stream& in, uint32_t size, bool restartOnFail = false, bool restartOnSuccess = true); String getResetInfo(); struct rst_info * getResetInfoPtr(); 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/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp new file mode 100644 index 000000000..4d9dca9e2 --- /dev/null +++ b/cores/esp8266/Updater.cpp @@ -0,0 +1,209 @@ +#include "Updater.h" +#include "Arduino.h" +#include "eboot_command.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) +{ +} + +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){ +#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; + } + + _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 + 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 + uint32_t updateStartAddress = updateEndAddress - roundedSize; + + //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; + } + + //initialize + _startAddress = updateStartAddress; + _currentAddress = _startAddress; + _size = size; + _buffer = new uint8_t[FLASH_SECTOR_SIZE]; + + return true; +} + +bool UpdaterClass::end(bool evenIfRemaining){ + if(_size == 0){ +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.println("no update"); +#endif + return false; + } + + if(hasError() || (!isFinished() && !evenIfRemaining)){ +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.printf("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size); +#endif + + _reset(); + return false; + } + + if(evenIfRemaining){ + if(_bufferLen > 0){ + _writeBuffer(); + } + _size = progress(); + } + + eboot_command ebcmd; + ebcmd.action = ACTION_COPY_RAW; + ebcmd.args[0] = _startAddress; + ebcmd.args[1] = 0x00000; + ebcmd.args[2] = _size; + eboot_command_write(&ebcmd); + +#ifdef DEBUG_UPDATER + DEBUG_UPDATER.printf("Staged: address:0x%08X, size:0x%08X\n", _startAddress, _size); +#endif + + _reset(); + return true; +} + +bool UpdaterClass::_writeBuffer(){ + noInterrupts(); + int rc = SPIEraseSector(_currentAddress/FLASH_SECTOR_SIZE); + if (!rc) { + rc = SPIWrite(_currentAddress, _buffer, _bufferLen); + } + interrupts(); + if (rc) { + _error = UPDATE_ERROR_WRITE; + _currentAddress = (_startAddress + _size); +#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() || !isRunning()) + 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); + _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() || !isRunning()) + return 0; + + while(remaining()) { + toRead = FLASH_SECTOR_SIZE - _bufferLen; + toRead = data.readBytes(_buffer + _bufferLen, toRead); + if(toRead == 0){ //Timeout + _error = UPDATE_ERROR_STREAM; + _currentAddress = (_startAddress + _size); +#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..be1a04dd6 --- /dev/null +++ b/cores/esp8266/Updater.h @@ -0,0 +1,121 @@ +#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 the space needed for the update + Will return false if there is not enough space + */ + 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 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 + 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 and !evenIfRemainanig + 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); + + /* + 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 _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 writeStream method + writes only what is available + */ + template + size_t write(T &data){ + size_t written = 0; + if (hasError() || !isRunning()) + 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: + void _reset(); + bool _writeBuffer(); + + uint8_t *_buffer; + size_t _bufferLen; + size_t _size; + uint32_t _startAddress; + uint32_t _currentAddress; + uint8_t _error; +}; + +extern UpdaterClass Update; + +#endif diff --git a/cores/esp8266/core_esp8266_noniso.c b/cores/esp8266/core_esp8266_noniso.c index eafe4fa72..eddac25a5 100644 --- a/cores/esp8266/core_esp8266_noniso.c +++ b/cores/esp8266/core_esp8266_noniso.c @@ -23,11 +23,11 @@ */ #include +#include #include "stdlib_noniso.h" #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 67ed0dbef..84ba411e0 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; @@ -126,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) { @@ -160,13 +124,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; } @@ -476,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 + */ + diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp new file mode 100644 index 000000000..24997eee8 --- /dev/null +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -0,0 +1,406 @@ +/* +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. + +*/ +#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 "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" + "ST: upnp:rootdevice\r\n"; + +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"; + +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 + "USN: uuid:%s\r\n" // _uuid + "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL + "\r\n"; + +static 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" + "" + "http://%u.%u.%u.%u:%u/" // WiFi.localIP(), _port + "" + "urn:schemas-upnp-org:device:Basic:1" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "uuid:%s" + "" +// "" +// "" +// "image/png" +// "48" +// "48" +// "24" +// "icon48.png" +// "" +// "" +// "image/png" +// "120" +// "120" +// "24" +// "icon120.png" +// "" +// "" + "\r\n" + "\r\n"; + + +struct SSDPTimer { + ETSTimer timer; +}; + +SSDPClass::SSDPClass() : +_server(0), +_port(80), +_pending(false), +_timer(new SSDPTimer) +{ + _uuid[0] = '\0'; + _modelNumber[0] = '\0'; + _friendlyName[0] = '\0'; + _presentationURL[0] = '\0'; + _serialNumber[0] = '\0'; + _modelName[0] = '\0'; + _modelURL[0] = '\0'; + _manufacturer[0] = '\0'; + _manufacturerURL[0] = '\0'; + sprintf(_schemaURL, "ssdp/schema.xml"); +} + +SSDPClass::~SSDPClass(){ + delete _timer; +} + +bool SSDPClass::begin(){ + _pending = false; + + uint32_t chipId = ESP.getChipId(); + 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 + + 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){ + char buffer[1460]; + uint32_t ip = WiFi.localIP(); + + 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->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){ + uint32_t ip = WiFi.localIP(); + client.printf(_ssdp_schema_template, + IP2STR(&ip), _port, + _friendlyName, + _presentationURL, + _serialNumber, + _modelName, + _modelNumber, + _modelURL, + _manufacturer, + _manufacturerURL, + _uuid + ); +} + +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; + + 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->getSize() > 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; + } + } + } + + 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); + } + + if (_pending) { + while (_server->next()) + _server->flush(); + } + +} + +void SSDPClass::setSchemaURL(const char *url){ + strlcpy(_schemaURL, url, sizeof(_schemaURL)); +} + +void SSDPClass::setHTTPPort(uint16_t port){ + _port = port; +} + +void SSDPClass::setName(const char *name){ + strlcpy(_friendlyName, name, sizeof(_friendlyName)); +} + +void SSDPClass::setURL(const char *url){ + strlcpy(_presentationURL, url, sizeof(_presentationURL)); +} + +void SSDPClass::setSerialNumber(const char *serialNumber){ + strlcpy(_serialNumber, serialNumber, sizeof(_serialNumber)); +} + +void SSDPClass::setModelName(const char *name){ + strlcpy(_modelName, name, sizeof(_modelName)); +} + +void SSDPClass::setModelNumber(const char *num){ + strlcpy(_modelNumber, num, sizeof(_modelNumber)); +} + +void SSDPClass::setModelURL(const char *url){ + strlcpy(_modelURL, url, sizeof(_modelURL)); +} + +void SSDPClass::setManufacturer(const char *name){ + strlcpy(_manufacturer, name, sizeof(_manufacturer)); +} + +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 new file mode 100644 index 000000000..8f6e280f3 --- /dev/null +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -0,0 +1,119 @@ +/* +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 + +class UdpContext; + +#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, + NOTIFY +} ssdp_method_t; + + +struct SSDPTimer; + +class SSDPClass{ + public: + SSDPClass(); + ~SSDPClass(); + + bool begin(); + + void schema(WiFiClient client); + + 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); + + 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; + unsigned long _notify_time; + + uint16_t _port; + 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]; +}; + +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/SSDP.ino b/libraries/ESP8266SSDP/examples/SSDP/SSDP.ino new file mode 100644 index 000000000..a1f55a848 --- /dev/null +++ b/libraries/ESP8266SSDP/examples/SSDP/SSDP.ino @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +const char* ssid = "************"; +const char* password = "***********"; + +ESP8266WebServer HTTP(80); + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println("Starting WiFi..."); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if(WiFi.waitForConnectResult() == WL_CONNECTED){ + + Serial.printf("Starting HTTP...\n"); + HTTP.on("/index.html", HTTP_GET, [](){ + HTTP.send(200, "text/plain", "Hello World!"); + }); + HTTP.on("/description.xml", HTTP_GET, [](){ + SSDP.schema(HTTP.client()); + }); + HTTP.begin(); + + Serial.printf("Starting SSDP...\n"); + SSDP.setSchemaURL("description.xml"); + SSDP.setHTTPPort(80); + 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 { + Serial.printf("WiFi Failed\n"); + while(1) delay(100); + } +} + +void loop() { + HTTP.handleClient(); + delay(1); +} diff --git a/libraries/ESP8266SSDP/keywords.txt b/libraries/ESP8266SSDP/keywords.txt new file mode 100644 index 000000000..241d34145 --- /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 +schema KEYWORD2 +setName KEYWORD2 +setURL KEYWORD2 +setHTTPPort KEYWORD2 +setSchemaURL 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 diff --git a/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino new file mode 100644 index 000000000..665dd6199 --- /dev/null +++ b/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino @@ -0,0 +1,73 @@ +/* + To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update +*/ + +#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); + WiFiUDP::stopAll(); + Serial.printf("Update: %s\n", upload.filename.c_str()); + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + if(!Update.begin(maxSketchSpace)){//start with max available size + Update.printError(Serial); + } + } else if(upload.status == UPLOAD_FILE_WRITE){ + if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){ + Update.printError(Serial); + } + } 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.printf("Ready! Open http://%s.local in your browser\n", host); + } else { + Serial.println("WiFi Failed"); + } +} + +void loop(void){ + server.handleClient(); + delay(1); +} diff --git a/libraries/ESP8266WebServer/src/Parsing.cpp b/libraries/ESP8266WebServer/src/Parsing.cpp index 40a58d24a..88877372a 100644 --- a/libraries/ESP8266WebServer/src/Parsing.cpp +++ b/libraries/ESP8266WebServer/src/Parsing.cpp @@ -108,8 +108,20 @@ bool ESP8266WebServer::_parseRequest(WiFiClient& client) { if (!isForm){ if (searchStr != "") searchStr += '&'; - searchStr += client.readStringUntil('\r'); - client.readStringUntil('\n'); + 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'); + } } _parseArguments(searchStr); if (isForm){ 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(); 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..6e50313a4 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" } @@ -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..e7dc97795 --- /dev/null +++ b/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino @@ -0,0 +1,98 @@ +#include +#include +#include + +const char* host = "esp8266-ota"; +const char* ssid = "**********"; +const char* pass = "**********"; +const uint16_t aport = 8266; + +WiFiServer TelnetServer(aport); +WiFiClient Telnet; +WiFiUDP OTA; + +void setup() { + Serial.begin(115200); + 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); + OTA.begin(aport); + TelnetServer.begin(); + TelnetServer.setNoDelay(true); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } +} + +void loop() { + //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(); + + WiFiUDP::stopAll(); + + if(!Update.begin(size)){ + Serial.println("Update Begin Error"); + return; + } + + 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(1); +} diff --git a/platform.txt b/platform.txt index 9b872c11e..7534d2075 100644 --- a/platform.txt +++ b/platform.txt @@ -93,3 +93,13 @@ 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=python "{path}/espota.py" "{serial.port}" "{network.port}" "{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}" 8266 "{build.path}/{build.project_name}.bin" diff --git a/tools/espota.py b/tools/espota.py new file mode 100755 index 000000000..dbd22eb45 --- /dev/null +++ b/tools/espota.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# +# this script will push an OTA update to the ESP +# use it like: python espota.py + +from __future__ import print_function +import socket +import sys +import os + +def serve(remoteAddr, remotePort, 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('Starting on %s:%s' % server_address, file=sys.stderr) + try: + sock.bind(server_address) + sock.listen(1) + except: + print('Listen Failed', file=sys.stderr) + return 1 + + content_size = os.path.getsize(filename) + print('Upload size: %d' % content_size, file=sys.stderr) + message = '%d %d %d\n' % (0, serverPort, content_size) + + # 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) + 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() + 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: + connection.settimeout(60) + data = connection.recv(32) + 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: + connection.close() + f.close() + + sock.close() + return 1 + +def main(args): + return serve(args[1], args[2], args[3]) + + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + 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. *