Нужен совет по подключению к Гугл-таблицам

Доброго всем вечера.

Из-за блокировки Телеграм пришлось срочно модифицировать свои автополивалки. ESP-32 довольно просто перевел на Гуглотаблицы. Но есть проблема с передачей данных. Сначала использовал совсем простой вариант - постучал и ушол.

 else if (mode == 1)
    //------------ ПЕРЕДАЧА ДАННЫХ ---------------
  {
	if (wifiConnected())
	{
		//Передача данных на сервер
		HTTPClient http;
		String Send_Data = Web_App_URL + "?sts=write";
		Send_Data += "&date=" + dateStr;
		Send_Data += "&time=" + timeStr;
		Serial.println(Send_Data);      
		for (int i = 0; i <= 5; i++)
		{
			String I = String(i);
			Send_Data += "&moisture" +  I + "=" + String(Channels[i].soilMoisturePercent);
			Send_Data += "&setMoisture" +  I + "=" + String(Channels[i].soilMoisturePercentControl);
			Send_Data += "&flowRate" +  I + "=" + String(Channels[i].flowRate);
			Send_Data += "&limit" +  I + "=" + String(Channels[i].volumeControl);
			Send_Data += "&block" +  I + "=" + String(Channels[i].isBlock);
			Send_Data += "&coef" +  I + "=" + String(Channels[i].volumeCoeff);
		}
		Send_Data += "&reservuar=" + String(reservuar);
		Send_Data += "&flowCoeff=" + String(flowCoeff);
		Serial.println("Загрузка в Google Spreadsheet...");              
		http.begin(Send_Data.c_str());
		http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
		int httpResponseCode = http.GET();
		if (httpResponseCode > 0)
		{
			String payload;
			payload = http.getString();
			Serial.print("payload : ");
			Serial.println(payload);
			Serial.print("HTTP Status Code : ");
			Serial.println(httpResponseCode);
		}
		else
		{
			Serial.println("Send failed.");
		}
		http.end();          
    }
    mode = 2; // ОЖИДАНИЕ
  }

В таком варианте в таблицу могут записаться как все 12 строк (каждые 2 часа), так и 5, и 7, и 4.

Решил проявить настойчивость в количестве пяти штук.

 if (mode == 0)
    //------------ ОБНОВЛЕНИЕ ДАННЫХ ---------------
  {
    if (wifiConnected())
    {
      //Обновление параметров каналов
      HTTPClient http;
      String Read_Data = Web_App_URL + "?sts=read";
      Serial.println("Чтение из Google Spreadsheet...");
      http.begin(Read_Data.c_str());
      http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
      //Подключаемся 5 раз
      bool flag = false;
      for(int i=0; i<5; i++)
      {         
        int httpResponseCode = http.GET();
        if (httpResponseCode > 0)
        {
          flag = true;
          String payload;
          payload = http.getString();
          Serial.println("Payload OK get: " + payload);
          for (int i = 0; i <= 5; i++)
          {
            if (getValue(payload, ',', 18).toInt() != 0)
            {
              prefs.begin("my-app", false);
              Channels[i].soilMoisturePercentControl = getValue(payload, ',', i * 3).toInt();
              Channels[i].volumeControl = getValue(payload, ',', i * 3 + 1).toInt();            
              prefs.putUChar(moistControlKeys[i], Channels[i].soilMoisturePercentControl);
              prefs.putUChar(volControlKeys[i], Channels[i].volumeControl);
              prefs.end();
              Serial.println("Установки каналов обновлены");
            }
            if (getValue(payload, ',', i * 3 + 2).toInt() == 0)
            {
              Channels[i].isBlock = false;
            }
            if (getValue(payload, ',', i * 3 + 2).toInt() == 1)
            {
              Channels[i].isBlock = true;
            }
          }
          if (getValue(payload, ',', 20).toInt() != 0)
          {
            flowCoeff = getValue(payload, ',', 19).toInt();
            prefs.begin("my-app", false);
            prefs.putUChar(flowCoeffKeys, flowCoeff);
            prefs.end();
          }
          Serial.print("errorGet ");
          Serial.println(errorGet);
          break;
        }
        else
        {
          Serial.println("Get failed. Retrying...");
          errorGet++;
        }
      }       
      if (!flag)
      {
        Serial.print("Could not get server: ");
        Serial.println(errorGet);
        FullErrorGet = 1;
      }
      http.end();
    }
    mode = 3;// КОНТРОЛЬ
  }
  else if (mode == 1)
    //------------ ПЕРЕДАЧА ДАННЫХ ---------------
  {
    if (wifiConnected())
    {
      //Передача данных на сервер
      HTTPClient http;
      String Send_Data = Web_App_URL + "?sts=write";
      Send_Data += "&date=" + dateStr;
      Send_Data += "&time=" + timeStr;
      Serial.println(Send_Data);      
      for (int i = 0; i <= 5; i++)
      {
        String I = String(i);
        Send_Data += "&moisture" +  I + "=" + String(Channels[i].soilMoisturePercent);
        Send_Data += "&setMoisture" +  I + "=" + String(Channels[i].soilMoisturePercentControl);
        Send_Data += "&flowRate" +  I + "=" + String(Channels[i].flowRate);
        Send_Data += "&limit" +  I + "=" + String(Channels[i].volumeControl);
        Send_Data += "&block" +  I + "=" + String(Channels[i].isBlock);
        Send_Data += "&coef" +  I + "=" + String(Channels[i].volumeCoeff);
      }
      Send_Data += "&reservuar=" + String(reservuar);
      Send_Data += "&flowCoeff=" + String(flowCoeff);
      Send_Data += "&erG=" + String(errorGet);
      Send_Data += "&fErG=" + String(FullErrorGet);

      Serial.println("Загрузка в Google Spreadsheet..."); 
      Serial.print(errorSend);  
      Serial.print("...");  
      Serial.println(errorGet);     
      //Подключаемся 5 раз
      bool flag = false;
      for(int i=0; i<5; i++)
      { 
        String Send_Data2 = "&erS=" + String(errorSend);
        Send_Data2 += "&fErS=" + String(FullErrorSend);
        Send_Data += Send_Data2;
        
        http.begin(Send_Data.c_str());
        http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);

        int httpResponseCode = http.GET();
        Serial.print("HTTP Status Code : ");
        Serial.println(httpResponseCode);
        String payload;
        payload = http.getString();
        Serial.println("Payload send: " + payload);
        if (httpResponseCode > 0)
        {
          flag = true;
          //String payload;
          Serial.print("HTTP Status Code : ");
          Serial.println(httpResponseCode);
          errorSend = 0;
          FullErrorSend = 0;
          errorGet = 0;
          FullErrorGet = 0;
          http.end();
          break;
        }
        else
        {
          Serial.println("Send failed. Retrying...");
          errorSend++;
          http.end();
        }
      }       
      if (!flag)
      {
        Serial.print("Could not send to server: ");
        Serial.println(errorSend);
        FullErrorSend = 1;
      }
      
    }
    mode = 2; // ОЖИДАНИЕ
  }

При этом коде в случае сбоя (неправильный httpResponseCode) делается повторное подключение. Но… если в моониторе виден один сбой - в Гугле две идентичные записи, если два сбоя - три записи дрруг за другом.

17:21:27.617 -> https://script.google.com/macros/s/AKfycby_F7DFGWr1lbkndNWqarAFWWCijKWsuYNsECd6L7syDag756FcL8Ep9jBoqKm63B9N/exec?sts=write&date=21.05.2026&time=17:21:08
17:21:27.804 -> Загрузка в Google Spreadsheet...
17:21:33.535 -> HTTP Status Code : -11
17:21:33.535 -> Payload send: 
17:21:33.581 -> Send failed. Retrying...
17:21:38.133 -> HTTP Status Code : 200
17:21:38.133 -> Payload send: Ok
17:21:38.179 -> HTTP Status Code : 200
17:27:08.203 -> Чтение из Google Spreadsheet...
17:27:13.568 -> Payload OK get: 70,40,0,80,40,0,70,40,0,60,30,0,70,40,0,70,40,0,0,128,0

Это из монитора порта. И на 17:21 в таблице две одинаковые записи.

Пытался подставить костыль в скрипте таблиц. Сравнить время с предыдущей записью и не писать дубль. Что-то не работает.

Получение данных (блок mode=0) работает хорошо. А вот в передаче (блок mode=1) где-то накосячил.

Если надо могу выложитть полный код и скрипт для таблицы, но без актуального идентификатора работать не будет.

Еще забыл. В первом коде строка 14. Имело ли смысл так изголяться с дополнительной переменной?

Оно вроде бы умеет так (требует проверки):

Send_Data += String("&moisture") + i + "=" + Channels[i].soilMoisturePercent;

если Вы об этом.

По основному вопросу гугл сразу такое выдал, похоже на Ваш случай:

Код ошибки -11 при вызове http.GET() в библиотеке HTTPClient (обычно на контроллерах ESP8266 или ESP32 в среде Arduino) означает таймаут чтения данных (HTTPC_ERROR_READ_TIMEOUT). [1]

Это указывает на то, что микроконтроллер успешно установил соединение с сервером и отправил запрос, но сервер слишком долго не отвечал или разорвал сессию до завершения передачи данных. Часто при этом сам запрос на сервере успевает выполниться (например, данные записываются в БД), но плата об этом уже не узнает. [1, 2, 3]

Это тот, который Вы выложили между https://script.google.com/macros/s/ и /exec?

Верно. Он меняется при каждом развертывании скрипта.

Компилятор ругается. Альтернатива - String(i) в каждой строке. Визуально мой вариант лучше) А что на самом деле, интересно узнать.

Увеличил таймаут до 10 сек, умолчание - 5. Повнимательнее просмотрел монитор, разница времени 4-9 сек. Пока ошибки только в приеме данных (там не стал менять время), на передачу работает целый час каждые три минуты без проблем.

Оставлю на тест, утром отпишу.

Десяти секунд почти хватает. На сотню запросов всего две сдвоенные записи. По монитору на 15 и 20 секунд задержки.

Спасибо за помощь.