mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +03:00
commit
7891a84e8b
22
boards.txt
22
boards.txt
@ -1,7 +1,9 @@
|
|||||||
menu.UploadSpeed=Upload Speed
|
menu.UploadSpeed=Upload Speed
|
||||||
menu.CpuFrequency=CPU Frequency
|
menu.CpuFrequency=CPU Frequency
|
||||||
menu.FlashSize=Flash Size
|
menu.FlashSize=Flash Size
|
||||||
|
menu.FlashMode=Flash Mode
|
||||||
menu.FlashFreq=Flash Frequency
|
menu.FlashFreq=Flash Frequency
|
||||||
|
menu.UploadTool=Upload Using
|
||||||
|
|
||||||
##############################################################
|
##############################################################
|
||||||
generic.name=Generic ESP8266 Module
|
generic.name=Generic ESP8266 Module
|
||||||
@ -23,11 +25,26 @@ generic.build.variant=generic
|
|||||||
generic.build.flash_mode=qio
|
generic.build.flash_mode=qio
|
||||||
generic.build.spiffs_pagesize=256
|
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=80 MHz
|
||||||
generic.menu.CpuFrequency.80.build.f_cpu=80000000L
|
generic.menu.CpuFrequency.80.build.f_cpu=80000000L
|
||||||
generic.menu.CpuFrequency.160=160 MHz
|
generic.menu.CpuFrequency.160=160 MHz
|
||||||
generic.menu.CpuFrequency.160.build.f_cpu=160000000L
|
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=115200
|
||||||
generic.menu.UploadSpeed.115200.upload.speed=115200
|
generic.menu.UploadSpeed.115200.upload.speed=115200
|
||||||
generic.menu.UploadSpeed.9600=9600
|
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_end=0x1000000
|
||||||
# generic.menu.FlashSize.16M.build.spiffs_blocksize=8192
|
# 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)
|
modwifi.name=Olimex MOD-WIFI-ESP8266(-DEV)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
XTENSA_TOOLCHAIN ?=
|
XTENSA_TOOLCHAIN ?= ../../tools/xtensa-lx106-elf/bin/
|
||||||
|
ESPTOOL ?= ../../tools/esptool
|
||||||
|
|
||||||
BIN_DIR := ./
|
BIN_DIR := ./
|
||||||
TARGET_DIR := ./
|
TARGET_DIR := ./
|
||||||
@ -6,7 +7,7 @@ TARGET_DIR := ./
|
|||||||
TARGET_OBJ_FILES := \
|
TARGET_OBJ_FILES := \
|
||||||
eboot.o \
|
eboot.o \
|
||||||
eboot_command.o \
|
eboot_command.o \
|
||||||
flash.o \
|
|
||||||
|
|
||||||
TARGET_OBJ_PATHS := $(addprefix $(TARGET_DIR)/,$(TARGET_OBJ_FILES))
|
TARGET_OBJ_PATHS := $(addprefix $(TARGET_DIR)/,$(TARGET_OBJ_FILES))
|
||||||
|
|
||||||
|
@ -76,38 +76,31 @@ int copy_raw(const uint32_t src_addr,
|
|||||||
const uint32_t dst_addr,
|
const uint32_t dst_addr,
|
||||||
const uint32_t size)
|
const uint32_t size)
|
||||||
{
|
{
|
||||||
ets_putc('\n');
|
|
||||||
ets_putc('c');
|
|
||||||
ets_putc('p');
|
|
||||||
ets_putc('\n');
|
|
||||||
// require regions to be aligned
|
// require regions to be aligned
|
||||||
if (src_addr & 0xfff != 0 ||
|
if (src_addr & 0xfff != 0 ||
|
||||||
dst_addr & 0xfff != 0) {
|
dst_addr & 0xfff != 0) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SPIEraseAreaEx(dst_addr, size)) {
|
const uint32_t buffer_size = FLASH_SECTOR_SIZE;
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t buffer_size = 4096;
|
|
||||||
uint8_t buffer[buffer_size];
|
uint8_t buffer[buffer_size];
|
||||||
|
uint32_t left = ((size+buffer_size-1) & ~(buffer_size-1));
|
||||||
const uint32_t end = src_addr + size;
|
|
||||||
uint32_t saddr = src_addr;
|
uint32_t saddr = src_addr;
|
||||||
uint32_t daddr = dst_addr;
|
uint32_t daddr = dst_addr;
|
||||||
uint32_t left = size;
|
|
||||||
while (saddr < end) {
|
while (left) {
|
||||||
uint32_t will_copy = (left < buffer_size) ? left : buffer_size;
|
if (SPIEraseSector(daddr/buffer_size)) {
|
||||||
if (SPIRead(saddr, buffer, will_copy)) {
|
return 2;
|
||||||
return 3;
|
}
|
||||||
}
|
if (SPIRead(saddr, buffer, buffer_size)) {
|
||||||
if (SPIWrite(daddr, buffer, will_copy)) {
|
return 3;
|
||||||
return 4;
|
}
|
||||||
}
|
if (SPIWrite(daddr, buffer, buffer_size)) {
|
||||||
saddr += will_copy;
|
return 4;
|
||||||
daddr += will_copy;
|
}
|
||||||
left -= will_copy;
|
saddr += buffer_size;
|
||||||
|
daddr += buffer_size;
|
||||||
|
left -= buffer_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -123,14 +116,16 @@ void main()
|
|||||||
if (eboot_command_read(&cmd)) {
|
if (eboot_command_read(&cmd)) {
|
||||||
cmd.action = ACTION_LOAD_APP;
|
cmd.action = ACTION_LOAD_APP;
|
||||||
cmd.args[0] = 0;
|
cmd.args[0] = 0;
|
||||||
ets_putc('e');
|
ets_putc('~');
|
||||||
} else {
|
} else {
|
||||||
ets_putc('@');
|
ets_putc('@');
|
||||||
}
|
}
|
||||||
eboot_command_clear();
|
eboot_command_clear();
|
||||||
|
|
||||||
if (cmd.action == ACTION_COPY_RAW) {
|
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]);
|
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2]);
|
||||||
|
ets_putc('0'+res); ets_putc('\n');
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
cmd.action = ACTION_LOAD_APP;
|
cmd.action = ACTION_LOAD_APP;
|
||||||
cmd.args[0] = cmd.args[1];
|
cmd.args[0] = cmd.args[1];
|
||||||
@ -138,15 +133,14 @@ void main()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cmd.action == ACTION_LOAD_APP) {
|
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) {
|
if (res) {
|
||||||
ets_putc('\n');
|
SWRST;
|
||||||
ets_putc('#');
|
|
||||||
ets_putc('0' + res);
|
|
||||||
ets_putc('\n');
|
|
||||||
SWRST;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while(true){}
|
while(true){}
|
||||||
|
Binary file not shown.
@ -233,6 +233,7 @@ void loop(void);
|
|||||||
|
|
||||||
#include "HardwareSerial.h"
|
#include "HardwareSerial.h"
|
||||||
#include "Esp.h"
|
#include "Esp.h"
|
||||||
|
#include "Updater.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
|
||||||
#define min(a,b) ((a)<(b)?(a):(b))
|
#define min(a,b) ((a)<(b)?(a):(b))
|
||||||
|
@ -30,7 +30,7 @@ extern struct rst_info resetInfo;
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// #define DEBUG_SERIAL Serial
|
//#define DEBUG_SERIAL Serial
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -358,83 +358,38 @@ uint32_t EspClass::getFreeSketchSpace() {
|
|||||||
return freeSpaceEnd - freeSpaceStart;
|
return freeSpaceEnd - freeSpaceStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EspClass::updateSketch(Stream& in, uint32_t size) {
|
bool EspClass::updateSketch(Stream& in, uint32_t size, bool restartOnFail, bool restartOnSuccess) {
|
||||||
|
if(!Update.begin(size)){
|
||||||
|
#ifdef DEBUG_SERIAL
|
||||||
|
DEBUG_SERIAL.print("Update ");
|
||||||
|
Update.printError(DEBUG_SERIAL);
|
||||||
|
#endif
|
||||||
|
if(restartOnFail) ESP.restart();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (size > getFreeSketchSpace())
|
if(Update.writeStream(in) != size){
|
||||||
return false;
|
#ifdef DEBUG_SERIAL
|
||||||
|
DEBUG_SERIAL.print("Update ");
|
||||||
|
Update.printError(DEBUG_SERIAL);
|
||||||
|
#endif
|
||||||
|
if(restartOnFail) ESP.restart();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t usedSize = getSketchSize();
|
if(!Update.end()){
|
||||||
uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
#ifdef DEBUG_SERIAL
|
||||||
uint32_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
DEBUG_SERIAL.print("Update ");
|
||||||
|
Update.printError(DEBUG_SERIAL);
|
||||||
|
#endif
|
||||||
|
if(restartOnFail) ESP.restart();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_SERIAL
|
#ifdef DEBUG_SERIAL
|
||||||
DEBUG_SERIAL.printf("erase @0x%x size=0x%x\r\n", freeSpaceStart, roundedSize);
|
DEBUG_SERIAL.println("Update SUCCESS");
|
||||||
#endif
|
#endif
|
||||||
|
if(restartOnSuccess) ESP.restart();
|
||||||
noInterrupts();
|
return true;
|
||||||
int rc = SPIEraseAreaEx(freeSpaceStart, roundedSize);
|
|
||||||
interrupts();
|
|
||||||
if (rc)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
#ifdef DEBUG_SERIAL
|
|
||||||
DEBUG_SERIAL.println("erase done");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint32_t addr = freeSpaceStart;
|
|
||||||
uint32_t left = size;
|
|
||||||
|
|
||||||
const uint32_t bufferSize = FLASH_SECTOR_SIZE;
|
|
||||||
std::unique_ptr<uint8_t> buffer(new uint8_t[bufferSize]);
|
|
||||||
|
|
||||||
#ifdef DEBUG_SERIAL
|
|
||||||
DEBUG_SERIAL.println("writing");
|
|
||||||
#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
|
|
||||||
}
|
|
||||||
|
|
||||||
#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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ class EspClass {
|
|||||||
|
|
||||||
uint32_t getSketchSize();
|
uint32_t getSketchSize();
|
||||||
uint32_t getFreeSketchSpace();
|
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();
|
String getResetInfo();
|
||||||
struct rst_info * getResetInfoPtr();
|
struct rst_info * getResetInfoPtr();
|
||||||
|
209
cores/esp8266/Updater.cpp
Normal file
209
cores/esp8266/Updater.cpp
Normal file
@ -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;
|
121
cores/esp8266/Updater.h
Normal file
121
cores/esp8266/Updater.h
Normal file
@ -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<typename T>
|
||||||
|
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
|
73
libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino
Normal file
73
libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
#include <ESP8266mDNS.h>
|
||||||
|
|
||||||
|
const char* host = "esp8266-webupdate";
|
||||||
|
const char* ssid = "........";
|
||||||
|
const char* password = "........";
|
||||||
|
|
||||||
|
ESP8266WebServer server(80);
|
||||||
|
const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
@ -66,11 +66,16 @@ extern "C" {
|
|||||||
#define MDNS_TYPE_PTR 0x000C
|
#define MDNS_TYPE_PTR 0x000C
|
||||||
#define MDNS_TYPE_SRV 0x0021
|
#define MDNS_TYPE_SRV 0x0021
|
||||||
#define MDNS_TYPE_TXT 0x0010
|
#define MDNS_TYPE_TXT 0x0010
|
||||||
#define MDNS_TYPE_NSEC 0x002F
|
|
||||||
|
|
||||||
#define MDNS_CLASS_IN 0x0001
|
#define MDNS_CLASS_IN 0x0001
|
||||||
#define MDNS_CLASS_IN_FLUSH_CACHE 0x8001
|
#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_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_read16() (((uint16_t)_conn->read() << 8) | _conn->read())
|
||||||
#define _conn_read8() _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_PTR) os_printf(" PTR ");
|
||||||
else if(currentType == MDNS_TYPE_SRV) os_printf(" SRV ");
|
else if(currentType == MDNS_TYPE_SRV) os_printf(" SRV ");
|
||||||
else if(currentType == MDNS_TYPE_TXT) os_printf(" TXT ");
|
else if(currentType == MDNS_TYPE_TXT) os_printf(" TXT ");
|
||||||
else if(currentType == MDNS_TYPE_NSEC) os_printf(" NSEC ");
|
|
||||||
else os_printf(" 0x%04X ", currentType);
|
else os_printf(" 0x%04X ", currentType);
|
||||||
|
|
||||||
if(currentClass == MDNS_CLASS_IN) os_printf(" IN ");
|
if(currentClass == MDNS_CLASS_IN) os_printf(" IN ");
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESP8266mDNS.h>
|
||||||
|
#include <WiFiUDP.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
10
platform.txt
10
platform.txt
@ -93,3 +93,13 @@ tools.esptool.upload.protocol=esp
|
|||||||
tools.esptool.upload.params.verbose=-vv
|
tools.esptool.upload.params.verbose=-vv
|
||||||
tools.esptool.upload.params.quiet=
|
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.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"
|
||||||
|
94
tools/espota.py
Executable file
94
tools/espota.py
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# this script will push an OTA update to the ESP
|
||||||
|
# use it like: python espota.py <ESP_IP_address> <sketch.bin>
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user