├── LICENSE ├── README.md ├── ArduinoOTA.h ├── main.ino └── ArduinoOTA.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 erkobg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :exclamation: Try it with PlatformIO http://platformio.org/ :exclamation: 2 | 3 | 4 | 5 | 6 | # WiFiManager-OTA 7 | 8 | It is a combination between: 9 | - **WiFiManager** https://github.com/tzapu/WiFiManager 10 | - **OTA Update** (more info here https://github.com/esp8266/Arduino/tree/master/doc/ota_updates) 11 | 12 | 13 | 1. Checks for stored Wifi and password 14 | 2. If found tries to connect 15 | 3. If successful - handles code and OTA 16 | 4. If nothing is found or connection fails - creates AP - where you can set using default address 192.168.4.1 connection parameters 17 | 18 | 19 | 20 | 21 | 22 | 23 | from https://github.com/tzapu/WiFiManager 24 | 25 | 26 | 27 | > ## How It Works 28 | > - when y> > our ESP starts up, it sets it up in Station mode and tries to connect to a previously saved Access Point 29 | > - if this > > is unsuccessful (or no previous network saved) it moves the ESP into Access Point mode and spins up a DNS and WebServer > (default i> p 192.168.4.1) 30 | > - using any wifi enabled device with a browser (computer, phone, tablet) connect to the newly created Access Point 31 | > - because > of the Cap> tive Portal and the DNS server you will either get a 'Join to network' type of popup or get any domain you try to access redirected t> o the configuration portal 32 | > - choose one of the access points scanned, enter password, click save 33 | > - ESP will try to connect. If successful, it relinquishes control back to your app. If not, reconnect to AP and reconfigure. 34 | 35 | > ## How It Looks 36 | > ![ESP8266 WiFi Captive Portal Homepage](http://i.imgur.com/YPvW9eql.png) ![ESP8266 WiFi Captive Portal Configuration](http://i.imgur.com/oicWJ4gl.png) 37 | -------------------------------------------------------------------------------- /ArduinoOTA.h: -------------------------------------------------------------------------------- 1 | #ifndef __ARDUINO_OTA_H 2 | #define __ARDUINO_OTA_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class UdpContext; 9 | 10 | typedef enum { 11 | OTA_IDLE, 12 | OTA_WAITAUTH, 13 | OTA_RUNUPDATE 14 | } ota_state_t; 15 | 16 | typedef enum { 17 | OTA_AUTH_ERROR, 18 | OTA_BEGIN_ERROR, 19 | OTA_CONNECT_ERROR, 20 | OTA_RECEIVE_ERROR, 21 | OTA_END_ERROR 22 | } ota_error_t; 23 | 24 | class ArduinoOTAClass 25 | { 26 | public: 27 | typedef std::function THandlerFunction; 28 | typedef std::function THandlerFunction_Error; 29 | typedef std::function THandlerFunction_Progress; 30 | 31 | ArduinoOTAClass(); 32 | ~ArduinoOTAClass(); 33 | 34 | //Sets the service port. Default 8266 35 | void setPort(uint16_t port); 36 | 37 | //Sets the device hostname. Default esp8266-xxxxxx 38 | void setHostname(const char *hostname); 39 | String getHostname(); 40 | 41 | //Sets the password that will be required for OTA. Default NULL 42 | void setPassword(const char *password); 43 | 44 | //Sets the password as above but in the form MD5(password). Default NULL 45 | void setPasswordHash(const char *password); 46 | 47 | //Sets if the device should be rebooted after successful update. Default true 48 | void setRebootOnSuccess(bool reboot); 49 | 50 | //This callback will be called when OTA connection has begun 51 | void onStart(THandlerFunction fn); 52 | 53 | //This callback will be called when OTA has finished 54 | void onEnd(THandlerFunction fn); 55 | 56 | //This callback will be called when OTA encountered Error 57 | void onError(THandlerFunction_Error fn); 58 | 59 | //This callback will be called when OTA is receiving data 60 | void onProgress(THandlerFunction_Progress fn); 61 | 62 | //Starts the ArduinoOTA service 63 | void begin(); 64 | 65 | //Call this in loop() to run the service 66 | void handle(); 67 | 68 | //Gets update command type after OTA has started. Either U_FLASH or U_SPIFFS 69 | int getCommand(); 70 | 71 | private: 72 | int _port; 73 | String _password; 74 | String _hostname; 75 | String _nonce; 76 | UdpContext *_udp_ota; 77 | bool _initialized; 78 | bool _rebootOnSuccess; 79 | ota_state_t _state; 80 | int _size; 81 | int _cmd; 82 | int _ota_port; 83 | IPAddress _ota_ip; 84 | String _md5; 85 | 86 | THandlerFunction _start_callback; 87 | THandlerFunction _end_callback; 88 | THandlerFunction_Error _error_callback; 89 | THandlerFunction_Progress _progress_callback; 90 | 91 | void _runUpdate(void); 92 | void _onRx(void); 93 | int parseInt(void); 94 | String readStringUntil(char end); 95 | }; 96 | 97 | #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA) 98 | extern ArduinoOTAClass ArduinoOTA; 99 | #endif 100 | 101 | #endif /* __ARDUINO_OTA_H */ 102 | -------------------------------------------------------------------------------- /main.ino: -------------------------------------------------------------------------------- 1 | #include //https://github.com/esp8266/Arduino 2 | //needed for library 3 | #include 4 | #include 5 | #include //https://github.com/tzapu/WiFiManager 6 | 7 | //#include 8 | #include 9 | #include "ArduinoOTA.h" //https://github.com/esp8266/Arduino/tree/master/libraries/ArduinoOTA 10 | 11 | void setup() { 12 | Serial.begin(115200); 13 | Serial.println("Booting"); 14 | 15 | //WiFiManager 16 | //Local intialization. Once its business is done, there is no need to keep it around 17 | WiFiManager wifiManager; 18 | 19 | //reset saved settings 20 | //wifiManager.resetSettings(); 21 | 22 | //set custom ip for portal 23 | //wifiManager.setAPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0)); 24 | 25 | //fetches ssid and pass from eeprom and tries to connect 26 | //if it does not connect it starts an access point with the specified name 27 | //here "AutoConnectAP" 28 | //and goes into a blocking loop awaiting configuration 29 | //if you like you can create AP with password 30 | //wifiManager.autoConnect("APNAME", "password"); 31 | wifiManager.autoConnect("APNAME"); 32 | //or use this for auto generated name ESP + ChipID 33 | //wifiManager.autoConnect(); 34 | 35 | 36 | //if you get here you have connected to the WiFi 37 | Serial.println("connected...yeey :)"); 38 | 39 | // Port defaults to 8266 40 | // ArduinoOTA.setPort(8266); 41 | 42 | // Hostname defaults to esp8266-[ChipID] 43 | // ArduinoOTA.setHostname("myesp8266"); 44 | 45 | // No authentication by default 46 | // ArduinoOTA.setPassword("admin"); 47 | 48 | // Password can be set with it's md5 value as well 49 | // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 50 | // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); 51 | 52 | ArduinoOTA.onStart([]() { 53 | String type; 54 | if (ArduinoOTA.getCommand() == U_FLASH) 55 | type = "sketch"; 56 | else // U_SPIFFS 57 | type = "filesystem"; 58 | 59 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() 60 | Serial.println("Start updating " + type); 61 | }); 62 | ArduinoOTA.onEnd([]() { 63 | Serial.println("\nEnd"); 64 | }); 65 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 66 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 67 | }); 68 | ArduinoOTA.onError([](ota_error_t error) { 69 | Serial.printf("Error[%u]: ", error); 70 | if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); 71 | else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); 72 | else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); 73 | else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); 74 | else if (error == OTA_END_ERROR) Serial.println("End Failed"); 75 | }); 76 | ArduinoOTA.begin(); 77 | Serial.println("Ready OTA8"); 78 | Serial.print("IP address: "); 79 | Serial.println(WiFi.localIP()); 80 | } 81 | 82 | void loop() { 83 | ArduinoOTA.handle(); 84 | //any custom code or logic goes here 85 | } 86 | -------------------------------------------------------------------------------- /ArduinoOTA.cpp: -------------------------------------------------------------------------------- 1 | #ifndef LWIP_OPEN_SRC 2 | #define LWIP_OPEN_SRC 3 | #endif 4 | #include 5 | #include 6 | #include "ArduinoOTA.h" 7 | #include "MD5Builder.h" 8 | 9 | extern "C" { 10 | #include "osapi.h" 11 | #include "ets_sys.h" 12 | #include "user_interface.h" 13 | } 14 | 15 | #include "lwip/opt.h" 16 | #include "lwip/udp.h" 17 | #include "lwip/inet.h" 18 | #include "lwip/igmp.h" 19 | #include "lwip/mem.h" 20 | #include "include/UdpContext.h" 21 | #include 22 | 23 | 24 | #ifdef DEBUG_ESP_OTA 25 | #ifdef DEBUG_ESP_PORT 26 | #define OTA_DEBUG DEBUG_ESP_PORT 27 | #endif 28 | #endif 29 | 30 | ArduinoOTAClass::ArduinoOTAClass() 31 | : _port(0) 32 | , _udp_ota(0) 33 | , _initialized(false) 34 | , _rebootOnSuccess(true) 35 | , _state(OTA_IDLE) 36 | , _size(0) 37 | , _cmd(0) 38 | , _ota_port(0) 39 | , _start_callback(NULL) 40 | , _end_callback(NULL) 41 | , _error_callback(NULL) 42 | , _progress_callback(NULL) 43 | { 44 | } 45 | 46 | ArduinoOTAClass::~ArduinoOTAClass(){ 47 | if(_udp_ota){ 48 | _udp_ota->unref(); 49 | _udp_ota = 0; 50 | } 51 | } 52 | 53 | void ArduinoOTAClass::onStart(THandlerFunction fn) { 54 | _start_callback = fn; 55 | } 56 | 57 | void ArduinoOTAClass::onEnd(THandlerFunction fn) { 58 | _end_callback = fn; 59 | } 60 | 61 | void ArduinoOTAClass::onProgress(THandlerFunction_Progress fn) { 62 | _progress_callback = fn; 63 | } 64 | 65 | void ArduinoOTAClass::onError(THandlerFunction_Error fn) { 66 | _error_callback = fn; 67 | } 68 | 69 | void ArduinoOTAClass::setPort(uint16_t port) { 70 | if (!_initialized && !_port && port) { 71 | _port = port; 72 | } 73 | } 74 | 75 | void ArduinoOTAClass::setHostname(const char * hostname) { 76 | if (!_initialized && !_hostname.length() && hostname) { 77 | _hostname = hostname; 78 | } 79 | } 80 | 81 | String ArduinoOTAClass::getHostname() { 82 | return _hostname; 83 | } 84 | 85 | void ArduinoOTAClass::setPassword(const char * password) { 86 | if (!_initialized && !_password.length() && password) { 87 | MD5Builder passmd5; 88 | passmd5.begin(); 89 | passmd5.add(password); 90 | passmd5.calculate(); 91 | _password = passmd5.toString(); 92 | } 93 | } 94 | 95 | void ArduinoOTAClass::setPasswordHash(const char * password) { 96 | if (!_initialized && !_password.length() && password) { 97 | _password = password; 98 | } 99 | } 100 | 101 | void ArduinoOTAClass::setRebootOnSuccess(bool reboot){ 102 | _rebootOnSuccess = reboot; 103 | } 104 | 105 | void ArduinoOTAClass::begin() { 106 | if (_initialized) 107 | return; 108 | 109 | if (!_hostname.length()) { 110 | char tmp[15]; 111 | sprintf(tmp, "esp8266-%06x", ESP.getChipId()); 112 | _hostname = tmp; 113 | } 114 | if (!_port) { 115 | _port = 8266; 116 | } 117 | 118 | if(_udp_ota){ 119 | _udp_ota->unref(); 120 | _udp_ota = 0; 121 | } 122 | 123 | _udp_ota = new UdpContext; 124 | _udp_ota->ref(); 125 | 126 | if(!_udp_ota->listen(*IP_ADDR_ANY, _port)) 127 | return; 128 | _udp_ota->onRx(std::bind(&ArduinoOTAClass::_onRx, this)); 129 | MDNS.begin(_hostname.c_str()); 130 | 131 | if (_password.length()) { 132 | MDNS.enableArduino(_port, true); 133 | } else { 134 | MDNS.enableArduino(_port); 135 | } 136 | _initialized = true; 137 | _state = OTA_IDLE; 138 | #ifdef OTA_DEBUG 139 | OTA_DEBUG.printf("OTA server at: %s.local:%u\n", _hostname.c_str(), _port); 140 | #endif 141 | } 142 | 143 | int ArduinoOTAClass::parseInt(){ 144 | char data[16]; 145 | uint8_t index = 0; 146 | char value; 147 | while(_udp_ota->peek() == ' ') _udp_ota->read(); 148 | while(true){ 149 | value = _udp_ota->peek(); 150 | if(value < '0' || value > '9'){ 151 | data[index++] = '\0'; 152 | return atoi(data); 153 | } 154 | data[index++] = _udp_ota->read(); 155 | } 156 | return 0; 157 | } 158 | 159 | String ArduinoOTAClass::readStringUntil(char end){ 160 | String res = ""; 161 | char value; 162 | while(true){ 163 | value = _udp_ota->read(); 164 | if(value == '\0' || value == end){ 165 | return res; 166 | } 167 | res += value; 168 | } 169 | return res; 170 | } 171 | 172 | void ArduinoOTAClass::_onRx(){ 173 | if(!_udp_ota->next()) return; 174 | ip_addr_t ota_ip; 175 | 176 | if (_state == OTA_IDLE) { 177 | int cmd = parseInt(); 178 | if (cmd != U_FLASH && cmd != U_SPIFFS) 179 | return; 180 | _ota_ip = _udp_ota->getRemoteAddress(); 181 | _cmd = cmd; 182 | _ota_port = parseInt(); 183 | _size = parseInt(); 184 | _udp_ota->read(); 185 | _md5 = readStringUntil('\n'); 186 | _md5.trim(); 187 | if(_md5.length() != 32) 188 | return; 189 | 190 | ota_ip.addr = (uint32_t)_ota_ip; 191 | 192 | if (_password.length()){ 193 | MD5Builder nonce_md5; 194 | nonce_md5.begin(); 195 | nonce_md5.add(String(micros())); 196 | nonce_md5.calculate(); 197 | _nonce = nonce_md5.toString(); 198 | 199 | char auth_req[38]; 200 | sprintf(auth_req, "AUTH %s", _nonce.c_str()); 201 | _udp_ota->append((const char *)auth_req, strlen(auth_req)); 202 | _udp_ota->send(&ota_ip, _udp_ota->getRemotePort()); 203 | _state = OTA_WAITAUTH; 204 | return; 205 | } else { 206 | _udp_ota->append("OK", 2); 207 | _udp_ota->send(&ota_ip, _udp_ota->getRemotePort()); 208 | _state = OTA_RUNUPDATE; 209 | } 210 | } else if (_state == OTA_WAITAUTH) { 211 | int cmd = parseInt(); 212 | if (cmd != U_AUTH) { 213 | _state = OTA_IDLE; 214 | return; 215 | } 216 | _udp_ota->read(); 217 | String cnonce = readStringUntil(' '); 218 | String response = readStringUntil('\n'); 219 | if (cnonce.length() != 32 || response.length() != 32) { 220 | _state = OTA_IDLE; 221 | return; 222 | } 223 | 224 | String challenge = _password + ":" + String(_nonce) + ":" + cnonce; 225 | MD5Builder _challengemd5; 226 | _challengemd5.begin(); 227 | _challengemd5.add(challenge); 228 | _challengemd5.calculate(); 229 | String result = _challengemd5.toString(); 230 | 231 | ota_ip.addr = (uint32_t)_ota_ip; 232 | if(result.equals(response)){ 233 | _udp_ota->append("OK", 2); 234 | _udp_ota->send(&ota_ip, _udp_ota->getRemotePort()); 235 | _state = OTA_RUNUPDATE; 236 | } else { 237 | _udp_ota->append("Authentication Failed", 21); 238 | _udp_ota->send(&ota_ip, _udp_ota->getRemotePort()); 239 | if (_error_callback) _error_callback(OTA_AUTH_ERROR); 240 | _state = OTA_IDLE; 241 | } 242 | } 243 | 244 | while(_udp_ota->next()) _udp_ota->flush(); 245 | } 246 | 247 | void ArduinoOTAClass::_runUpdate() { 248 | if (!Update.begin(_size, _cmd)) { 249 | #ifdef OTA_DEBUG 250 | OTA_DEBUG.println("Update Begin Error"); 251 | #endif 252 | if (_error_callback) { 253 | _error_callback(OTA_BEGIN_ERROR); 254 | } 255 | _udp_ota->listen(*IP_ADDR_ANY, _port); 256 | _state = OTA_IDLE; 257 | return; 258 | } 259 | Update.setMD5(_md5.c_str()); 260 | WiFiUDP::stopAll(); 261 | WiFiClient::stopAll(); 262 | 263 | if (_start_callback) { 264 | _start_callback(); 265 | } 266 | if (_progress_callback) { 267 | _progress_callback(0, _size); 268 | } 269 | 270 | WiFiClient client; 271 | if (!client.connect(_ota_ip, _ota_port)) { 272 | #ifdef OTA_DEBUG 273 | OTA_DEBUG.printf("Connect Failed\n"); 274 | #endif 275 | _udp_ota->listen(*IP_ADDR_ANY, _port); 276 | if (_error_callback) { 277 | _error_callback(OTA_CONNECT_ERROR); 278 | } 279 | _state = OTA_IDLE; 280 | } 281 | 282 | uint32_t written, total = 0; 283 | while (!Update.isFinished() && client.connected()) { 284 | int waited = 1000; 285 | while (!client.available() && waited--) 286 | delay(1); 287 | if (!waited){ 288 | #ifdef OTA_DEBUG 289 | OTA_DEBUG.printf("Receive Failed\n"); 290 | #endif 291 | _udp_ota->listen(*IP_ADDR_ANY, _port); 292 | if (_error_callback) { 293 | _error_callback(OTA_RECEIVE_ERROR); 294 | } 295 | _state = OTA_IDLE; 296 | } 297 | written = Update.write(client); 298 | if (written > 0) { 299 | client.print(written, DEC); 300 | total += written; 301 | if(_progress_callback) { 302 | _progress_callback(total, _size); 303 | } 304 | } 305 | } 306 | 307 | if (Update.end()) { 308 | client.print("OK"); 309 | client.stop(); 310 | delay(10); 311 | #ifdef OTA_DEBUG 312 | OTA_DEBUG.printf("Update Success\n"); 313 | #endif 314 | if (_end_callback) { 315 | _end_callback(); 316 | } 317 | if(_rebootOnSuccess){ 318 | #ifdef OTA_DEBUG 319 | OTA_DEBUG.printf("Rebooting...\n"); 320 | #endif 321 | //let serial/network finish tasks that might be given in _end_callback 322 | delay(100); 323 | ESP.restart(); 324 | } 325 | } else { 326 | _udp_ota->listen(*IP_ADDR_ANY, _port); 327 | if (_error_callback) { 328 | _error_callback(OTA_END_ERROR); 329 | } 330 | Update.printError(client); 331 | #ifdef OTA_DEBUG 332 | Update.printError(OTA_DEBUG); 333 | #endif 334 | _state = OTA_IDLE; 335 | } 336 | } 337 | 338 | void ArduinoOTAClass::handle() { 339 | if (_state == OTA_RUNUPDATE) { 340 | _runUpdate(); 341 | _state = OTA_IDLE; 342 | } 343 | } 344 | 345 | int ArduinoOTAClass::getCommand() { 346 | return _cmd; 347 | } 348 | 349 | #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA) 350 | ArduinoOTAClass ArduinoOTA; 351 | #endif 352 | --------------------------------------------------------------------------------