1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-21 10:26:06 +03:00

Merge pull request #980 from me-no-dev/master

IDE OTA Upload changes
This commit is contained in:
Ivan Grokhotkov 2015-11-10 23:34:11 +03:00
commit 450718b4a4
16 changed files with 615 additions and 273 deletions

View File

@ -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<len; i+=2) tmp[i/2] = (hex_char_to_byte(data[i]) & 0x0F) << 4 | (hex_char_to_byte(data[i+1]) & 0x0F);
add(tmp, len/2);
free(tmp);
}
void MD5Builder::calculate(void){
MD5Final(_buf, &_ctx);
}
void MD5Builder::getBytes(uint8_t * output){
memcpy(output, _buf, 16);
}
void MD5Builder::getChars(char * output){
for(uint8_t i = 0; i < 16; i++)
sprintf(output + (i * 2), "%02x", _buf[i]);
}
String MD5Builder::toString(void){
char out[32];
getChars(out);
return String(out);
}

View File

@ -0,0 +1,47 @@
/*
md5.h - exposed md5 ROM functions for esp8266
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __ESP8266_MD5_BUILDER__
#define __ESP8266_MD5_BUILDER__
#include "Arduino.h"
#include "md5.h"
class MD5Builder {
private:
md5_context_t _ctx;
uint8_t _buf[16];
public:
void begin(void);
void add(uint8_t * data, uint16_t len);
void add(const char * data){ add((uint8_t*)data, strlen(data)); }
void add(char * data){ add((const char*)data); }
void add(String data){ add(data.c_str()); }
void addHexString(const char * data);
void addHexString(char * data){ addHexString((const char*)data); }
void addHexString(String data){ addHexString(data.c_str()); }
void calculate(void);
void getBytes(uint8_t * output);
void getChars(char * output);
String toString(void);
};
#endif

View File

@ -32,6 +32,7 @@ void UpdaterClass::_reset() {
_currentAddress = 0; _currentAddress = 0;
_size = 0; _size = 0;
_command = U_FLASH; _command = U_FLASH;
_target_md5 = 0;
} }
bool UpdaterClass::begin(size_t size, int command) { bool UpdaterClass::begin(size_t size, int command) {
@ -97,9 +98,16 @@ bool UpdaterClass::begin(size_t size, int command) {
_buffer = new uint8_t[FLASH_SECTOR_SIZE]; _buffer = new uint8_t[FLASH_SECTOR_SIZE];
_command = command; _command = command;
_target_md5 = new char[64];
_md5.begin();
return true; return true;
} }
void UpdaterClass::setMD5(const char * expected_md5){
if(strlen(expected_md5) != 32) return;
strcpy(_target_md5, expected_md5);
}
bool UpdaterClass::end(bool evenIfRemaining){ bool UpdaterClass::end(bool evenIfRemaining){
if(_size == 0){ if(_size == 0){
#ifdef DEBUG_UPDATER #ifdef DEBUG_UPDATER
@ -124,6 +132,20 @@ bool UpdaterClass::end(bool evenIfRemaining){
_size = progress(); _size = progress();
} }
_md5.calculate();
if(_target_md5 && strlen(_target_md5) == 32){
if(strcmp(_target_md5, _md5.toString().c_str()) != 0){
_error = UPDATE_ERROR_MD5;
#ifdef DEBUG_UPDATER
DEBUG_UPDATER.printf("MD5 Failed: expected:%s, calculated:%s\n", _target_md5, _md5.toString().c_str());
#endif
return false;
}
#ifdef DEBUG_UPDATER
else DEBUG_UPDATER.printf("MD5 Success: %s\n", _md5.toString().c_str());
#endif
}
if (_command == U_FLASH) { if (_command == U_FLASH) {
eboot_command ebcmd; eboot_command ebcmd;
ebcmd.action = ACTION_COPY_RAW; ebcmd.action = ACTION_COPY_RAW;
@ -157,6 +179,7 @@ bool UpdaterClass::_writeBuffer(){
#endif #endif
return false; return false;
} }
_md5.add(_buffer, _bufferLen);
_currentAddress += _bufferLen; _currentAddress += _bufferLen;
_bufferLen = 0; _bufferLen = 0;
return true; return true;
@ -232,6 +255,8 @@ void UpdaterClass::printError(Stream &out){
out.println("Bad Size Given"); out.println("Bad Size Given");
} else if(_error == UPDATE_ERROR_STREAM){ } else if(_error == UPDATE_ERROR_STREAM){
out.println("Stream Read Timeout"); out.println("Stream Read Timeout");
} else if(_error == UPDATE_ERROR_MD5){
out.println("MD5 Check Failed");
} else { } else {
out.println("UNKNOWN"); out.println("UNKNOWN");
} }

View File

@ -3,6 +3,7 @@
#include "Arduino.h" #include "Arduino.h"
#include "flash_utils.h" #include "flash_utils.h"
#include "MD5Builder.h"
#define UPDATE_ERROR_OK 0 #define UPDATE_ERROR_OK 0
#define UPDATE_ERROR_WRITE 1 #define UPDATE_ERROR_WRITE 1
@ -10,11 +11,12 @@
#define UPDATE_ERROR_SPACE 3 #define UPDATE_ERROR_SPACE 3
#define UPDATE_ERROR_SIZE 4 #define UPDATE_ERROR_SIZE 4
#define UPDATE_ERROR_STREAM 5 #define UPDATE_ERROR_STREAM 5
#define UPDATE_ERROR_MD5 6
#define U_FLASH 0 #define U_FLASH 0
#define U_SPIFFS 100 #define U_SPIFFS 100
//#define DEBUG_UPDATER Serial1 //#define DEBUG_UPDATER Serial
class UpdaterClass { class UpdaterClass {
public: public:
@ -57,6 +59,21 @@ class UpdaterClass {
*/ */
void printError(Stream &out); void printError(Stream &out);
/*
sets the expected MD5 for the firmware (hexString)
*/
void setMD5(const char * expected_md5);
/*
returns the MD5 String of the sucessfully ended firmware
*/
String md5String(void){ return _md5.toString(); }
/*
populated the result with the md5 bytes of the sucessfully ended firmware
*/
void md5(uint8_t * result){ return _md5.getBytes(result); }
//Helpers //Helpers
uint8_t getError(){ return _error; } uint8_t getError(){ return _error; }
void clearError(){ _error = UPDATE_ERROR_OK; } void clearError(){ _error = UPDATE_ERROR_OK; }
@ -103,7 +120,7 @@ class UpdaterClass {
} }
if(remaining() == 0) if(remaining() == 0)
return written; return written;
yield(); delay(1);
available = data.available(); available = data.available();
} }
return written; return written;
@ -120,6 +137,9 @@ class UpdaterClass {
uint32_t _startAddress; uint32_t _startAddress;
uint32_t _currentAddress; uint32_t _currentAddress;
uint32_t _command; uint32_t _command;
char *_target_md5;
MD5Builder _md5;
}; };
extern UpdaterClass Update; extern UpdaterClass Update;

44
cores/esp8266/md5.h Normal file
View File

@ -0,0 +1,44 @@
/*
md5.h - exposed md5 ROM functions for esp8266
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
original C source from https://github.com/morrissinger/ESP8266-Websocket/raw/master/MD5.h
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __ESP8266_MD5__
#define __ESP8266_MD5__
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint32_t state[4];
uint32_t count[2];
uint8_t buffer[64];
} md5_context_t;
extern void MD5Init (md5_context_t *);
extern void MD5Update (md5_context_t *, uint8_t *, uint16_t);
extern void MD5Final (uint8_t [16], md5_context_t *);
#ifdef __cplusplus
} // extern "C"
#endif
#endif

View File

@ -2,14 +2,26 @@
#include <ESP8266mDNS.h> #include <ESP8266mDNS.h>
#include <WiFiUdp.h> #include <WiFiUdp.h>
#include "ArduinoOTA.h" #include "ArduinoOTA.h"
#include "MD5Builder.h"
ArduinoOTA::ArduinoOTA(const char *mdns_host_prefix, int port, bool serial_debug) //#define OTA_DEBUG 1
#define U_AUTH 200
ArduinoOTAClass::ArduinoOTAClass()
{ {
_port = port;
_mdns_host = new String(mdns_host_prefix);
*_mdns_host += String(ESP.getChipId(), HEX);
_udp_ota = new WiFiUDP(); _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; _start_callback = NULL;
_end_callback = NULL; _end_callback = NULL;
@ -17,104 +29,227 @@ ArduinoOTA::ArduinoOTA(const char *mdns_host_prefix, int port, bool serial_debug
_error_callback = NULL; _error_callback = NULL;
} }
void ArduinoOTA::onStart(OTA_CALLBACK(fn)){ void ArduinoOTAClass::onStart(OTA_CALLBACK(fn)){
_start_callback = fn; _start_callback = fn;
} }
void ArduinoOTA::onEnd(OTA_CALLBACK(fn)){ void ArduinoOTAClass::onEnd(OTA_CALLBACK(fn)){
_end_callback = fn; _end_callback = fn;
} }
void ArduinoOTA::onProgress(OTA_CALLBACK_PROGRESS(fn)){ void ArduinoOTAClass::onProgress(OTA_CALLBACK_PROGRESS(fn)){
_progress_callback = fn; _progress_callback = fn;
} }
void ArduinoOTA::onError(OTA_CALLBACK(fn)){ void ArduinoOTAClass::onError(OTA_CALLBACK_ERROR(fn)){
_error_callback = fn; _error_callback = fn;
} }
ArduinoOTA::~ArduinoOTA(){ ArduinoOTAClass::~ArduinoOTAClass(){
delete _udp_ota; delete _udp_ota;
delete _mdns_host;
} }
void ArduinoOTA::setup() { void ArduinoOTAClass::setPort(uint16_t port){
if(!_initialized && !_port && port){
_port = port;
}
}
void ArduinoOTAClass::setHostname(const char * hostname){
if(!_initialized && !_hostname && hostname){
_hostname = new char[strlen(hostname)];
sprintf(_hostname, "%s", hostname);
}
}
void ArduinoOTAClass::setPassword(const char * password){
if(!_initialized && !_password && password){
_password = new char[strlen(password)];
sprintf(_password, "%s", password);
}
}
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); _udp_ota->begin(_port);
if (_mdns_host) { MDNS.begin(_hostname);
if (_serial_debug) if(_password){
Serial.printf("OTA server at: %s:%u\n", _nonce = new char[33];
_mdns_host->c_str(), MDNS.enableArduino(_port, true);
_port); } else
MDNS.begin(_mdns_host->c_str()); MDNS.enableArduino(_port);
MDNS.addService("arduino", "tcp", _port); _state = OTA_IDLE;
#if OTA_DEBUG
Serial.printf("OTA server at: %s.local:%u\n", _hostname, _port);
#endif
}
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);
WiFiClient client;
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(OTA_CONNECT_ERROR);
_state = OTA_IDLE;
}
uint32_t written, total = 0;
while(!Update.isFinished() && client.connected()){
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);
total += written;
if(_progress_callback) _progress_callback(total, _size);
} }
} }
void ArduinoOTA::handle() { if(Update.end()){
client.print("OK");
client.stop();
delay(10);
#if OTA_DEBUG
Serial.printf("Update Success\nRebooting...\n");
#endif
if(_end_callback) _end_callback();
ESP.restart();
} else {
_udp_ota->begin(_port);
if (_error_callback) _error_callback(OTA_END_ERROR);
Update.printError(client);
#if OTA_DEBUG
Update.printError(Serial);
#endif
_state = OTA_IDLE;
}
}
void ArduinoOTAClass::handle() {
if (!*_udp_ota) { if (!*_udp_ota) {
_udp_ota->begin(_port); _udp_ota->begin(_port);
if (_serial_debug) { #if OTA_DEBUG
Serial.println("OTA restarted"); Serial.println("OTA restarted");
} #endif
} }
if (!_udp_ota->parsePacket()) return; if (!_udp_ota->parsePacket()) return;
IPAddress remote = _udp_ota->remoteIP(); if(_state == OTA_IDLE){
int cmd = _udp_ota->parseInt(); int cmd = _udp_ota->parseInt();
int port = _udp_ota->parseInt(); if(cmd != U_FLASH && cmd != U_SPIFFS)
int size = _udp_ota->parseInt(); return;
_ota_ip = _udp_ota->remoteIP();
_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 (_serial_debug){ #if OTA_DEBUG
Serial.print("Update Start: ip:"); Serial.print("Update Start: ip:");
Serial.print(remote); Serial.print(_ota_ip);
Serial.printf(", port:%d, size:%d\n", port, size); 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){
WiFiUDP::stopAll(); int cmd = _udp_ota->parseInt();
if(cmd != U_AUTH){
if(!Update.begin(size, cmd)){ _state = OTA_IDLE;
if (_serial_debug)
Serial.println("Update Begin Error");
if (_error_callback) _error_callback();
_udp_ota->begin(_port);
return; return;
} }
if (_start_callback) _start_callback(); _udp_ota->read();
if (_progress_callback) _progress_callback(0, size); String cnonce = _udp_ota->readStringUntil(' ');
String response = _udp_ota->readStringUntil('\n');
WiFiClient client; if(cnonce.length() != 32 || response.length() != 32){
if (!client.connect(remote, port)) { _state = OTA_IDLE;
if (_serial_debug) return;
Serial.printf("Connect Failed\n");
_udp_ota->begin(_port);
if (_error_callback) _error_callback();
} }
uint32_t written; MD5Builder _passmd5;
while(!Update.isFinished() && client.connected()){ _passmd5.begin();
// TODO(mangelajo): enhance the Update.write(client) to _passmd5.add(_password);
// accept a progress callback _passmd5.calculate();
written = Update.write(client); String passmd5 = _passmd5.toString();
if(written > 0) client.print(written, DEC);
if(_progress_callback) _progress_callback(written, size);
}
Serial.setDebugOutput(false); String challenge = passmd5 + ":" + String(_nonce) + ":" + cnonce;
MD5Builder _challengemd5;
_challengemd5.begin();
_challengemd5.add(challenge);
_challengemd5.calculate();
String result = _challengemd5.toString();
if(Update.end()){ if(result.equals(response)){
client.println("OK"); _udp_ota->beginPacket(_ota_ip, _udp_ota->remotePort());
if (_serial_debug) _udp_ota->print("OK");
Serial.printf("Update Success\nRebooting...\n"); _udp_ota->endPacket();
if(_end_callback) _end_callback(); _state = OTA_RUNUPDATE;
ESP.restart();
} else { } else {
// Update failed: listen UDP again, callback and print _udp_ota->beginPacket(_ota_ip, _udp_ota->remotePort());
_udp_ota->begin(_port); _udp_ota->print("Authentication Failed");
if (_error_callback) _error_callback(); _udp_ota->endPacket();
Update.printError(client); if (_error_callback) _error_callback(OTA_AUTH_ERROR);
if (_serial_debug) _state = OTA_IDLE;
Update.printError(Serial);
} }
} }
if(_state == OTA_RUNUPDATE)
_runUpdate();
}
ArduinoOTAClass ArduinoOTA;

View File

@ -5,31 +5,58 @@ class WiFiUDP;
#define OTA_CALLBACK(callback) void (*callback)() #define OTA_CALLBACK(callback) void (*callback)()
#define OTA_CALLBACK_PROGRESS(callback) void (*callback)(unsigned int, unsigned int) #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; int _port;
String* _mdns_host; char *_password;
char * _hostname;
char * _nonce;
WiFiUDP* _udp_ota; 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(_start_callback);
OTA_CALLBACK(_end_callback); OTA_CALLBACK(_end_callback);
OTA_CALLBACK(_error_callback); OTA_CALLBACK_ERROR(_error_callback);
OTA_CALLBACK_PROGRESS(_progress_callback); OTA_CALLBACK_PROGRESS(_progress_callback);
void _runUpdate(void);
public: public:
ArduinoOTA(const char *mdns_host="ESP8266-OTA-", ArduinoOTAClass();
int port=8266, ~ArduinoOTAClass();
bool serial_debug=true); void setPort(uint16_t port);
~ArduinoOTA(); void setHostname(const char *hostname);
void setup(); void setPassword(const char *password);
void handle();
void onStart(OTA_CALLBACK(fn)); void onStart(OTA_CALLBACK(fn));
void onEnd(OTA_CALLBACK(fn)); void onEnd(OTA_CALLBACK(fn));
void onProgress(OTA_CALLBACK_PROGRESS(fn)); void onProgress(OTA_CALLBACK_PROGRESS(fn));
void onError(OTA_CALLBACK (fn)); void onError(OTA_CALLBACK_ERROR (fn));
void begin();
void handle();
}; };
extern ArduinoOTAClass ArduinoOTA;
#endif /* __ARDUINO_OTA_H */ #endif /* __ARDUINO_OTA_H */

View File

@ -6,26 +6,36 @@
const char* ssid = "..."; const char* ssid = "...";
const char* password = "..."; const char* password = "...";
ArduinoOTA ota_server;
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
Serial.println("Booting"); Serial.println("Booting");
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
/* try the flash stored password first */
WiFi.begin();
while (WiFi.waitForConnectResult() != WL_CONNECTED){
WiFi.begin(ssid, password); WiFi.begin(ssid, password);
Serial.println("Retrying connection..."); 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"); Serial.println("Ready");
} }
void loop() { void loop() {
ota_server.handle(); ArduinoOTA.handle();
yield();
} }

View File

@ -5,9 +5,7 @@
const char* ssid = "..."; const char* ssid = "...";
const char* password = "..."; const char* password = "...";
const char* host_prefix = "OTA-LEDS-"; const char* host = "OTA-LEDS";
ArduinoOTA ota_server(host_prefix, 8266, /* debug_serial= */ true);
int led_pin = 13; int led_pin = 13;
#define N_DIMMERS 3 #define N_DIMMERS 3
@ -23,8 +21,7 @@ void setup() {
Serial.println("Booting"); Serial.println("Booting");
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
/* try the flash stored password first */ WiFi.begin(ssid, password);
WiFi.begin();
while (WiFi.waitForConnectResult() != WL_CONNECTED){ while (WiFi.waitForConnectResult() != WL_CONNECTED){
WiFi.begin(ssid, password); WiFi.begin(ssid, password);
@ -33,10 +30,6 @@ void setup() {
/* switch off led */ /* switch off led */
digitalWrite(led_pin, HIGH); digitalWrite(led_pin, HIGH);
/* setup the OTA server */
ota_server.setup();
Serial.println("Ready");
/* configure dimmers, and OTA server events */ /* configure dimmers, and OTA server events */
analogWriteRange(1000); analogWriteRange(1000);
analogWrite(led_pin,990); analogWrite(led_pin,990);
@ -47,13 +40,14 @@ void setup() {
analogWrite(dimmer_pin[i],50); 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<N_DIMMERS;i++) for(int i=0; i<N_DIMMERS;i++)
analogWrite(dimmer_pin[i], 0); analogWrite(dimmer_pin[i], 0);
analogWrite(led_pin,0); analogWrite(led_pin,0);
}); });
ota_server.onEnd([]() { // do a fancy thing with our board led at end ArduinoOTA.onEnd([]() { // do a fancy thing with our board led at end
for (int i=0;i<30;i++) for (int i=0;i<30;i++)
{ {
analogWrite(led_pin,(i*100) % 1001); analogWrite(led_pin,(i*100) % 1001);
@ -61,11 +55,14 @@ void setup() {
} }
}); });
ota_server.onError([]() { ESP.restart(); }); ArduinoOTA.onError([](ota_error_t error) { ESP.restart(); });
/* setup the OTA server */
ArduinoOTA.begin();
Serial.println("Ready");
} }
void loop() { void loop() {
ota_server.handle(); ArduinoOTA.handle();
yield();
} }

View File

@ -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_MULTICAST_TTL = 1;
static const int MDNS_PORT = 5353; static const int MDNS_PORT = 5353;
MDNSResponder::MDNSResponder() : _conn(0) { _services = 0; } MDNSResponder::MDNSResponder() : _conn(0) { _services = 0; _arduinoAuth = false; }
MDNSResponder::~MDNSResponder() {} MDNSResponder::~MDNSResponder() {}
bool MDNSResponder::begin(const char* domain){ bool MDNSResponder::begin(const char* domain){
@ -101,8 +101,6 @@ bool MDNSResponder::begin(const char* domain){
_hostName[i] = tolower(domain[i]); _hostName[i] = tolower(domain[i]);
_hostName[n] = '\0'; _hostName[n] = '\0';
os_strcpy(_boardName, ARDUINO_BOARD);
// Open the MDNS socket if it isn't already open. // Open the MDNS socket if it isn't already open.
if (!_conn) { if (!_conn) {
uint32_t ourIp = _getOurIp(); uint32_t ourIp = _getOurIp();
@ -372,7 +370,10 @@ void MDNSResponder::_parsePacket(){
return _reply(responseMask, (serviceName), (protoName), servicePort); 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){ void MDNSResponder::_reply(uint8_t replyMask, char * service, char *proto, uint16_t port){
int i; int i;
@ -439,24 +440,53 @@ void MDNSResponder::_reply(uint8_t replyMask, char * service, char *proto, uint1
// TXT Response // TXT Response
if(replyMask & 0x4){ if(replyMask & 0x4){
if(replyMask & 0x8){//send the name if(replyMask & 0x8){
uint8_t txtHead[2] = {0xC0, (uint8_t)(36 + serviceLen)}; uint8_t txtHead[10] = {
_conn->append(reinterpret_cast<const char*>(txtHead), 2); 0xC0, (uint8_t)(36 + serviceLen),//send the name
}
uint8_t boardNameLen = os_strlen(_boardName);
uint8_t txt[24] = {
0x00, 0x10, //Type TXT 0x00, 0x10, //Type TXT
0x80, 0x01, //Class IN, with cache flush 0x80, 0x01, //Class IN, with cache flush
0x00, 0x00, 0x11, 0x94, //TTL 4500 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<const char*>(txt), 17); _conn->append(reinterpret_cast<const char*>(txtHead), 10);
_conn->append(reinterpret_cast<const char*>(_boardName), boardNameLen); }
if(strcmp(reinterpret_cast<const char*>("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<const char*>((_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<const char*>(txt), 2);
_conn->append(reinterpret_cast<const char*>(&boardNameLen), 1);
_conn->append(reinterpret_cast<const char*>(boardName), boardNameLen);
_conn->append(reinterpret_cast<const char*>(&authUploadLen), 1);
_conn->append(reinterpret_cast<const char*>(authUpload), authUploadLen);
_conn->append(reinterpret_cast<const char*>(&tcpCheckExtraLen), 1);
_conn->append(reinterpret_cast<const char*>(tcpCheckExtra), tcpCheckExtraLen);
_conn->append(reinterpret_cast<const char*>(&sshUploadExtraLen), 1);
_conn->append(reinterpret_cast<const char*>(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<const char*>(txt), 2);
}
} }
// SRV Response // SRV Response

View File

@ -78,11 +78,13 @@ public:
addService(service.c_str(), proto.c_str(), port); addService(service.c_str(), proto.c_str(), port);
} }
void enableArduino(uint16_t port, bool auth=false);
private: private:
struct MDNSService * _services; struct MDNSService * _services;
UdpContext* _conn; UdpContext* _conn;
char _hostName[128]; char _hostName[128];
char _boardName[64]; bool _arduinoAuth;
uint32_t _getOurIp(); uint32_t _getOurIp();
uint16_t _getServicePort(char *service, char *proto); uint16_t _getServicePort(char *service, char *proto);

View File

@ -1,101 +0,0 @@
#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)) {
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);
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);
}

View File

@ -38,10 +38,6 @@ const char* ap_default_psk = "esp8266esp8266"; ///< Default PSK.
/// Uncomment the next line for verbose output over UART. /// Uncomment the next line for verbose output over UART.
//#define SERIAL_VERBOSE //#define SERIAL_VERBOSE
/// OTA server handle.
ArduinoOTA ota_server;
/** /**
* @brief Read WiFi connection information from file system. * @brief Read WiFi connection information from file system.
* @param ssid String pointer for storing SSID. * @param ssid String pointer for storing SSID.
@ -244,7 +240,8 @@ void setup()
} }
// Start OTA server. // Start OTA server.
ota_server.setup(); ArduinoOTA.setHostname((const char *)hostname.c_str());
ArduinoOTA.begin();
} }
@ -254,7 +251,7 @@ void setup()
void loop() void loop()
{ {
// Handle OTA server. // Handle OTA server.
ota_server.handle(); ArduinoOTA.handle();
yield(); yield();
} }

View File

@ -17,6 +17,7 @@ MDNS KEYWORD1
begin KEYWORD2 begin KEYWORD2
update KEYWORD2 update KEYWORD2
addService KEYWORD2 addService KEYWORD2
enableArduino KEYWORD2
####################################### #######################################
# Constants (LITERAL1) # Constants (LITERAL1)

View File

@ -92,18 +92,18 @@ recipe.size.regex.data=^(?:\.data|\.rodata|\.bss)\s+([0-9]+).*
tools.esptool.cmd=esptool tools.esptool.cmd=esptool
tools.esptool.cmd.windows=esptool.exe tools.esptool.cmd.windows=esptool.exe
tools.esptool.path={runtime.platform.path}/tools/esptool tools.esptool.path={runtime.platform.path}/tools/esptool
#runtime.tools.esptool.path tools.esptool.network_cmd=python
tools.esptool.network_cmd.windows=python.exe
tools.mkspiffs.cmd=mkspiffs
tools.mkspiffs.cmd.windows=mkspiffs.exe
tools.mkspiffs.path={runtime.platform.path}/tools/mkspiffs
#runtime.tools.mkspiffs.path
tools.esptool.upload.protocol=esp 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" -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=python
tools.espota.cmd.windows=python.exe tools.espota.cmd.windows=python.exe

View File

@ -4,11 +4,12 @@
# https://gist.github.com/igrr/d35ab8446922179dc58c # https://gist.github.com/igrr/d35ab8446922179dc58c
# #
# Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor) # 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 # This script will push an OTA update to the ESP
# use it like: python espota.py -i <ESP_IP_address> -p <ESP_port> -f <sketch.bin> # use it like: python espota.py -i <ESP_IP_address> -p <ESP_port> [-a password] -f <sketch.bin>
# Or to upload SPIFFS image: # Or to upload SPIFFS image:
# python espota.py -i <ESP_IP_address> -p <ESP_port> -s -f <spiffs.bin> # python espota.py -i <ESP_IP_address> -p <ESP_port> [-a password] -s -f <spiffs.bin>
# #
# Changes # Changes
# 2015-09-18: # 2015-09-18:
@ -16,6 +17,11 @@
# - Add logging. # - Add logging.
# - Send command to controller to differ between flashing and transmitting SPIFFS image. # - 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 from __future__ import print_function
import socket import socket
@ -23,13 +29,15 @@ import sys
import os import os
import optparse import optparse
import logging import logging
import hashlib
# Commands # Commands
FLASH = 0 FLASH = 0
SPIFFS = 100 SPIFFS = 100
AUTH = 200
def serve(remoteAddr, remotePort, filename, command = FLASH): def serve(remoteAddr, remotePort, password, filename, command = FLASH):
# Create a TCP/IP socket # Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverPort = 48266 serverPort = 48266
@ -43,17 +51,58 @@ def serve(remoteAddr, remotePort, filename, command = FLASH):
return 1 return 1
content_size = os.path.getsize(filename) 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) logging.info('Upload size: %d', content_size)
message = '%d %d %d\n' % (command, serverPort, content_size) message = '%d %d %d %s\n' % (command, serverPort, content_size, file_md5)
# Wait for a connection # Wait for a connection
logging.info('Sending invitation to: %s', remoteAddr) logging.info('Sending invitation to: %s', remoteAddr)
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
remote_address = (remoteAddr, int(remotePort)) remote_address = (remoteAddr, int(remotePort))
sent = sock2.sendto(message, remote_address) 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 = '%d %s %s\n' % (AUTH, 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() sock2.close()
logging.info('Waiting for device...\n') logging.info('Waiting for device...')
try: try:
sock.settimeout(10) sock.settimeout(10)
connection, client_address = sock.accept() connection, client_address = sock.accept()
@ -78,13 +127,15 @@ def serve(remoteAddr, remotePort, filename, command = FLASH):
connection.sendall(chunk) connection.sendall(chunk)
res = connection.recv(4) res = connection.recv(4)
except: except:
logging.error('\nError Uploading') sys.stderr.write('\n')
logging.error('Error Uploading')
connection.close() connection.close()
f.close() f.close()
sock.close() sock.close()
return 1 return 1
logging.info('\nWaiting for result...\n') sys.stderr.write('\n')
logging.info('Waiting for result...')
try: try:
connection.settimeout(60) connection.settimeout(60)
data = connection.recv(32) data = connection.recv(32)
@ -92,9 +143,13 @@ def serve(remoteAddr, remotePort, filename, command = FLASH):
connection.close() connection.close()
f.close() f.close()
sock.close() sock.close()
if (data != "OK"):
sys.stderr.write('\n')
logging.error('%s', data)
return 1;
return 0 return 0
except: except:
logging.error('Result: No Answer!') logging.error('No Result!')
connection.close() connection.close()
f.close() f.close()
sock.close() sock.close()
@ -131,6 +186,16 @@ def parser():
) )
parser.add_option_group(group) 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 # image
group = optparse.OptionGroup(parser, "Image") group = optparse.OptionGroup(parser, "Image")
group.add_option("-f", "--file", group.add_option("-f", "--file",
@ -190,7 +255,7 @@ def main(args):
command = SPIFFS command = SPIFFS
# end if # 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 # end main