From 843e11100d73465d9f66ef6ccc855fcc97a71c7a Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Sat, 7 Nov 2015 22:02:43 +0200 Subject: [PATCH 1/6] mDNS, platform and espota.py changes for IDE Upload mDNS responds with more TXT properties platform change to support OTA functions espota.py added authentication parameter IDE branch: https://github.com/me-no-dev/Arduino-1/tree/esp8266-ota --- libraries/ESP8266mDNS/ESP8266mDNS.cpp | 70 +++++++++++++++++++-------- libraries/ESP8266mDNS/ESP8266mDNS.h | 4 +- libraries/ESP8266mDNS/keywords.txt | 1 + platform.txt | 14 +++--- tools/espota.py | 16 ++++-- 5 files changed, 74 insertions(+), 31 deletions(-) diff --git a/libraries/ESP8266mDNS/ESP8266mDNS.cpp b/libraries/ESP8266mDNS/ESP8266mDNS.cpp index 687f886239..92b0410983 100644 --- a/libraries/ESP8266mDNS/ESP8266mDNS.cpp +++ b/libraries/ESP8266mDNS/ESP8266mDNS.cpp @@ -85,7 +85,7 @@ static const IPAddress MDNS_MULTICAST_ADDR(224, 0, 0, 251); static const int MDNS_MULTICAST_TTL = 1; static const int MDNS_PORT = 5353; -MDNSResponder::MDNSResponder() : _conn(0) { _services = 0; } +MDNSResponder::MDNSResponder() : _conn(0) { _services = 0; _arduinoAuth = false; } MDNSResponder::~MDNSResponder() {} bool MDNSResponder::begin(const char* domain){ @@ -101,8 +101,6 @@ bool MDNSResponder::begin(const char* domain){ _hostName[i] = tolower(domain[i]); _hostName[n] = '\0'; - os_strcpy(_boardName, ARDUINO_BOARD); - // Open the MDNS socket if it isn't already open. if (!_conn) { uint32_t ourIp = _getOurIp(); @@ -372,7 +370,10 @@ void MDNSResponder::_parsePacket(){ return _reply(responseMask, (serviceName), (protoName), servicePort); } - +void MDNSResponder::enableArduino(uint16_t port, bool auth){ + _arduinoAuth = auth; + addService("arduino", "tcp", port); +} void MDNSResponder::_reply(uint8_t replyMask, char * service, char *proto, uint16_t port){ int i; @@ -439,24 +440,53 @@ void MDNSResponder::_reply(uint8_t replyMask, char * service, char *proto, uint1 // TXT Response if(replyMask & 0x4){ - if(replyMask & 0x8){//send the name - uint8_t txtHead[2] = {0xC0, (uint8_t)(36 + serviceLen)}; - _conn->append(reinterpret_cast(txtHead), 2); + if(replyMask & 0x8){ + uint8_t txtHead[10] = { + 0xC0, (uint8_t)(36 + serviceLen),//send the name + 0x00, 0x10, //Type TXT + 0x80, 0x01, //Class IN, with cache flush + 0x00, 0x00, 0x11, 0x94, //TTL 4500 + }; + _conn->append(reinterpret_cast(txtHead), 10); } - uint8_t boardNameLen = os_strlen(_boardName); - - uint8_t txt[24] = { - 0x00, 0x10, //Type TXT - 0x80, 0x01, //Class IN, with cache flush - 0x00, 0x00, 0x11, 0x94, //TTL 4500 - 0x00, 0x0e, //DATA LEN - (uint8_t)(6 + boardNameLen), //strlen(board=) + strlen(boardName) - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x3d //board= - }; - _conn->append(reinterpret_cast(txt), 17); - _conn->append(reinterpret_cast(_boardName), boardNameLen); - + if(strcmp(reinterpret_cast("arduino"), service) == 0){ + //arduino + //arduino service dependance should be removed and properties abstracted + const char *tcpCheckExtra = "tcp_check=no"; + uint8_t tcpCheckExtraLen = os_strlen(tcpCheckExtra); + + const char *sshUploadExtra = "ssh_upload=no"; + uint8_t sshUploadExtraLen = os_strlen(sshUploadExtra); + + char boardName[64]; + const char *boardExtra = "board="; + os_sprintf(boardName, "%s%s\0", boardExtra, ARDUINO_BOARD); + uint8_t boardNameLen = os_strlen(boardName); + + char authUpload[16]; + const char *authUploadExtra = "auth_upload="; + os_sprintf(authUpload, "%s%s\0", authUploadExtra, reinterpret_cast((_arduinoAuth)?"yes":"no")); + uint8_t authUploadLen = os_strlen(authUpload); + + uint16_t textDataLen = (1 + boardNameLen) + (1 + tcpCheckExtraLen) + (1 + sshUploadExtraLen) + (1 + authUploadLen); + uint8_t txt[2] = {(uint8_t)(textDataLen >> 8), (uint8_t)(textDataLen)}; //DATA LEN + _conn->append(reinterpret_cast(txt), 2); + + _conn->append(reinterpret_cast(&boardNameLen), 1); + _conn->append(reinterpret_cast(boardName), boardNameLen); + _conn->append(reinterpret_cast(&authUploadLen), 1); + _conn->append(reinterpret_cast(authUpload), authUploadLen); + _conn->append(reinterpret_cast(&tcpCheckExtraLen), 1); + _conn->append(reinterpret_cast(tcpCheckExtra), tcpCheckExtraLen); + _conn->append(reinterpret_cast(&sshUploadExtraLen), 1); + _conn->append(reinterpret_cast(sshUploadExtra), sshUploadExtraLen); + } else { + //not arduino + //we should figure out an API so TXT properties can be added for services + uint8_t txt[2] = {0,0}; + _conn->append(reinterpret_cast(txt), 2); + } } // SRV Response diff --git a/libraries/ESP8266mDNS/ESP8266mDNS.h b/libraries/ESP8266mDNS/ESP8266mDNS.h index a2fd17d247..5958c40d15 100644 --- a/libraries/ESP8266mDNS/ESP8266mDNS.h +++ b/libraries/ESP8266mDNS/ESP8266mDNS.h @@ -77,12 +77,14 @@ class MDNSResponder { void addService(String service, String proto, uint16_t port){ addService(service.c_str(), proto.c_str(), port); } + + void enableArduino(uint16_t port, bool auth=false); private: struct MDNSService * _services; UdpContext* _conn; char _hostName[128]; - char _boardName[64]; + bool _arduinoAuth; uint32_t _getOurIp(); uint16_t _getServicePort(char *service, char *proto); diff --git a/libraries/ESP8266mDNS/keywords.txt b/libraries/ESP8266mDNS/keywords.txt index 4a538e0806..6b2f03951e 100644 --- a/libraries/ESP8266mDNS/keywords.txt +++ b/libraries/ESP8266mDNS/keywords.txt @@ -17,6 +17,7 @@ MDNS KEYWORD1 begin KEYWORD2 update KEYWORD2 addService KEYWORD2 +enableArduino KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/platform.txt b/platform.txt index 58ba9edfa7..ca99e858c6 100644 --- a/platform.txt +++ b/platform.txt @@ -92,18 +92,18 @@ recipe.size.regex.data=^(?:\.data|\.rodata|\.bss)\s+([0-9]+).* tools.esptool.cmd=esptool tools.esptool.cmd.windows=esptool.exe tools.esptool.path={runtime.platform.path}/tools/esptool -#runtime.tools.esptool.path - -tools.mkspiffs.cmd=mkspiffs -tools.mkspiffs.cmd.windows=mkspiffs.exe -tools.mkspiffs.path={runtime.platform.path}/tools/mkspiffs -#runtime.tools.mkspiffs.path +tools.esptool.network_cmd=python +tools.esptool.network_cmd.windows=python.exe 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" -i "{serial.port}" -p "{network.port}" -f "{build.path}/{build.project_name}.bin" +tools.esptool.upload.network_pattern="{network_cmd}" "{runtime.platform.path}/tools/espota.py" -i "{serial.port}" -p "{network.port}" "--auth={network.password}" -f "{build.path}/{build.project_name}.bin" + +tools.mkspiffs.cmd=mkspiffs +tools.mkspiffs.cmd.windows=mkspiffs.exe +tools.mkspiffs.path={runtime.platform.path}/tools/mkspiffs tools.espota.cmd=python tools.espota.cmd.windows=python.exe diff --git a/tools/espota.py b/tools/espota.py index c5b7f50104..bea72910da 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -29,7 +29,7 @@ SPIFFS = 100 -def serve(remoteAddr, remotePort, filename, command = FLASH): +def serve(remoteAddr, remotePort, password, filename, command = FLASH): # Create a TCP/IP socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverPort = 48266 @@ -44,7 +44,7 @@ def serve(remoteAddr, remotePort, filename, command = FLASH): content_size = os.path.getsize(filename) logging.info('Upload size: %d', content_size) - message = '%d %d %d\n' % (command, serverPort, content_size) + message = '%s %d %d %d\n' % (password, command, serverPort, content_size) # Wait for a connection logging.info('Sending invitation to: %s', remoteAddr) @@ -131,6 +131,16 @@ def parser(): ) parser.add_option_group(group) + # auth + group = optparse.OptionGroup(parser, "Authentication") + group.add_option("-a", "--auth", + dest = "auth", + help = "Set authentication password.", + action = "store", + default = "" + ) + parser.add_option_group(group) + # image group = optparse.OptionGroup(parser, "Image") group.add_option("-f", "--file", @@ -190,7 +200,7 @@ def main(args): command = SPIFFS # end if - return serve(options.esp_ip, options.esp_port, options.image, command) + return serve(options.esp_ip, options.esp_port, options.auth, options.image, command) # end main From e613e42249830f6221f11c4951c5b1840a728dbd Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Sun, 8 Nov 2015 02:01:06 +0200 Subject: [PATCH 2/6] Fix example sketch, espota output and failing updater I get 100% success with OTA now --- cores/esp8266/Updater.h | 2 +- .../DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino | 66 ++++++++++--------- tools/espota.py | 8 ++- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index 670b0e7e94..587831ef2e 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -103,7 +103,7 @@ class UpdaterClass { } if(remaining() == 0) return written; - yield(); + delay(1); available = data.available(); } return written; 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 0689a8531a..583a1b4062 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 @@ -1,20 +1,20 @@ #include #include -#include const char* host = "esp8266-ota"; const char* ssid = "**********"; const char* pass = "**********"; -const uint16_t aport = 8266; +const uint16_t ota_port = 8266; +const char* ota_pass = "1234"; -WiFiServer TelnetServer(aport); -WiFiClient Telnet; WiFiUDP OTA; +WiFiServer MonitorServer(ota_port); +WiFiClient Monitor; void setup() { Serial.begin(115200); Serial.println(""); - Serial.println("Arduino OTA Test"); + Serial.println("Bare Minimum Arduino OTA"); Serial.printf("Sketch size: %u\n", ESP.getSketchSize()); Serial.printf("Free size: %u\n", ESP.getFreeSketchSpace()); @@ -22,45 +22,52 @@ void setup() { 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); + MDNS.enableArduino(ota_port, true); + OTA.begin(ota_port); + MonitorServer.begin(); + MonitorServer.setNoDelay(true); Serial.print("IP address: "); Serial.println(WiFi.localIP()); + } else { + Serial.println("WiFi Connect Failed"); + delay(10000); + ESP.reset(); } } void loop() { - //OTA Sketch if (OTA.parsePacket()) { IPAddress remote = OTA.remoteIP(); + String pass = OTA.readStringUntil(' '); int cmd = OTA.parseInt(); int port = OTA.parseInt(); int size = OTA.parseInt(); - + + if(!pass.equals(String(ota_pass))){ + Serial.println("ERROR: Wrong Password"); + return; + } + 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"); + Update.printError(Serial); return; } + WiFiUDP::stopAll(); + WiFiClient::stopAll(); + WiFiClient client; if (client.connect(remote, port)) { - uint32_t written; while(!Update.isFinished()){ written = Update.write(client); if(written > 0) client.print(written, DEC); } - Serial.setDebugOutput(false); - if(Update.end()){ client.println("OK"); Serial.printf("Update Success: %u\nRebooting...\n", millis() - startTime); @@ -74,28 +81,27 @@ void loop() { } } //IDE Monitor (connected to Serial) - if (TelnetServer.hasClient()){ - if (!Telnet || !Telnet.connected()){ - if(Telnet) Telnet.stop(); - Telnet = TelnetServer.available(); + if (MonitorServer.hasClient()){ + if (!Monitor || !Monitor.connected()){ + if(Monitor) Monitor.stop(); + Monitor = MonitorServer.available(); } else { - WiFiClient toKill = TelnetServer.available(); - toKill.stop(); + MonitorServer.available().stop(); } } - if (Telnet && Telnet.connected() && Telnet.available()){ - while(Telnet.available()) - Serial.write(Telnet.read()); + if (Monitor && Monitor.connected() && Monitor.available()){ + while(Monitor.available()) + Serial.write(Monitor.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(); + if (Monitor && Monitor.connected()){ + Monitor.write((uint8_t *)sbuf, len); + delay(0); } free(sbuf); } - delay(1); + delay(0); } diff --git a/tools/espota.py b/tools/espota.py index bea72910da..cacbc12d54 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -53,7 +53,7 @@ def serve(remoteAddr, remotePort, password, filename, command = FLASH): sent = sock2.sendto(message, remote_address) sock2.close() - logging.info('Waiting for device...\n') + logging.info('Waiting for device...') try: sock.settimeout(10) connection, client_address = sock.accept() @@ -78,13 +78,15 @@ def serve(remoteAddr, remotePort, password, filename, command = FLASH): connection.sendall(chunk) res = connection.recv(4) except: - logging.error('\nError Uploading') + sys.stderr.write('\n') + logging.error('Error Uploading') connection.close() f.close() sock.close() return 1 - logging.info('\nWaiting for result...\n') + sys.stderr.write('\n') + logging.info('Waiting for result...') try: connection.settimeout(60) data = connection.recv(32) From a8976a01fd14285b7926ffe5cdc403426e4ebae9 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Mon, 9 Nov 2015 00:42:30 +0200 Subject: [PATCH 3/6] Add MD5 to core, Fix OTA examples and Digest Authentication to OTA and espota.py --- cores/esp8266/MD5Builder.cpp | 43 ++++ cores/esp8266/MD5Builder.h | 47 ++++ cores/esp8266/Updater.cpp | 29 ++- cores/esp8266/Updater.h | 22 +- cores/esp8266/md5.h | 44 ++++ libraries/ArduinoOTA/ArduinoOTA.cpp | 242 +++++++++++++----- libraries/ArduinoOTA/ArduinoOTA.h | 59 +++-- .../ArduinoOTA/examples/BasicOTA/BasicOTA.ino | 42 +-- .../ArduinoOTA/examples/OTALeds/OTALeds.ino | 25 +- .../DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino | 107 -------- .../OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino | 9 +- platform.txt | 2 +- tools/espota.py | 60 ++++- 13 files changed, 501 insertions(+), 230 deletions(-) create mode 100644 cores/esp8266/MD5Builder.cpp create mode 100644 cores/esp8266/MD5Builder.h create mode 100644 cores/esp8266/md5.h delete mode 100644 libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino diff --git a/cores/esp8266/MD5Builder.cpp b/cores/esp8266/MD5Builder.cpp new file mode 100644 index 0000000000..0957785ce6 --- /dev/null +++ b/cores/esp8266/MD5Builder.cpp @@ -0,0 +1,43 @@ +#include "Arduino.h" +#include "md5.h" +#include "MD5Builder.h" + +#define hex_char_to_byte(c) (((c)>='a'&&(c)<='f')?((c)-87):((c)>='A'&&(c)<='F')?((c)-55):((c)>='0'&&(c)<='9')?((c)-48):0) + +void MD5Builder::begin(void){ + memset(_buf, 0x00, 16); + MD5Init(&_ctx); +} + +void MD5Builder::add(uint8_t * data, uint16_t len){ + MD5Update(&_ctx, data, len); +} + +void MD5Builder::addHexString(const char * data){ + uint16_t i, len = strlen(data); + uint8_t * tmp = (uint8_t*)malloc(len/2); + if(tmp == NULL) + return; + for(i=0; i #include #include "ArduinoOTA.h" +#include "MD5Builder.h" -ArduinoOTA::ArduinoOTA(const char *mdns_host_prefix, int port, bool serial_debug) +//#define OTA_DEBUG 1 + +ArduinoOTAClass::ArduinoOTAClass() { - _port = port; - _mdns_host = new String(mdns_host_prefix); - *_mdns_host += String(ESP.getChipId(), HEX); _udp_ota = new WiFiUDP(); - _serial_debug = serial_debug; - + _password = 0; + _hostname = 0; + _port = 0; + _nonce = 0; + _state = OTA_IDLE; + + _size = 0; + _cmd = 0; + _ota_port = 0; + _ota_ip = (uint32_t)0; + _md5 = new char[33]; + _start_callback = NULL; _end_callback = NULL; _progress_callback = NULL; _error_callback = NULL; } -void ArduinoOTA::onStart(OTA_CALLBACK(fn)){ +void ArduinoOTAClass::onStart(OTA_CALLBACK(fn)){ _start_callback = fn; } -void ArduinoOTA::onEnd(OTA_CALLBACK(fn)){ +void ArduinoOTAClass::onEnd(OTA_CALLBACK(fn)){ _end_callback = fn; } -void ArduinoOTA::onProgress(OTA_CALLBACK_PROGRESS(fn)){ +void ArduinoOTAClass::onProgress(OTA_CALLBACK_PROGRESS(fn)){ _progress_callback = fn; } -void ArduinoOTA::onError(OTA_CALLBACK(fn)){ +void ArduinoOTAClass::onError(OTA_CALLBACK_ERROR(fn)){ _error_callback = fn; } -ArduinoOTA::~ArduinoOTA(){ +ArduinoOTAClass::~ArduinoOTAClass(){ delete _udp_ota; - delete _mdns_host; } -void ArduinoOTA::setup() { - _udp_ota->begin(_port); - if (_mdns_host) { - if (_serial_debug) - Serial.printf("OTA server at: %s:%u\n", - _mdns_host->c_str(), - _port); - MDNS.begin(_mdns_host->c_str()); - MDNS.addService("arduino", "tcp", _port); +void ArduinoOTAClass::setPort(uint16_t port){ + if(!_initialized && !_port && port){ + _port = port; } } -void ArduinoOTA::handle() { - - if (!*_udp_ota) { - _udp_ota->begin(_port); - if (_serial_debug) { - Serial.println("OTA restarted"); - } +void ArduinoOTAClass::setHostname(const char * hostname){ + if(!_initialized && !_hostname && hostname){ + _hostname = new char[strlen(hostname)]; + sprintf(_hostname, "%s", hostname); } +} - if (!_udp_ota->parsePacket()) return; - - IPAddress remote = _udp_ota->remoteIP(); - int cmd = _udp_ota->parseInt(); - int port = _udp_ota->parseInt(); - int size = _udp_ota->parseInt(); - - if (_serial_debug){ - Serial.print("Update Start: ip:"); - Serial.print(remote); - Serial.printf(", port:%d, size:%d\n", port, size); +void ArduinoOTAClass::setPassword(const char * password){ + if(!_initialized && !_password && password){ + _password = new char[strlen(password)]; + sprintf(_password, "%s", password); } +} - WiFiUDP::stopAll(); +void ArduinoOTAClass::begin() { + if(_initialized) + return; + _initialized = true; + if(!_hostname){ + _hostname = new char[15]; + sprintf(_hostname, "esp8266-%02x", ESP.getChipId()); + } + if(!_port) + _port = 8266; + + _udp_ota->begin(_port); + MDNS.begin(_hostname); + if(_password){ + _nonce = new char[33]; + MDNS.enableArduino(_port, true); + } else + MDNS.enableArduino(_port); + _state = OTA_IDLE; +#if OTA_DEBUG + Serial.printf("OTA server at: %s.local:%u\n", _hostname, _port); +#endif +} - if(!Update.begin(size, cmd)){ - if (_serial_debug) - Serial.println("Update Begin Error"); - if (_error_callback) _error_callback(); +void ArduinoOTAClass::_runUpdate(){ + if(!Update.begin(_size, _cmd)){ +#if OTA_DEBUG + Serial.println("Update Begin Error"); +#endif + if (_error_callback) _error_callback(OTA_BEGIN_ERROR); _udp_ota->begin(_port); + _state = OTA_IDLE; return; } + Update.setMD5(_md5); + WiFiUDP::stopAll(); + WiFiClient::stopAll(); + + if (_start_callback) _start_callback(); - if (_progress_callback) _progress_callback(0, size); + if (_progress_callback) _progress_callback(0, _size); WiFiClient client; - if (!client.connect(remote, port)) { - if (_serial_debug) - Serial.printf("Connect Failed\n"); + if (!client.connect(_ota_ip, _ota_port)) { +#if OTA_DEBUG + Serial.printf("Connect Failed\n"); +#endif _udp_ota->begin(_port); - if (_error_callback) _error_callback(); + if (_error_callback) _error_callback(OTA_CONNECT_ERROR); + _state = OTA_IDLE; } - uint32_t written; + uint32_t written, total = 0; while(!Update.isFinished() && client.connected()){ - // TODO(mangelajo): enhance the Update.write(client) to - // accept a progress callback + int waited = 1000; + while(!client.available() && waited--) + delay(1); + if(!waited){ +#if OTA_DEBUG + Serial.printf("Recieve Failed\n"); +#endif + _udp_ota->begin(_port); + if (_error_callback) _error_callback(OTA_RECIEVE_ERROR); + _state = OTA_IDLE; + } written = Update.write(client); - if(written > 0) client.print(written, DEC); - if(_progress_callback) _progress_callback(written, size); + if(written > 0){ + client.print(written, DEC); + total += written; + if(_progress_callback) _progress_callback(total, _size); + } } - Serial.setDebugOutput(false); - if(Update.end()){ - client.println("OK"); - if (_serial_debug) - Serial.printf("Update Success\nRebooting...\n"); + client.print("OK"); +#if OTA_DEBUG + Serial.printf("Update Success\nRebooting...\n"); +#endif if(_end_callback) _end_callback(); ESP.restart(); } else { - // Update failed: listen UDP again, callback and print _udp_ota->begin(_port); - if (_error_callback) _error_callback(); + if (_error_callback) _error_callback(OTA_END_ERROR); Update.printError(client); - if (_serial_debug) - Update.printError(Serial); +#if OTA_DEBUG + Update.printError(Serial); +#endif + _state = OTA_IDLE; } } + +void ArduinoOTAClass::handle() { + if (!*_udp_ota) { + _udp_ota->begin(_port); +#if OTA_DEBUG + Serial.println("OTA restarted"); +#endif + } + + if (!_udp_ota->parsePacket()) return; + + if(_state == OTA_IDLE){ + _ota_ip = _udp_ota->remoteIP(); + _cmd = _udp_ota->parseInt(); + _ota_port = _udp_ota->parseInt(); + _size = _udp_ota->parseInt(); + _udp_ota->read(); + sprintf(_md5, "%s", _udp_ota->readStringUntil('\n').c_str()); + +#if OTA_DEBUG + Serial.print("Update Start: ip:"); + Serial.print(_ota_ip); + Serial.printf(", port:%d, size:%d, md5:%s\n", _ota_port, _size, _md5); +#endif + + _udp_ota->beginPacket(_ota_ip, _udp_ota->remotePort()); + if(_password){ + MD5Builder nonce_md5; + nonce_md5.begin(); + nonce_md5.add(String(micros())); + nonce_md5.calculate(); + nonce_md5.getChars(_nonce); + _udp_ota->printf("AUTH %s", _nonce); + _udp_ota->endPacket(); + _state = OTA_WAITAUTH; + return; + } else { + _udp_ota->print("OK"); + _udp_ota->endPacket(); + _state = OTA_RUNUPDATE; + } + } else if(_state == OTA_WAITAUTH){ + String cnonce = _udp_ota->readStringUntil(' '); + String response = _udp_ota->readStringUntil('\n'); + + MD5Builder _passmd5; + _passmd5.begin(); + _passmd5.add(_password); + _passmd5.calculate(); + String passmd5 = _passmd5.toString(); + + String challenge = passmd5 + ":" + String(_nonce) + ":" + cnonce; + MD5Builder _challengemd5; + _challengemd5.begin(); + _challengemd5.add(challenge); + _challengemd5.calculate(); + String result = _challengemd5.toString(); + + if(result.equals(response)){ + _udp_ota->beginPacket(_ota_ip, _udp_ota->remotePort()); + _udp_ota->print("OK"); + _udp_ota->endPacket(); + _state = OTA_RUNUPDATE; + } else { + _udp_ota->beginPacket(_ota_ip, _udp_ota->remotePort()); + _udp_ota->print("Authentication Failed"); + _udp_ota->endPacket(); + if (_error_callback) _error_callback(OTA_AUTH_ERROR); + _state = OTA_IDLE; + } + } + + if(_state == OTA_RUNUPDATE) + _runUpdate(); +} + +ArduinoOTAClass ArduinoOTA; diff --git a/libraries/ArduinoOTA/ArduinoOTA.h b/libraries/ArduinoOTA/ArduinoOTA.h index 1413306e8c..350c129e5e 100644 --- a/libraries/ArduinoOTA/ArduinoOTA.h +++ b/libraries/ArduinoOTA/ArduinoOTA.h @@ -5,31 +5,58 @@ class WiFiUDP; #define OTA_CALLBACK(callback) void (*callback)() #define OTA_CALLBACK_PROGRESS(callback) void (*callback)(unsigned int, unsigned int) +#define OTA_CALLBACK_ERROR(callback) void (*callback)(ota_error_t) -class ArduinoOTA +typedef enum { + OTA_IDLE, + OTA_WAITAUTH, + OTA_RUNUPDATE +} ota_state_t; + +typedef enum { + OTA_AUTH_ERROR, + OTA_BEGIN_ERROR, + OTA_CONNECT_ERROR, + OTA_RECIEVE_ERROR, + OTA_END_ERROR +} ota_error_t; + +class ArduinoOTAClass { + private: int _port; - String* _mdns_host; + char *_password; + char * _hostname; + char * _nonce; WiFiUDP* _udp_ota; - bool _serial_debug; + bool _initialized; + + ota_state_t _state; + int _size, _cmd, _ota_port; + IPAddress _ota_ip; + char * _md5; OTA_CALLBACK(_start_callback); OTA_CALLBACK(_end_callback); - OTA_CALLBACK(_error_callback); - + OTA_CALLBACK_ERROR(_error_callback); OTA_CALLBACK_PROGRESS(_progress_callback); + + void _runUpdate(void); - public: - ArduinoOTA(const char *mdns_host="ESP8266-OTA-", - int port=8266, - bool serial_debug=true); - ~ArduinoOTA(); - void setup(); - void handle(); - void onStart(OTA_CALLBACK(fn)); - void onEnd(OTA_CALLBACK(fn)); - void onProgress(OTA_CALLBACK_PROGRESS(fn)); - void onError(OTA_CALLBACK (fn)); + public: + ArduinoOTAClass(); + ~ArduinoOTAClass(); + void setPort(uint16_t port); + void setHostname(const char *hostname); + void setPassword(const char *password); + void onStart(OTA_CALLBACK(fn)); + void onEnd(OTA_CALLBACK(fn)); + void onProgress(OTA_CALLBACK_PROGRESS(fn)); + void onError(OTA_CALLBACK_ERROR (fn)); + void begin(); + void handle(); }; +extern ArduinoOTAClass ArduinoOTA; + #endif /* __ARDUINO_OTA_H */ diff --git a/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino index 0f8967f898..1cbb24e7bb 100644 --- a/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino +++ b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino @@ -6,26 +6,36 @@ const char* ssid = "..."; const char* password = "..."; -ArduinoOTA ota_server; - void setup() { - Serial.begin(115200); - - Serial.println("Booting"); - WiFi.mode(WIFI_STA); - - /* try the flash stored password first */ - WiFi.begin(); - - while (WiFi.waitForConnectResult() != WL_CONNECTED){ - WiFi.begin(ssid, password); - Serial.println("Retrying connection..."); + Serial.begin(115200); + Serial.println("Booting"); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + while (WiFi.waitForConnectResult() != WL_CONNECTED){ + Serial.println("Connection Failed! Rebooting..."); + delay(5000); + ESP.reset(); } - ota_server.setup(); + //ArduinoOTA.setPort(8266);//Defaults to 8266 + //ArduinoOTA.setHostname((const char *)"myesp8266");//Defaults to esp8266-[ChipID] + //ArduinoOTA.setPassword((const char *)"123");//defaults to no authentication + ArduinoOTA.onStart([]() { Serial.println("Start"); }); + ArduinoOTA.onEnd([]() { Serial.println("End"); }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\n", (progress/(total/100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if(error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if(error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if(error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if(error == OTA_RECIEVE_ERROR) Serial.println("Recieve Failed"); + else if(error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); Serial.println("Ready"); } void loop() { - ota_server.handle(); - yield(); + ArduinoOTA.handle(); } diff --git a/libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino b/libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino index 208e0be084..3b15fe6066 100644 --- a/libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino +++ b/libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino @@ -5,9 +5,7 @@ const char* ssid = "..."; const char* password = "..."; -const char* host_prefix = "OTA-LEDS-"; - -ArduinoOTA ota_server(host_prefix, 8266, /* debug_serial= */ true); +const char* host = "OTA-LEDS"; int led_pin = 13; #define N_DIMMERS 3 @@ -23,8 +21,7 @@ void setup() { Serial.println("Booting"); WiFi.mode(WIFI_STA); - /* try the flash stored password first */ - WiFi.begin(); + WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED){ WiFi.begin(ssid, password); @@ -33,10 +30,6 @@ void setup() { /* switch off led */ digitalWrite(led_pin, HIGH); - /* setup the OTA server */ - ota_server.setup(); - Serial.println("Ready"); - /* configure dimmers, and OTA server events */ analogWriteRange(1000); analogWrite(led_pin,990); @@ -47,13 +40,14 @@ void setup() { analogWrite(dimmer_pin[i],50); } - ota_server.onStart([]() { // switch off all the PWMs during upgrade + ArduinoOTA.setHostname(host); + ArduinoOTA.onStart([]() { // switch off all the PWMs during upgrade for(int i=0; i -#include - -const char* host = "esp8266-ota"; -const char* ssid = "**********"; -const char* pass = "**********"; -const uint16_t ota_port = 8266; -const char* ota_pass = "1234"; - -WiFiUDP OTA; -WiFiServer MonitorServer(ota_port); -WiFiClient Monitor; - -void setup() { - Serial.begin(115200); - Serial.println(""); - Serial.println("Bare Minimum Arduino OTA"); - - 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.enableArduino(ota_port, true); - OTA.begin(ota_port); - MonitorServer.begin(); - MonitorServer.setNoDelay(true); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - } else { - Serial.println("WiFi Connect Failed"); - delay(10000); - ESP.reset(); - } -} - -void loop() { - if (OTA.parsePacket()) { - IPAddress remote = OTA.remoteIP(); - String pass = OTA.readStringUntil(' '); - int cmd = OTA.parseInt(); - int port = OTA.parseInt(); - int size = OTA.parseInt(); - - if(!pass.equals(String(ota_pass))){ - Serial.println("ERROR: Wrong Password"); - return; - } - - 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)){ - Update.printError(Serial); - return; - } - - WiFiUDP::stopAll(); - WiFiClient::stopAll(); - - WiFiClient client; - if (client.connect(remote, port)) { - uint32_t written; - while(!Update.isFinished()){ - written = Update.write(client); - if(written > 0) client.print(written, DEC); - } - 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 (MonitorServer.hasClient()){ - if (!Monitor || !Monitor.connected()){ - if(Monitor) Monitor.stop(); - Monitor = MonitorServer.available(); - } else { - MonitorServer.available().stop(); - } - } - if (Monitor && Monitor.connected() && Monitor.available()){ - while(Monitor.available()) - Serial.write(Monitor.read()); - } - if(Serial.available()){ - size_t len = Serial.available(); - uint8_t * sbuf = (uint8_t *)malloc(len); - Serial.readBytes(sbuf, len); - if (Monitor && Monitor.connected()){ - Monitor.write((uint8_t *)sbuf, len); - delay(0); - } - free(sbuf); - } - delay(0); -} diff --git a/libraries/ESP8266mDNS/examples/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino b/libraries/ESP8266mDNS/examples/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino index 896c692050..618dff351f 100644 --- a/libraries/ESP8266mDNS/examples/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino +++ b/libraries/ESP8266mDNS/examples/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino @@ -38,10 +38,6 @@ const char* ap_default_psk = "esp8266esp8266"; ///< Default PSK. /// Uncomment the next line for verbose output over UART. //#define SERIAL_VERBOSE -/// OTA server handle. -ArduinoOTA ota_server; - - /** * @brief Read WiFi connection information from file system. * @param ssid String pointer for storing SSID. @@ -244,7 +240,8 @@ void setup() } // Start OTA server. - ota_server.setup(); + ArduinoOTA.setHostname((const char *)hostname.c_str()); + ArduinoOTA.begin(); } @@ -254,7 +251,7 @@ void setup() void loop() { // Handle OTA server. - ota_server.handle(); + ArduinoOTA.handle(); yield(); } diff --git a/platform.txt b/platform.txt index ca99e858c6..254bacd326 100644 --- a/platform.txt +++ b/platform.txt @@ -64,7 +64,7 @@ recipe.cpp.o.pattern="{compiler.path}{compiler.cpp.cmd}" {compiler.cpreprocessor recipe.S.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.cpreprocessor.flags} {compiler.S.flags} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.c.extra_flags} {build.extra_flags} {includes} "{source_file}" -o "{object_file}" ## Create archives -recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compiler.ar.extra_flags} "{build.path}/{archive_file}" "{object_file}" +recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compiler.ar.extra_flags} "{archive_file_path}" "{object_file}" ## Combine gc-sections, archives, and objects recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} -o "{build.path}/{build.project_name}.elf" -Wl,--start-group {object_files} "{build.path}/{archive_file}" {compiler.c.elf.libs} -Wl,--end-group "-L{build.path}" diff --git a/tools/espota.py b/tools/espota.py index cacbc12d54..a07c8d61c1 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -4,11 +4,12 @@ # https://gist.github.com/igrr/d35ab8446922179dc58c # # Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor) +# Modified since 2015-11-09 from Hristo Gochkov (https://github.com/me-no-dev) # # This script will push an OTA update to the ESP -# use it like: python espota.py -i -p -f +# use it like: python espota.py -i -p [-a password] -f # Or to upload SPIFFS image: -# python espota.py -i -p -s -f +# python espota.py -i -p [-a password] -s -f # # Changes # 2015-09-18: @@ -16,6 +17,11 @@ # - Add logging. # - Send command to controller to differ between flashing and transmitting SPIFFS image. # +# Changes +# 2015-11-09: +# - Added digest authentication +# - Enchanced error tracking and reporting +# from __future__ import print_function import socket @@ -23,6 +29,7 @@ import os import optparse import logging +import hashlib # Commands FLASH = 0 @@ -43,14 +50,55 @@ def serve(remoteAddr, remotePort, password, filename, command = FLASH): return 1 content_size = os.path.getsize(filename) + f = open(filename,'rb') + file_md5 = hashlib.md5(f.read()).hexdigest() + f.close() logging.info('Upload size: %d', content_size) - message = '%s %d %d %d\n' % (password, command, serverPort, content_size) + message = '%d %d %d %s\n' % (command, serverPort, content_size, file_md5) # Wait for a connection logging.info('Sending invitation to: %s', remoteAddr) sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) remote_address = (remoteAddr, int(remotePort)) sent = sock2.sendto(message, remote_address) + sock2.settimeout(10) + try: + data = sock2.recv(37) + except: + logging.error('No Answer') + sock2.close() + return 1 + if (data != "OK"): + if(data.startswith('AUTH')): + nonce = data.split()[1] + cnonce_text = '%s%u%s%s' % (filename, content_size, file_md5, remoteAddr) + cnonce = hashlib.md5(cnonce_text).hexdigest() + passmd5 = hashlib.md5(password).hexdigest() + result_text = '%s:%s:%s' % (passmd5 ,nonce, cnonce) + result = hashlib.md5(result_text).hexdigest() + sys.stderr.write('Authenticating...') + sys.stderr.flush() + message = '%s %s\n' % (cnonce, result) + sock2.sendto(message, remote_address) + sock2.settimeout(10) + try: + data = sock2.recv(32) + except: + sys.stderr.write('FAIL\n') + logging.error('No Answer to our Authentication') + sock2.close() + return 1 + if (data != "OK"): + sys.stderr.write('FAIL\n') + logging.error('%s', data) + sock2.close() + sys.exit(1); + return 1 + sys.stderr.write('OK\n') + else: + logging.error('Bad Answer: %s', data) + sock2.close() + return 1 sock2.close() logging.info('Waiting for device...') @@ -94,9 +142,13 @@ def serve(remoteAddr, remotePort, password, filename, command = FLASH): connection.close() f.close() sock.close() + if (data != "OK"): + sys.stderr.write('\n') + logging.error('%s', data) + return 1; return 0 except: - logging.error('Result: No Answer!') + logging.error('No Result!') connection.close() f.close() sock.close() From 13b8cc0a27711616bd3071551a9346744bcb7019 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Mon, 9 Nov 2015 01:47:51 +0200 Subject: [PATCH 4/6] Add sanity check so we do not trigger an update from wrong data --- libraries/ArduinoOTA/ArduinoOTA.cpp | 19 ++++++++++++++++++- tools/espota.py | 3 ++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/libraries/ArduinoOTA/ArduinoOTA.cpp b/libraries/ArduinoOTA/ArduinoOTA.cpp index e9e0a3464b..fd24bf1001 100644 --- a/libraries/ArduinoOTA/ArduinoOTA.cpp +++ b/libraries/ArduinoOTA/ArduinoOTA.cpp @@ -6,6 +6,8 @@ //#define OTA_DEBUG 1 +#define U_AUTH 200 + ArduinoOTAClass::ArduinoOTAClass() { _udp_ota = new WiFiUDP(); @@ -169,12 +171,17 @@ void ArduinoOTAClass::handle() { if (!_udp_ota->parsePacket()) return; if(_state == OTA_IDLE){ + int cmd = _udp_ota->parseInt(); + if(cmd != U_FLASH && cmd != U_SPIFFS) + return; _ota_ip = _udp_ota->remoteIP(); - _cmd = _udp_ota->parseInt(); + _cmd = cmd; _ota_port = _udp_ota->parseInt(); _size = _udp_ota->parseInt(); _udp_ota->read(); sprintf(_md5, "%s", _udp_ota->readStringUntil('\n').c_str()); + if(strlen(_md5) != 32) + return; #if OTA_DEBUG Serial.print("Update Start: ip:"); @@ -199,8 +206,18 @@ void ArduinoOTAClass::handle() { _state = OTA_RUNUPDATE; } } else if(_state == OTA_WAITAUTH){ + int cmd = _udp_ota->parseInt(); + if(cmd != U_AUTH){ + _state = OTA_IDLE; + return; + } + _udp_ota->read(); String cnonce = _udp_ota->readStringUntil(' '); String response = _udp_ota->readStringUntil('\n'); + if(cnonce.length() != 32 || response.length() != 32){ + _state = OTA_IDLE; + return; + } MD5Builder _passmd5; _passmd5.begin(); diff --git a/tools/espota.py b/tools/espota.py index a07c8d61c1..c03e1c4a70 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -34,6 +34,7 @@ # Commands FLASH = 0 SPIFFS = 100 +AUTH = 200 def serve(remoteAddr, remotePort, password, filename, command = FLASH): @@ -78,7 +79,7 @@ def serve(remoteAddr, remotePort, password, filename, command = FLASH): result = hashlib.md5(result_text).hexdigest() sys.stderr.write('Authenticating...') sys.stderr.flush() - message = '%s %s\n' % (cnonce, result) + message = '%d %s %s\n' % (AUTH, cnonce, result) sock2.sendto(message, remote_address) sock2.settimeout(10) try: From d3628f5a01ca670f8649b7cd21e1118848fe7499 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Mon, 9 Nov 2015 01:59:13 +0200 Subject: [PATCH 5/6] Learn to live with a warning so older versions do not fail --- platform.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform.txt b/platform.txt index 254bacd326..ca99e858c6 100644 --- a/platform.txt +++ b/platform.txt @@ -64,7 +64,7 @@ recipe.cpp.o.pattern="{compiler.path}{compiler.cpp.cmd}" {compiler.cpreprocessor recipe.S.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.cpreprocessor.flags} {compiler.S.flags} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.c.extra_flags} {build.extra_flags} {includes} "{source_file}" -o "{object_file}" ## Create archives -recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compiler.ar.extra_flags} "{archive_file_path}" "{object_file}" +recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compiler.ar.extra_flags} "{build.path}/{archive_file}" "{object_file}" ## Combine gc-sections, archives, and objects recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} -o "{build.path}/{build.project_name}.elf" -Wl,--start-group {object_files} "{build.path}/{archive_file}" {compiler.c.elf.libs} -Wl,--end-group "-L{build.path}" From 950e9c9522298f96f850b4d2592328ef9c95cc9a Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Mon, 9 Nov 2015 02:38:20 +0200 Subject: [PATCH 6/6] Let the socket to properly close python was keeping the connection open and sending duplicate FINs until the ESP came back online, because the ESP was resetting without giving the network a chance to answer and close --- libraries/ArduinoOTA/ArduinoOTA.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/ArduinoOTA/ArduinoOTA.cpp b/libraries/ArduinoOTA/ArduinoOTA.cpp index fd24bf1001..4014c43840 100644 --- a/libraries/ArduinoOTA/ArduinoOTA.cpp +++ b/libraries/ArduinoOTA/ArduinoOTA.cpp @@ -144,6 +144,8 @@ void ArduinoOTAClass::_runUpdate(){ if(Update.end()){ client.print("OK"); + client.stop(); + delay(10); #if OTA_DEBUG Serial.printf("Update Success\nRebooting...\n"); #endif