ArduinoOTA again - ESP8266

I am stuck again with a strange behaviour of the ArduinoOTA functionality, this time on ESP8285 MCUs (ESP8266 with 1M flash on board).

After having worked for weeks and months, out of a sudden OTA will work once in a while only, most attempts are endig with this result:

Configuring upload protocol...
AVAILABLE: espota, esptool
CURRENT: upload_protocol = espota
Uploading .pio\build\esp8285\firmware.bin
15:51:30 [DEBUG]: Options: {'esp_ip': 'NTPtest', 'host_ip': '0.0.0.0', 'esp_port': 8266, 'host_port': 24359, 'auth': 'XXXX', 'image': '.pio\\build\\esp8285\\firmware.bin', 'spiffs': False, 'debug': True, 'progress': True}
15:51:30 [INFO]: Starting on 0.0.0.0:24359
15:51:30 [INFO]: Upload size: 325552
15:51:30 [INFO]: Sending invitation to: NTPtest
Authenticating...FAIL
15:51:40 [ERROR]: No Answer to our Authentication
*** [upload] Error 1

There was no change involving ArduinoOTA at all applied to the sketch code or build environment (Espressif8266 core 2.6.3, as was installed by PlatformIO). The ArduinoOTA onError() callback reports an OTA_BEGIN_ERROR (1) on the ESP8285.

In the rare cases of OTA working still, there is no indication of any problem.

Several other TCP services are running okay as before, like Telnet, Fauxmo (Philips Hue emulation) and a Modbus server. The same sketch is doing OTA nicely on another’s device in his network.

I uninstalled and installed again the ESP8266 framework to no avail - I doubted it would change a thing, but did not want to let anything untested.

The only change I am aware of is that my router (an AVM Fritz.box 6591) got an OS update from 7.13 to 7.21 recently.

Concluding, indications are it may be a network issue, but I have no idea left as of where to look.

Sugegstions, anyone? Pleeeez… :wink:

If I omit the OTA password, the upload is successful every time.

I did some more investigation and it looks like the MD5 hash espota.py is using does not match that the ArduinoOTA from the ESP8266 core is generating. This has been working previously, so either one must have been changed recently.

Is there a way to see when the last updates of the respective files were? The OS time stamps are 2020-31-07 for both, but these files must have been working until about 3-4 weeks ago.

I moved the code to a NodeMCU unit to have Serial output. I snapped the UDP packets going back and forth, but if the second hadn’t AUTH on it, I could not make heads or tails from it.

First packet espota.py → NodeMCU:

0000   48 3f da 7e 48 34 30 85 a9 40 0b 23 08 00 45 00   H?.~H40..@.#..E.
0010   00 4c e8 16 00 00 80 11 00 00 c0 a8 b2 2a c0 a8   .L...........*..
0020   b2 46 de da 20 4a 00 38 e6 0b 30 20 33 34 31 34   .F.. J.8..0 3414
0030   33 20 33 32 33 31 35 32 20 34 61 34 66 34 66 36   3 323152 4a4f4f6
0040   39 36 31 32 66 63 38 32 64 38 62 39 61 39 32 30   9612fc82d8b9a920
0050   36 32 65 36 37 64 33 65 61 0a                     62e67d3ea.

Response:

0000   30 85 a9 40 0b 23 48 3f da 7e 48 34 08 00 45 00   0..@.#H?.~H4..E.
0010   00 41 00 26 00 00 ff 11 d5 c3 c0 a8 b2 46 c0 a8   .A.&.........F..
0020   b2 2a 20 4a de da 00 2d 66 e7 41 55 54 48 20 62   .* J...-f.AUTH b
0030   30 32 61 61 33 39 63 33 36 33 37 30 35 39 38 64   02aa39c36370598d
0040   31 66 31 35 31 30 31 30 63 66 38 31 66 31 34      1f151010cf81f14

And a final packet from espota.py

0000   48 3f da 7e 48 34 30 85 a9 40 0b 23 08 00 45 00   H?.~H40..@.#..E.
0010   00 62 e8 17 00 00 80 11 00 00 c0 a8 b2 2a c0 a8   .b...........*..
0020   b2 46 de da 20 4a 00 4e e6 21 32 30 30 20 38 36   .F.. J.N.!200 86
0030   64 62 61 64 36 64 38 34 66 34 36 31 35 35 38 33   dbad6d84f4615583
0040   33 31 38 30 31 34 32 35 37 61 62 39 64 33 20 32   318014257ab9d3 2
0050   63 34 38 31 38 36 66 32 30 35 31 66 36 30 66 32   c48186f2051f60f2
0060   63 31 30 31 65 63 34 37 38 38 61 62 38 64 31 0a   c101ec4788ab8d1.

Now only the error message follows. The NodeMCU gives me “End failed” as compared to the ESP8285’s “Begin failed”.

Various online MD5 calculators are giving me ad347226dcc1d205f58e693925c28783 for the given password XXXX, but I do not see that in the packet sent as response to the AUTH packet.

1 Like

The protocol is a bit more complicate here, see code

The first packet is parsed with the command number, which must be one of

followed by the OTA port number, the length of the firwmare and an MD5 hash. So that’s what’s in

0 → U_FLASH
34143 → OTA port
323152 → Firmware update length
4a4f4f69612fc82d8b9a92062e67d3ea → MD5 Hash of firmware update data (not authentication).

The hash is then just internally saved as _md5. Since the _password was previously set by calling ArduinoOTAClass::setPassword or setPasswordHash(), it triggers this code

Which uses the micros() value as a “random value”, MD5’s the String represenetation of that number, and sends it back with the “AUTH” prefix. So that’s the packet

And b02aa39c36370598d1f151010cf81f14 is the hash of micros() at that point there, and it saves the hash as _nonce.

Then it waits for another packet and does

So here it reads two strings, the first space seperated and then the rest until the newline.

For the packet

It reads out the values
86dbad6d84f4615583318014257ab9d3 → cnonce
2c48186f2051f60f2c101ec4788ab8d1 → response

It he then constructs the string

String challenge = _password + ':' + String(_nonce) + ':' + cnonce;

where _password is already the MD5 of the original hash and _nonce was the nonce generated in the first answer, so in this case b02aa39c36370598d1f151010cf81f14 . So the final challenge is

(internal password hash):b02aa39c36370598d1f151010cf81f14:86dbad6d84f4615583318014257ab9d3  

The string is then MD5 hashed and compared to response, which is 2c48186f2051f60f2c101ec4788ab8d1.

This in principle is a basic form of a challenge-response protocol (https://www.incibe-cert.es/en/blog/password-based-authentication).

The ESP8266 OTA logic, if it internally has the MD5 hash of the password, generates a random number “nonce” and sends that, then asks esptool.py to

  • generate an arbitrary cnonce (client nonce)
  • calculate the string MD5 (MD5(original_password) + ":" + nonce + ":" cnonce))
  • and send it back to comparison

This enables the EPS8266 to also calculate the same string (since it has its original password and receives cnonce from the client again) and check whether they get the same result. If they do not get the same result, then esptool.py must have used a different value for original_password and thus the hashes don’t match. If esptool.py does all the calculations correctly, the hashes must turn out correct.

So for your example, if the password is ASCII XXXX and the ESP8266 sends back b02aa39c36370598d1f151010cf81f14 as the _nonce, and esptool.py has chosen 86dbad6d84f4615583318014257ab9d3 as its cnonce, then it should compute

MD5("XXXX") + ":" + "b02aa39c36370598d1f151010cf81f14" + ":" + "86dbad6d84f4615583318014257ab9d3"
= ad347226dcc1d205f58e693925c28783:b02aa39c36370598d1f151010cf81f14:86dbad6d84f4615583318014257ab9d3 

and the hash of that that again is 2c48186f2051f60f2c101ec4788ab8d1 per https://md5generator.de/.

So esptool.py does everything correctly here because we can also see it that that’s what it sends as the response hash

Since the ESP8266 is still rejecting it, it either generally calculates something wrong in this protocol (which is highly unlikely and also there haven’t been changes to the ArduinoOTA library since 8 months, History for libraries/ArduinoOTA - esp8266/Arduino · GitHub) or it internally has a different saved password(hash).

Are you able to reproduce this in a minimal example that any OTA uploads that are password protected fail? With what exact platformio.ini and code is that?

Hi Max, thank you for being the one helping me again! :+1:

The sketch is quite small already, as I am using it to nail down exactly this issue. It will print out the current time every 10s only - you might have to fill in another NTP server in the fist #define:

// Copyright 2021 blah

#include <Arduino.h>
#include <ArduinoOTA.h>
#include <time.h>
#include <ESP8266WiFi.h>

// NTP definitions
#define MY_NTP_SERVER "fritz.box"
#define MY_TZ "CET-1CEST-2,M3.5.0/2:00,M10.5.0/3:00"

#define C_SSID "xxxxx"
#define C_PWD  "xxxxx"

#define WHOAMI "OTAtest"

// -----------------------------------------------------------------------------
// Setup WiFi
// -----------------------------------------------------------------------------
void wifiSetup(const char *hostname) {
  // Set WIFI module to STA mode
  WiFi.mode(WIFI_STA);
  WiFi.hostname(hostname);

  // Connect
  WiFi.begin(C_SSID, C_PWD);

  // Wait for connection. ==> We will hang here in RUN mode forever without a WiFi!
  while (WiFi.status() != WL_CONNECTED) {
    delay(50);
  }
}

// -----------------------------------------------------------------------------

void setup() {
  // STart WiFI
  wifiSetup(WHOAMI);

  // ArduinoOTA setup
  ArduinoOTA.setHostname(WHOAMI);  // Set OTA host name
  ArduinoOTA.setPassword("XXXX");  // Set OTA password
  ArduinoOTA.begin();               // start OTA scan
  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_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });

  // Start NTP
  configTime(MY_TZ, MY_NTP_SERVER); 

  // Init Serial
  Serial.begin(115200);
  Serial.println(".");
  Serial.println("__OK__");
}

void loop() {
  static uint32_t tick = millis();

  // Check for OTA update requests
  ArduinoOTA.handle();

  if (millis() - tick > 10000) {
    time_t now = time(NULL);
    tm tm;
    localtime_r(&now, &tm);
    Serial.printf("%02d:%02d:%02d\n", tm.tm_hour, tm.tm_min, tm.tm_sec);
    tick = millis();
  }
}

The corresponding platformio.ini is this:

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
board_build.flash_mode = dout
board_build.ldscript = eagle.flash.1m64.ld
monitor_speed = 115200
monitor_filters = esp8266_exception_decoder
build_flags = 
    -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -DOTA_DEBUG=Serial
upload_protocol = espota
upload_port = OTAtest
upload_flags = 
	--port=8266
	--auth=XXXX

I had a fellow developer test it in his environment and it worked without a flaw, including OTA.

I can also confirm that this works.

First flashing normally via serial and then via OTA gives

CURRENT: upload_protocol = espota
"c:\users\max\appdata\local\programs\python\python38\python.exe" "C:\Users\Max\.platformio\packages\framework-arduinoespressif8266\tools\espota.py" --port=8266 --auth=XXXX --debug --progress -i OTAtest -f .pio\build\nodemcuv2\firmware.bin
17:15:52 [DEBUG]: Options: {'esp_ip': 'OTAtest', 'host_ip': '0.0.0.0', 'esp_port': 8266, 'host_port': 25846, 'auth': 'XXXX', 'image': '.pio\\build\\nodemcuv2\\firmware.bin', 'spiffs': False, 'debug': True, 'progress': True}
17:15:52 [INFO]: Starting on 0.0.0.0:25846
17:15:52 [INFO]: Upload size: 323360
17:15:52 [INFO]: Sending invitation to: OTAtest
Authenticating...OK
17:15:52 [INFO]: Waiting for device...


Uploading: [                                                            ] 0% 
Uploading: [                                                            ] 0%
Uploading: [=                                                           ] 0%
..
Uploading: [============================================================] 99%
Uploading: [============================================================] 100% Done...


17:16:03 [INFO]: Waiting for result...
17:16:04 [INFO]: Result: OK

And the serial output

17:15:37
17:15:47
Update Success
Rebooting...

 ets Jan  8 2013,rst cause:2, boot mode:(3,6)

load 0x4010f000, len 3584, room 16 
tail 0
chksum 0xb0
csum 0xb0
v2843a5ac
@cp:0
ld
.
__OK__
17:16:28
17:16:38
17:16:48

looks good too.

But I did comment out the lines

to just get my standard 4MB + DIO NodeMCUv2 settings (which is my target board, since I see that using

py>esptool.py flash_id
esptool.py v3.0
Found 2 serial ports
Serial port COM9
Connecting....
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: 60:01:94:0d:XX:XX
Uploading stub...
Running stub...
Stub running...
Manufacturer: ef
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

)

Sounds more like a flash configuration issue to me?

Whith what exact platformio.ini and chip do you expect the OTA to work but it doesn’t?

My “regular” platformio.ini is:

[env:esp8285]
platform = espressif8266
board = esp8285
framework = arduino
lib_deps = 
	FauxmoESP=https://github.com/vintlabs/fauxmoESP
	eModbus=https://github.com/eModbus/eModbus
board_build.flash_mode = dout
board_build.ldscript = eagle.flash.1m64.ld
monitor_speed = 115200
monitor_filters = esp8266_exception_decoder
build_flags = 
    -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH 
    -DLOG_LEVEL=LOG_LEVEL_ERROR
	-DDEVICETYPE=2
# upload_speed = 9600
# upload_flags = --no-stub
upload_protocol = espota
upload_port = 192.168.178.25
upload_flags = 
	--port=8266
	--auth="XXXX"

The devices are Chinese smart plugs with (“Gosund SP-1” and “Maxcio DE-W004” brands) equipped with an ESP8285. These devices do have 1MB flash, and my application is using a LittleFS file system - therefore the 64kB.
These used to work flawlessly with the above platformio.ini; I only picked a NodeMCU for testing to have Serial output, as the smart plug devices are using all their GPIOs for other purposes.

You are right in that this device has a 4MB flash as well:

esptool.py v2.6
Found 1 serial ports
Serial port COM3
Connecting....
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
MAC: 48:3f:da:7e:48:34
Uploading stub...
Running stub...
Stub running...
Manufacturer: 20
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

Unfortunately I cannot use esptool on the smart plug devices to verify and have to rely on findings on the Internet. So far everything was fine…

So do you have that exact device that won’t accept OTA updates physically there, as a reproducable case?

Also these mDNS names might be dangererous if multiple devices it, or someone forgot about a test device.

Yes, I do. One of each brand, to be precise. The other 10+ are on site and running. This exactly is the reason I need OTA in the first row.

Sure, but each device has a different name. The “NTPtest” f.i. is another than the “OTAtest” I am currently using. WiFi host name and ArduinoOTA host name are identical, if that matters.

By the way: commenting out the two lines like you did does not change a bit, unfortunately.

Btw there’s also another log level you can activate for the underlying updater logic:

So if you have access to one device + serial output can you can maybe further post output with that

build_flags = 
    -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH 
    -DLOG_LEVEL=LOG_LEVEL_ERROR
    -DDDEVICETYPE=2
    -DDEBUG_UPDATER=Serial
    -DDEBUG_ESP_PORT=Serial 

This is what I got (the “cnonce=” line was put in by me in ArduinoOTA.cpp):

r$dÜź|â–’#â–’N
â–’
$â–’
#|â–’â–’â–’â–’{â–’"â–’câ–’â–’onâ–’lNoâ–’â–’â–’bpâ–’â–’c$`rlpâ–’oâ–’
â–’
lâ–’â–’
c
oâ–’|â–’lâ–’â–’pâ–’â–’onâ–’dâ–’â–’d`â–’o
$`nrâ–’â–’â–’N
bl {â–’â–’n
cl â–’#
â–’â–’$â–’lrâ–’ â–’oâ–’
SDK:2.2.2-dev(38a443e)/Core:2.7.3-3-g2843a5ac=20703003/lwIP:STABLE-2_1_2_RELEASE/glue:1.2-30-g92add50/BearSSL:5c771be
.
__OK__
pm open,type:2 0
18:23:13
18:23:23
18:23:33
18:23:43
18:23:53
cnonce='0022cb63a5e674c4db069a8a8740bcf1' response='5bd753eea9944e171f20b6c16414834c'
sleep disable
[begin] roundedSize:       0x00050000 (327680)
[begin] updateEndAddress:  0x00300000 (3145728)
[begin] currentSketchSize: 0x00050000 (327680)
[begin] _startAddress:     0x002B0000 (2818048)
[begin] _currentAddress:   0x002B0000 (2818048)
[begin] _size:             0x0004FCB0 (326832)
premature end: res:0, pos:0/326832
Error[4]: End Failed
ERROR[0]: No Error
18:24:10

Interesting. Stems from

So we also see that res:0 is the return value of getError(), but it’s… 0, which maps to

UPDATE_ERROR_OK. So something else must be going on.

Also the pos:0/326832 indicates that progress() is 0 which should have triggered

but then we should see error value 13.

Seems like a premature end before it even started.

What did the espota uploader tool output for that upload?

This:

AVAILABLE: espota, esptool
CURRENT: upload_protocol = espota
Uploading .pio\build\nodemcuv2\firmware.bin
18:48:49 [DEBUG]: Options: {'esp_ip': 'OTAtest', 'host_ip': '0.0.0.0', 'esp_port': 8266, 'host_port': 56680, 'auth': 'XXXX', 'image': '.pio\\build\\nodemcuv2\\firmware.bin', 'spiffs': False, 'debug': True, 'progress': True}
18:48:49 [INFO]: Starting on 0.0.0.0:56680
18:48:49 [INFO]: Upload size: 326832
18:48:49 [INFO]: Sending invitation to: OTAtest
Authenticating...FAIL
18:48:59 [ERROR]: No Answer to our Authentication
*** [upload] Error 1

I would expect at least a message from ArduinoOTA to espota.py to start transmitting - but there was none after the authenttication has been sent.

Hm an auth error should at least trigger this code

But it seems that nothing was sent back at all?

Can you put some print statements in this code

to see which path it takes?

It took the very first option and went into _runUpdate():

rllâ–’â–’|â–’dâ–’|
â–’
lâ–’
b|â–’â–’â–’â–’â–’sâ–’#â–’p
â–’â–’onâ–’$nnâ–’â–’â–’
#pâ–’â–’lr$rlpâ–’oâ–’
â–’l
â–’â–’
bnâ–’|â–’lâ–’
â–’pâ–’â–’ooâ–’$â–’â–’$ â–’onl`nrâ–’â–’â–’â–’
bl {â–’â–’o
#$ â–’b
â–’â–’lrâ–’`â–’oâ–’
SDK:2.2.2-dev(38a443e)/Core:2.7.3-3-g2843a5ac=20703003/lwIP:STABLE-2_1_2_RELEASE/glue:1.2-30-g92add50/BearSSL:5c771be
.
__OK__
pm open,type:2 0
19:08:57
19:09:07
19:09:17
19:09:27
to OTA_RUNUPDATE
In _runUpdate()
sleep disable
[begin] roundedSize:       0x00050000 (327680)
[begin] updateEndAddress:  0x00300000 (3145728)
[begin] currentSketchSize: 0x00050000 (327680)
[begin] _startAddress:     0x002B0000 (2818048)
[begin] _currentAddress:   0x002B0000 (2818048)
[begin] _size:             0x0004FCC0 (326848)
premature end: res:0, pos:0/326848
Error[4]: End Failed
ERROR[0]: No Error
19:09:41

My bad, can you use

build_flags = 
    -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH 
    -DLOG_LEVEL=LOG_LEVEL_ERROR
    -DDDEVICETYPE=2
    -DDEBUG_UPDATER=Serial
    -DDEBUG_ESP_PORT=Serial 
    -DOTA_DEBUG=Serial 

instead and rerun the experiment? There are still debug messages in the ArduinoOTA logic.

I had the -DOTA_DEBUG=Serial line in all the time already.

I put several log lines into _runUpdate() and got:

{llÜž|â–’$â–’|
â–’
lâ–’
b|â–’â–’â–’â–’2â–’râ–’â–’>â–’bâ–’â–’nNâ–’lnoâ–’â–’â–’cpâ–’${lr$pâ–’nâ–’
â–’
lâ–’â–’
boâ–’|â–’lâ–’â–’p
â–’â–’noâ–’lâ–’â–’l â–’nnl`nrâ–’â–’â–’n
cl`rŰ’o
b$ â–’c
â–’â–’$â–’lrâ–’`â–’oâ–’
SDK:2.2.2-dev(38a443e)/Core:2.7.3-3-g2843a5ac=20703003/lwIP:STABLE-2_1_2_RELEASE/glue:1.2-30-g92add50/BearSSL:5c771be
.
__OK__
pm open,type:2 0
19:19:08
19:19:18
19:19:28
to OTA_RUNUPDATE
In _runUpdate()
sleep disable
[begin] roundedSize:       0x00050000 (327680)
[begin] updateEndAddress:  0x00300000 (3145728)
[begin] currentSketchSize: 0x00050000 (327680)
[begin] _startAddress:     0x002B0000 (2818048)
[begin] _currentAddress:   0x002B0000 (2818048)
[begin] _size:             0x0004FDB0 (327088)
Update.begin() OK
Connect about to begin
Connect OK
while loop ended written=0 total=0
premature end: res:0, pos:0/327088
Update.end() NOT OK
Error[4]: End Failed
ERROR[0]: No Error
19:19:43
19:19:53

So in the central while() loop nothing is received - matching my Wireshark observation of nothing being sent any more after the authentication packet.

So there’s a network problem from the ESP8285 side to the PC? Because in start update it should at least trigger the UDP packet

to be sent from the ESP8285 to the PC. Can you put a print statement there to verify that the ESP8285 tries to send this packet, but confirm via wireshark that no such packet is seen?

The statement was already in - the “Update.begin() OK” line is printed immediately before the append().

Wireshark has


for the NodeMCU IP only.

Hm the last UDP packet from port 8266 to the PC should be that OK line.

What happens if you increase that delay(100); to something bigger like 1000? Or add the code

  while(_udp_ota->next()) _udp_ota->flush();

after it which they seem to be doing above to flush stuff?

It could also be that just that packet is lost because it’s still UDP and maybe sending it multiple times helps?

I did both - no difference at all.

And why does all this only happen if a password is set? If it were a communications issue, wouldn’t the non-password update fail as well?

For illustration: here is an update without password set:

{llâ–’â–’|â–’$â–’|
â–’
$â–’
#|â–’â–’â–’{â–’bâ–’
#â–’â–’onâ–’$nNâ–’â–’##pâ–’${lr$pâ–’nâ–’
â–’
$
â–’â–’
b
oâ–’|â–’lâ–’â–’â–’pâ–’â–’ooâ–’$â–’â–’$`â–’nl`n{â–’Ű’o
"$ rۓo
b$ â–’c
â–’â–’$râ–’ â–’oâ–’
SDK:2.2.2-dev(38a443e)/Core:2.7.3-3-g2843a5ac=20703003/lwIP:STABLE-2_1_2_RELEASE/glue:1.2-30-g92add50/BearSSL:5c771be
.
__OK__
pm open,type:2 0
19:42:49
19:42:59
19:43:09
19:43:19
19:43:29
In _runUpdate()
sleep disable
[begin] roundedSize:       0x00050000 (327680)
[begin] updateEndAddress:  0x00300000 (3145728)
[begin] currentSketchSize: 0x00050000 (327680)
[begin] _startAddress:     0x002B0000 (2818048)
[begin] _currentAddress:   0x002B0000 (2818048)
[begin] _size:             0x0004FD50 (326992)
Update.begin() OK
Connect about to begin
Connect OK
Header: 0xE9 1 2 40
while loop ended written=1412 total=326992
MD5 Success: ba87bb55285690ba1ba6bfe1cc1c33fc
Staged: address:0x002B0000, size:0x0004FD50
Update.end() OK
Update Success
Rebooting...
state: 5 -> 0 (0)
rm 0
pm close 7
del if0
usl

 ets Jan  8 2013,rst cause:2, boot mode:(3,7)

load 0x4010f000, len 3584, room 16
tail 0
chksum 0xb0
csum 0xb0
v2843a5ac
@cp:0

In Wireshark I see the “OK” packet as well:

0000   30 85 a9 40 0b 23 48 3f da 7e 48 34 08 00 45 00   0..@.#H?.~H4..E.
0010   00 1e 00 1e 00 00 ff 11 d5 ee c0 a8 b2 46 c0 a8   .............F..
0020   b2 2a 20 4a df f7 00 0a ca 8a 4f 4b 00 00 00 00   .* J......OK....
0030   00 00 00 00 00 00 00 00 00 00 00 00               ............