Http get kills ESP32 OTA

The following is a "minimal reproducible example" from a larger program that has the same problem: running the function with HTTP GET via 192.168.1.210/showTSgdd kills subsequent attempts to upload OTA (but the GET returns the expected values).

I can upload OTA just fine if I only do the root call (192.168.1.210) and don't do the /showTSgdd.

But after doing the GET, OTA uploads get to 100% and then die with the following:

Uploading: [===================] 100% Done...
19:22:31 [ERROR]: Error receiving result: timed out
Failed uploading: uploading error: exit status 1

/* 
  XIAO-ESP32S3
  default partition
  Arduino IDE v2.3.4
  ESP32 core v3.1.3
*/
#include <WiFi.h>
#include <WiFiClient.h>
#include <ArduinoOTA.h>
#include <HTTPClient.h>
#include <WebServer.h>
WebServer myServer(80);

char ssid[] = "abc";
char pass[] = "xyz";
char* TSserverGET = "http://api.thingspeak.com/channels/2879857/fields/z/last.txt?api_key=abc";

float lastGDDfromTS;

void setup() {

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  myServer.on("/", handleRoot);
  myServer.on("/showTSgdd", showTSgdd);  // get last posted values from ThingSpeak
  myServer.begin();

  ArduinoOTA.begin();
}

void loop() {
  ArduinoOTA.handle();
  myServer.handleClient();
}

int16_t readFromTS(uint8_t myField) {
  //field 4 = GDD42A, 5 = GDD42B, 6 = GDD42C
  //TSserverGET[] = "http://api.thingspeak.com/channels/2879857/fields/z/last.txt?api_key=xyz";
  //                 012345678901234567890123456789012345678901234567890
  //                 0         1         2         3         4         5
  // replace z (in position 50) with 4, 5, or 6 (from myField)
  // using brute force and ignorance method:
  switch (myField) {
    case 4:
      TSserverGET[50] = '4';
      break;
    case 5:
      TSserverGET[50] = '5';
      break;
    case 6:
      TSserverGET[50] = '6';
      break;
  }

  HTTPClient httpForGet;

  httpForGet.begin(TSserverGET);
  int16_t httpResponseCode = httpForGet.GET();

  if (httpResponseCode == 200) {
    // success
    lastGDDfromTS = httpForGet.getString().toFloat();
  } else {
    // fail
    lastGDDfromTS = 0;
  }
  httpForGet.end();

  return httpResponseCode;
}

void handleRoot() {
  myServer.send(200, "text/plain", "hello world .210 OTA test v3");
}

void showTSgdd() {
  
  char myCstr[55];

  readFromTS(4);  // returns response code (int)
  float gddA = lastGDDfromTS;
  readFromTS(5);
  float gddB = lastGDDfromTS;
  readFromTS(6);
  float gddC = lastGDDfromTS;

  snprintf(myCstr, 55, "GDD42A = %.2f GDD42B = %.2f GDD42C = %.2f", gddA, gddB, gddC);
  myServer.send(200, "text/plain", myCstr);
}

The problems doesn't seem to be related to the web server, because this has the same problem:

/* 
  XIAO-ESP32S3
  default partition
  Arduino IDE v2.3.4
  ESP32 core v3.1.3
*/
#include <WiFi.h>
#include <WiFiClient.h>
#include <ArduinoOTA.h>
#include <HTTPClient.h>

char ssid[] = "abc";
char pass[] = "123";
char* TSserverGET = "http://api.thingspeak.com/channels/2879857/fields/z/last.txt?api_key=abc";
// the following code replaces field "z" with the correct field ID (4, 5, or 6)
// the above URL can be used in a browser, too
float lastGDDfromTS;

void setup() {
  Serial.begin(115200);
  delay(1000);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  ArduinoOTA.begin();
}

void loop() {
  static uint8_t i;

  ArduinoOTA.handle();

  if (!i) {
    readFromTS(4);
    Serial.println(lastGDDfromTS, 2);
    i++;
  }
}

int16_t readFromTS(uint8_t myField) {
  // field 4 = GDD42A, 5 = GDD42B, 6 = GDD42C
  //TSserverGET[] = "http://api.thingspeak.com/channels/2879857/fields/z/last.txt?api_key=xyz";
  //                 012345678901234567890123456789012345678901234567890
  //                 0         1         2         3         4         5
  // replace z (in position 50) with 4, 5, or 6 (from myField)
  // using brute force and ignorance method:
  switch (myField) {
    case 4:
      TSserverGET[50] = '4';
      break;
    case 5:
      TSserverGET[50] = '5';
      break;
    case 6:
      TSserverGET[50] = '6';
      break;
  }

  HTTPClient httpForGet;

  httpForGet.begin(TSserverGET);
  int16_t httpResponseCode = httpForGet.GET();

  if (httpResponseCode == 200) {
    // success
    lastGDDfromTS = httpForGet.getString().toFloat();
  } else {
    // fail
    lastGDDfromTS = 0;
  }
  httpForGet.end();

  return httpResponseCode;
}

Am I doing something wrong with the HTTP GET (even tho' it returns the expected values)? Any other ideas?

Hmm it seems to be related to the outbound request.
Keep in mind that once the update starts the upload, the program will keep cycling through loop() and that maybe the outbound network activity is slowing it down to much. I do not do any outbound requests myself, so i can not help you investigate.

Now i assume you just want the update to work, so the most obvious solution is to stop doing requests once the update starts. Now if i look at your sketch and compare it to the basic OTA example, the callback function definitions are missing !

ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .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");
    });

  ArduinoOTA.begin();

Of course you probably don't want all the information send to Serial, but what you can do is set a flag

ArduinoOTA
    .onStart([]() {
      isUpdating = true;
    })

which should be a global variable

bool  isUpdating = false;

and disable any excess processing and outbound activity in particular

if (!isUpdating) {
  if (!i) {
    readFromTS(4);
    Serial.println(lastGDDfromTS, 2);
    i++;
  }
}

Of course it is wise to re enable everything once the update stops, maybe not so much in the onEnd since the ESP will restart, but after the onError it won't, and you do want your sketch to keep running.

In your second example you make so many requests that i am a little surprised this is not causing any issues in general, but i guess it is just an example.

Thank you for the reply and idea about onStart().

Unfortunately, this is not true:

In the 2nd example, just one request is made (the first time thru the loop); after that, ArduinoOTA.handle() is the only thing being executed, and that's when I try the OTA update. So there's no network activity associated with the GET during the update.

Similar for the 1st example: after my one call to 192.168.1.210/showTSgdd, loop is just doing

so there is no network activity associated with the GET when I try the OTA update.

Well, I can't explain it, but changing...

...to...

...fixes the problem. OTA uploads now work. And the GETs work (as they did with char*...)