ESP32 - Http get issues and corrupt heap

Hi guys.
I have a fairly long program that connects to the internet to get data 3 times.
The functions for getting the data is very similar to each other and one is posted here below:

void geoLocation() {

  // GEO location api URL
  String geolink = String("http://ip-api.com/json/?fields=lat,lon,query");
  // Make HTTP request to GEO location API
  http.begin(geolink);
  geolink = "";
  int httpCode = http.GET();

  if (httpCode == HTTP_CODE_OK) {

    // Parse JSON response
    String payload = http.getString();

    // Deserialize JSON response
    DynamicJsonDocument doc(100);
    deserializeJson(doc, payload);
    payload = "";

    // Extract data from JSON
    float new_lat = doc["lat"].as<float>();
    float new_lon = doc["lon"].as<float>();
    String new_ip = doc["query"].as<String>();
    lat = new_lat;
    lon = new_lon;
    publicIP = new_ip;

  } 
  else {
    Serial.println("GEO Location HTTP Error: " + String(httpCode));
  }
}

My problem is that I get HTTP error codes for 2 out of 3 of these when running after eachother, while they work well if I only call one of them.
And sometimes I get errors like this one:

13:23:52.594 -> Guru Meditation Error: Core  0 panic'ed (LoadProhibited). Exception was unhandled.
13:23:52.594 -> 
13:23:52.594 -> Core  0 register dump:
13:23:52.594 -> PC      : 0x400fd9aa  PS      : 0x00060530  A0      : 0x800fda15  A1      : 0x3ffb2e10  
13:23:52.594 -> A2      : 0x00000744  A3      : 0x400fda48  A4      : 0x00000000  A5      : 0x00000002  
13:23:52.594 -> A6      : 0x3ffd0414  A7      : 0x000000f0  A8      : 0x3ffb8b01  A9      : 0x0040105a  
13:23:52.626 -> A10     : 0x3ffb8c5c  A11     : 0x00000744  A12     : 0x00060120  A13     : 0x3ffbc700  
13:23:52.626 -> A14     : 0x007b8ce0  A15     : 0x003fffff  SAR     : 0x00000020  EXCCAUSE: 0x0000001c  
13:23:52.626 -> EXCVADDR: 0x0040105e  LBEG    : 0x40089af8  LEND    : 0x40089b0e  LCOUNT  : 0xffffffff  
13:23:52.626 -> 
13:23:52.626 -> 
13:23:52.626 -> Backtrace: 0x400fd9a7:0x3ffb2e10 0x400fda12:0x3ffb2e30 0x400fda64:0x3ffb2e50 0x400fdb09:0x3ffb2e70 0x400f5d21:0x3ffb2e90
13:23:52.660 -> 
13:23:52.660 -> 
13:23:52.660 -> 
13:23:52.660 -> 
13:23:52.660 -> ELF file SHA256: 21fbcdcde8693135
13:23:52.660 -> 
13:23:52.800 -> Rebooting... 

or this one:

13:23:50.423 -> CORRUPT HEAP: Bad head at 0x3ffd024c. Expected 0xabba1234 got 0x00000000
13:23:50.423 -> 
13:23:50.423 -> assert failed: multi_heap_free multi_heap_poisoning.c:253 (head != NULL)
13:23:50.423 -> 
13:23:50.423 -> 
13:23:50.423 -> Backtrace: 0x400839bd:0x3ffb2c40 0x4008d785:0x3ffb2c60 0x40093329:0x3ffb2c80 0x40092f9b:0x3ffb2db0 0x40083e81:0x3ffb2dd0 0x40093359:0x3ffb2df0 0x400f755f:0x3ffb2e10 0x400f75bb:0x3ffb2e30 0x400f75ef:0x3ffb2e50 0x400fdb01:0x3ffb2e70 0x400f5d21:0x3ffb2e90
13:23:50.455 -> 
13:23:50.455 -> 
13:23:50.455 -> 
13:23:50.455 -> 
13:23:50.455 -> ELF file SHA256: 21fbcdcde8693135
13:23:50.455 -> 
13:23:50.627 -> Rebooting...

This is placed before my setup:

HTTPClient http;

I'm wondering if maybe someone here knows if I need to close the HTTP request between calling them in different functions in some way, or if I need to set up the functions differently for this to work?
Or if it's obvious that it is some other cause?

In case someone wants to look at the full program I'll leave that one down below. Thanks :slight_smile: (Sorry about all my terrible solutions, I'm a newbie)

#include <TimeLib.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeMono9pt7b.h>
#include <NewPing.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <time.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

#define pump_pin 5 // Output pin 5 for water pump
#define manual_pin 14 // Input pin 14 for manually starting irrigation
#define pot_pin 15 // Pin for potentiometer input

#define SCREEN_ADDRESS 0x3C // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32. GPIO21 for SDA and GPIO22 for SCL in ESP32.
Adafruit_SSD1306 display(128, 64, &Wire, -1);

// HTTP variables
HTTPClient http;

// Declaring variables and settings
int use_geo = 1; // Toggle from 0 to 1 to use geolocation based on public IP
int button;
int isPressed;
int sunup;
int sundown;
int start_day;
int now_day;
int now_hour;
int last_hour;
int now_minute;
int last_minute;
int now_second;
int last_second;
int cloud;
int pres;
int humi;
int symb;
int maxtime = 300;
int has_watered;
int is_watering;
int manual_start;
int basetime = 10;
float temp;
float wind;
float rain;
float watertime;
float lat = 57.319435;
float lon = 15.147673;
String publicIP = "0.0.0.0";

// Add automatic connection to NFC router if possible

// Wifi connection settings
const char* ssid = "SSID";
const char* password = "PASSWORD";

// NTP time synchronization settings
const char* ntpServer = "europe.pool.ntp.org";
const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;

// Loop time counter
long loopTime = millis();

// Watering timer
long startTime = millis();

void setup() {

  Serial.begin(115200); // For ESP32

  pinMode(pump_pin, OUTPUT);
  pinMode(manual_pin, INPUT_PULLUP);
  pinMode(pot_pin, INPUT);

  delay(50);

  // Connecting to Wifi
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  delay(50);

  // Mapping potentiometer reading to temperature
  int pot_modifier = map(analogRead(pot_pin), 0, 4095, 0, 2); // Range normally from 0-4095
    Serial.println("Potentiometer reading: "+String(analogRead(pot_pin)));
  if (pot_modifier < 1) {
    basetime = basetime*0.8;
    Serial.println(F("Basetime modifier 0.8"));
  } 
  else if (pot_modifier < 2) {
    basetime = basetime*1;
    Serial.println(F("Basetime modifier 1"));
  } 
  else if (pot_modifier < 3) {
    basetime = basetime*1.2;
    Serial.println(F("Basetime modifier 1.2"));
  }

  Serial.println(F(" ")); // Adding empty serial row

  // Checking internet connection and selecting time setting option
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println(F("Wifi connection failed! Rebooting.."));
    delay(5000);
    ESP.restart();
  }
  else {
    Serial.println(F("Wifi connection successful!"));
    // Initializing NTP time
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
    fetchLocalTime();
    start_day = now_day;
    last_minute = now_minute-1;
  }

  // OTA settings

  // Hostname defaults to esp3232-[MAC]
  ArduinoOTA.setHostname("HOSTNAME");

  // No authentication by default
  ArduinoOTA.setPassword("PASSWORD"); // OTA password

  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(F("\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(F("Auth Failed"));
      else if (error == OTA_BEGIN_ERROR) Serial.println(F("Begin Failed"));
      else if (error == OTA_CONNECT_ERROR) Serial.println(F("Connect Failed"));
      else if (error == OTA_RECEIVE_ERROR) Serial.println(F("Receive Failed"));
      else if (error == OTA_END_ERROR) Serial.println(F("End Failed"));
    });

  ArduinoOTA.begin();

  if (use_geo == 1) {
    // Get public IP, longitude and latitude coordinates
    delay(500);
    geoLocation();
  }

  // Get sunrise and sunset variables
  delay(500);
  sunTimes();

  Serial.println(F("Network Ready"));
  Serial.print(F("Local IP address: "));
  Serial.println(WiFi.localIP());

  Serial.print(F("Public IP address: "));
  Serial.println(publicIP);
  
  // Pause
  delay(500);

  // Start time for the first loop
  last_second = now_second;
  loopTime = millis();
}

void sunTimes() {

  // Sunrise-Sunset api URL
  String sunlink = String("https://api.sunrise-sunset.org/json?lat="+String(lat)+"&lng="+String(lon));
  // Make HTTP request to Sunrise-Sunset API
  http.begin(sunlink);
  sunlink = "";
  int httpCode = http.GET();

  if (httpCode == HTTP_CODE_OK) {

    // Parse JSON response
    String payload = http.getString();

    // Deserialize JSON response
    DynamicJsonDocument doc(1000);
    deserializeJson(doc, payload);
    payload = "";

    // Extract data from JSON
    String uptime = doc["results"]["sunrise"].as<String>();
    String downtime = doc["results"]["sunset"].as<String>();
    String upsec = uptime;
    String downsec = downtime;
    
    upsec.remove(2,upsec.length()-2);
    upsec.remove(0,1);
    if (upsec == ":") {
      uptime.remove(1,uptime.length()-1);
    }
    else {
      uptime.remove(2,uptime.length()-2);
    }
    
    downsec.remove(2,downsec.length()-2);
    downsec.remove(0,1);
    if (downsec == ":") {
      downtime.remove(1,downtime.length()-1);
    }
    else {
      downtime.remove(2,downtime.length()-2);
    }

    sunup = uptime.toInt()+1;
    sundown = downtime.toInt()+11;

  } 
  else {
    Serial.println("SunTimes HTTP Error: " + String(httpCode));
  }
}

void geoLocation() {

  // GEO location api URL
  String geolink = String("http://ip-api.com/json/?fields=lat,lon,query");
  // Make HTTP request to GEO location API
  http.begin(geolink);
  geolink = "";
  int httpCode = http.GET();

  if (httpCode == HTTP_CODE_OK) {

    // Parse JSON response
    String payload = http.getString();

    // Deserialize JSON response
    DynamicJsonDocument doc(100);
    deserializeJson(doc, payload);
    payload = "";

    // Extract data from JSON
    float new_lat = doc["lat"].as<float>();
    float new_lon = doc["lon"].as<float>();
    String new_ip = doc["query"].as<String>();
    lat = new_lat;
    lon = new_lon;
    publicIP = new_ip;

  } 
  else {
    Serial.println("GEO Location HTTP Error: " + String(httpCode));
  }
}

void tempMod(){
  
  // Returning watertime based on temperature
  if (temp < 14) {
    watertime = 0;
  }
  else if (temp < 18) {
    watertime = basetime;
  }
  else if (temp < 22) {
    watertime = basetime*1.5;
  }
  else if (temp < 25) {
    watertime = basetime*1.7;
  }
  else if (temp < 28) {
    watertime = basetime*1.9;
  }
  else if (temp < 30) {
    watertime = basetime*2.1;
  }
  else {
    watertime = basetime*2.3;
  }
}

void cloudMod(){

  // Function for modufying watering time depending on clouds
  if (cloud == 0) {
    watertime = watertime*1.7;
  }
  else if (cloud == 1) {
    watertime = watertime*1.4;
  }
  else if (cloud == 2) {
    watertime = watertime*1.2;
  }
  else if (cloud == 3) {
    watertime = watertime*1.1;
  }
  else {
    watertime = watertime;
  }
  
}

void rainMod(){

  // Function for modifying watering time depending on rain
  if (rain == 0) {
    watertime = watertime;
  }
  else if (rain < 0.1) {
    watertime = watertime-1;
  }
  else if (rain < 0.2) {
    watertime = watertime-2;
  }
  else if (rain < 0.3) {
    watertime = watertime-3;
  }
  else if (rain < 0.4) {
    watertime = watertime-4;
  }
  else if (rain < 0.5) {
    watertime = watertime-5;
  }
  else if (rain < 0.6) {
    watertime = watertime-6;
  }
  else if (rain < 0.7) {
    watertime = watertime-7;
  }
  else if (rain < 0.8) {
    watertime = watertime-8;
  }
  else if (rain < 0.9) {
    watertime = watertime-9;
  }
  else  {
    watertime = watertime-10;
  }

}

void windMod(){

  // Function for modifying watering time depending on wind
  if (wind < 3) {
    watertime = watertime;
  }
  else if (wind < 5) {
    watertime = watertime*0.9;
  }
  else if (wind < 7) {
    watertime = watertime*0.8;
  }
  else {
    watertime = watertime*0.7;
  }

}

void setWaterTime(){

  // Function for calculating watering time for the day
  tempMod();
  if ((now_hour > sunup) && (now_hour < sundown)) {
    cloudMod();
  }
  rainMod();
  windMod();
  Serial.print(F("Watertime: "));
  Serial.println(watertime);

}

void doWatering(){

  // Function for performing watering
  startTime = millis();
  digitalWrite(pump_pin, HIGH);
  is_watering = 1;
  if (manual_start == 1) {
    Serial.println(F("Manually starting watering"));
  }
  else {
    Serial.println(F("Starting watering"));
  }

}

void stopWatering(){

  // Function for stopping watering 
  if (((millis()-startTime) > (watertime*1000)) && (manual_start == 0) && (is_watering == 1)) {
    digitalWrite(pump_pin, LOW);
    is_watering = 0;
    watertime = basetime;
    Serial.println(F("Stopping watering because watertime is finished"));
  }
  else if (((millis() - startTime) > (1000*maxtime)) && ((millis() - startTime) < 90000000) && (is_watering == 1)) {
    digitalWrite(pump_pin, LOW);
    is_watering = 0;
    Serial.println(F("Stopping watering because time exceeded maximum"));
  }
  else if ((isPressed == 1) && (is_watering == 1)) {
    digitalWrite(pump_pin, LOW);
    is_watering = 0;
    Serial.println(F("Stopping watering manually"));
  }

}

void fetchLocalTime(){

  // Function for obtaining current time
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println(F("Failed to obtain time"));
  }
  now_hour = timeinfo.tm_hour;
  now_minute = timeinfo.tm_min;
  now_second = timeinfo.tm_sec;
  now_day = timeinfo.tm_mday;
}

const char fetchIndex(const char* paramName, DynamicJsonDocument doc){

  // Function for fetching the array ID for the parameter needed
    int paramIndex = -1;
    JsonArray parameters = doc["timeSeries"][0]["parameters"].as<JsonArray>();
    for (size_t i = 0; i < parameters.size(); i++) {
      const char* name = parameters[i]["name"].as<const char*>();
      float data = parameters[i]["values"][0].as<float>();
      String strname = name;
      String strparam = paramName;
      if (strname == strparam) {
        paramIndex = i;
        break;
      }
    }
    return paramIndex;
}

void fetchWeather(){

  // Function for fetching weather from SMHI 

  String weblink = "https://opendata-download-metanalys.smhi.se/api/category/mesan1g/version/2/geotype/point/lon/"+String(lon)+"/lat/"+String(lat)+"/data.json";
  // Make HTTP request to SMHI API
  http.begin(weblink);
  weblink = "";
  int httpCode = http.GET();

  if (httpCode == HTTP_CODE_OK) {

    Serial.println(F("."));
    Serial.println(F("."));

    // Parse JSON response
    String payload = http.getString();

    // Deserialize JSON response
    DynamicJsonDocument doc(3300);
    deserializeJson(doc, payload);
    payload = "";

    // Extract data from JSON
    temp = doc["timeSeries"][0]["parameters"][fetchIndex("t",doc)]["values"][0].as<float>();
    rain = doc["timeSeries"][0]["parameters"][fetchIndex("prec1h",doc)]["values"][0].as<float>();
    cloud = doc["timeSeries"][0]["parameters"][fetchIndex("tcc",doc)]["values"][0].as<int>();
    wind = doc["timeSeries"][0]["parameters"][fetchIndex("ws",doc)]["values"][0].as<float>();
    pres = doc["timeSeries"][0]["parameters"][fetchIndex("msl",doc)]["values"][0].as<int>();
    humi = doc["timeSeries"][0]["parameters"][fetchIndex("r",doc)]["values"][0].as<int>();
    symb = doc["timeSeries"][0]["parameters"][fetchIndex("Wsymb2",doc)]["values"][0].as<int>();

    Serial.println(F("."));
    Serial.print(F("Temp: "));
    Serial.print(temp);
    Serial.println(F(" C"));

    Serial.print(F("Rain: "));
    Serial.print(rain);
    Serial.println(F(" mm"));

    Serial.print(F("Clouds: "));
    Serial.print(cloud);
    Serial.println(F(" of 8"));

    Serial.print(F("Wind: "));
    Serial.print(wind);
    Serial.println(F(" m/s"));

    Serial.print(F("Pressure: "));
    Serial.print(pres);
    Serial.println(F(" hPa"));

    Serial.print(F("Humidity: "));
    Serial.print(humi);
    Serial.println(F(" % RH"));

    Serial.print(F("Weather: "));
    Serial.print(symb);
    Serial.println(F(" of 27"));

  } 
  
  else {
    Serial.println("SMHI HTTP Error: " + String(httpCode));
  }
  
}

void loop() {
  
  // OTA 
  ArduinoOTA.handle(); 
  if (millis() - loopTime > 1000) {
    // Getting time
    fetchLocalTime();
  }

  if ((now_hour != last_hour) && (now_hour != 0)) {
    fetchWeather();
    setWaterTime();
    last_hour = now_hour;
  }
  else if (now_hour != last_hour) {
    fetchWeather();
    last_hour = now_hour;
  }

  if ((now_hour == 13) || (now_hour == 23)) {
    if (has_watered == 0) {
      doWatering();
      manual_start = 0;
      has_watered = 1;
    }
  }
  else if (has_watered = 1) {
    has_watered = 0;
  }

  // Reading button state for manual irrigation
  button = digitalRead(manual_pin);

  if ((button == HIGH) && (isPressed == 0)) {
    Serial.println(F("Button pressed"));
    isPressed = 1;
    if (is_watering == 0) {
      manual_start = 1;
      doWatering();
    }
    else if (is_watering == 1) {
      stopWatering();
    }
    delay(500);
  }
  else if (button == LOW) {
    isPressed = 0;
  }

  if (now_second != last_second) {
    last_second = now_second;
    loopTime = millis();
    stopWatering();
    // Serial.println("Potentiometer reading: "+String(analogRead(pot_pin)));
  }

  if (now_minute != last_minute) {
    last_minute = now_minute;
  }

  // Rebooting daily
  if (now_day != start_day) {
    Serial.println(F("New day = Daily reset. Rebooting.."));
    delay(5000);
    ESP.restart();
  }

}

I've set up the project here as well if that helps you guys see what's going on :slight_smile: Weather based irrigation - Wokwi ESP32, STM32, Arduino Simulator

Is 100 enough memory for the JSON you get in return in sunTimes?

I'm missing the

http.end();

after you're done with one request. This way the object doesn't know when it may release the memory occupied by the response.

I think so. I tried reducing the number until it didn't work, and then increased some for buffer.
Not sure how to check how much memory is actually needed though?

Yeah absolutely. I added that and the code is updated in the wokwi simulator too, but I still get those errors :frowning:
Not sure what to check next..

You were given good advices in your other topic but it seems that you ignored most of them :frowning:

Frankly, delete the terribly inefficent code calling fetchIndex multiple times, and use a range-based for loop, as demonstrated in the ArduinoJson documentation and in my wokwi example.

Use the ESP32 exception decoder to see exactly where and why the crash occured.

Are you referring to the use of filters?
I didn't understand how that would help the program overall, or how it affects the memory usage.
And I don't want to add things I don't understand for reasons I don't know, because then I loose the little control I have when programming.
I am still a newbie, so I'm learning this every day.
I'll give it a go and see how it turns out, but how do you mean it would help me with the issues I'm having?
Are you saying the reason for my problems are memory related?

And could you please give me a little help getting started with this?
I've never used it and don't know how to.

Hi again guys! I've made progress :slight_smile: Thank you @guix for the filter help!
However, for the api requests that go over https I get HTTP errors..
Does anyone of you know why that is and how to resolve it?
My googling didn't give any insight..
Updated code below and in Wokwi:

#include <ArduinoOTA.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

#define pump_pin 5 // Output pin 5 for water pump
#define manual_pin 14 // Input pin 14 for manually starting irrigation
#define pot_pin 34 // Pin for potentiometer input

// HTTP variables
HTTPClient http;

// Declaring variables and settings
int use_geo = 1; // Toggle from 0 to 1 to use geolocation based on public IP
int high_level_relay = 0; // Toggle from 0 to 1 to use high level relay instead of low level
int button;
int isPressed;
int sunup;
int sundown;
int start_day;
int now_day;
int now_hour;
int last_hour;
int now_minute;
int last_minute;
int now_second;
int last_second;
int cloud;
int pres;
int humi;
int symb;
int maxtime = 300;
int has_watered;
int is_watering;
int manual_start;
int basetime = 10;
float temp;
float wind;
float rain;
float watertime;
float lat = 59.319435;
float lon = 18.147673;
String publicIP = "192.168.0.1";

// Add automatic connection to NFC router if possible

// Wifi connection settings
const char* ssid = "Wokwi-GUEST";
const char* password = "";

// NTP time synchronization settings
const char* ntpServer = "europe.pool.ntp.org";
const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;

// Loop time counter
long loopTime = millis();

// Watering timer
long startTime = millis();

void setup() {

  Serial.begin(115200); // For ESP32

  pinMode(pump_pin, OUTPUT);
  pinMode(manual_pin, INPUT_PULLUP);
  pinMode(pot_pin, INPUT);
  if (high_level_relay == 0) {
    digitalWrite(pump_pin, HIGH);
  }
  else {
    digitalWrite(pump_pin, LOW);
  }

  delay(50);

  Serial.println(F("."));
  Serial.println(F(".."));
  Serial.println(F("..."));
  Serial.println(F("...."));
  Serial.println(F("....."));
  Serial.println(F("......System starting..."));
  Serial.println(F("."));

  // Mapping potentiometer reading to temperature
  int pot_modifier = map(analogRead(pot_pin), 0, 4095, 0, 2); // Range normally from 0-4095
  if (pot_modifier < 1) {
    basetime = basetime*0.8;
    Serial.println(F("Basetime modifier 0.8"));
  } 
  else if (pot_modifier < 2) {
    basetime = basetime*1;
    Serial.println(F("Basetime modifier 1"));
  } 
  else if (pot_modifier < 3) {
    basetime = basetime*1.2;
    Serial.println(F("Basetime modifier 1.2"));
  }

  Serial.println(F(".")); // Adding empty serial row

  delay(500);

  // Connecting to Wifi
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print(F("Connecting to Wifi.."));
  while(WiFi.status() != WL_CONNECTED) {
    delay(10);
    Serial.print('.');
  }

  delay(50);

  // Checking internet connection and selecting time setting option
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println(F("Wifi connection failed! Rebooting.."));
    delay(5000);
    ESP.restart();
  }
  else {
    Serial.println(F("Wifi connection successful!"));
    // Initializing NTP time
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
    fetchLocalTime();
    start_day = now_day;
    last_minute = now_minute-1;
  }

  // OTA settings

  // Hostname defaults to esp3232-[MAC]
  ArduinoOTA.setHostname("HOSTNAME");

  // No authentication by default
  ArduinoOTA.setPassword("PASSWORD"); // OTA password

  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(F("\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(F("Auth Failed"));
      else if (error == OTA_BEGIN_ERROR) Serial.println(F("Begin Failed"));
      else if (error == OTA_CONNECT_ERROR) Serial.println(F("Connect Failed"));
      else if (error == OTA_RECEIVE_ERROR) Serial.println(F("Receive Failed"));
      else if (error == OTA_END_ERROR) Serial.println(F("End Failed"));
    });

  ArduinoOTA.begin();

  Serial.println(F("Network Ready"));

  if (use_geo == 1) {
    // Get public IP, longitude and latitude coordinates
    delay(500);
    geoLocation();
  }

  // Get sunrise and sunset variables
  delay(500);
  sunTimes();

  Serial.print(F("Local IP address: "));
  Serial.println(WiFi.localIP());

  Serial.print(F("Public IP address: "));
  Serial.println(String(publicIP));
  
  // Pause
  delay(500);

  // Start time for the first loop
  last_second = now_second;
  loopTime = millis();
}

void geoLocation() {

  int tries;
  int httpCode;
  int nap;
  const char apiName[] = "ip-api.com";
  const unsigned apiPort = 80;
  String apiPath = "json/?fields=lat,lon,query";

  // Make HTTP request to GEO location API
  do {
    if (tries < 5) {
      nap = (1000+(1000*tries));
    }
    else {
      nap = 1000;
    }
    delay(nap);
    http.begin(apiName, apiPort, apiPath);
    httpCode = http.GET();
    tries++;
    if (tries > 5) {
      break;
    }
  }
  while (httpCode != HTTP_CODE_OK);

  if (httpCode == HTTP_CODE_OK) {

    // Parse JSON response
    String payload = http.getString();

    // Deserialize JSON response
    StaticJsonDocument<80> doc;
    deserializeJson(doc, payload);
    payload = "";

    // Extract data from JSON
    lat = doc["lat"].as<float>();
    lon = doc["lon"].as<float>();
    publicIP = doc["query"].as<String>();
    Serial.println("Latitude: " + String(lat));
    Serial.println("Longitude: " + String(lon));

  } 
  else {
    Serial.println("GEO Location HTTP Error: " + String(httpCode));
  }
  http.end(); //Free the resources
}

void sunTimes() {

  int tries;
  int httpCode;
  int nap;
  const char apiName[] = "api.sunrise-sunset.org";
  const unsigned apiPort = 443;
  String apiPath = "json?lat="+String(lat)+"&lng="+String(lon);

  // Make HTTP request to Sunrise-Sunset API
  do {
    if (tries < 5) {
      nap = (1000+(1000*tries));
    }
    else {
      nap = 1000;
    }
    delay(nap);
    http.begin(apiName, apiPort, apiPath);
    httpCode = http.GET();
    tries++;
    if (tries > 5) {
      break;
    }
  }
  while (httpCode != HTTP_CODE_OK);

  if (httpCode == HTTP_CODE_OK) {

    // Parse JSON response
    String payload = http.getString();

    // Deserialize JSON response
    StaticJsonDocument<400> doc;
    deserializeJson(doc, payload);
    payload = "";

    // Extract data from JSON
    String uptime = doc["results"]["sunrise"].as<String>();
    String downtime = doc["results"]["sunset"].as<String>();
    String upsec = uptime;
    String downsec = downtime;
    
    upsec.remove(2,upsec.length()-2);
    upsec.remove(0,1);
    if (upsec == ":") {
      uptime.remove(1,uptime.length()-1);
    }
    else {
      uptime.remove(2,uptime.length()-2);
    }
    
    downsec.remove(2,downsec.length()-2);
    downsec.remove(0,1);
    if (downsec == ":") {
      downtime.remove(1,downtime.length()-1);
    }
    else {
      downtime.remove(2,downtime.length()-2);
    }

    sunup = uptime.toInt()+3;
    sundown = downtime.toInt()+13;
    Serial.println("Sunrise hour: " + String(sunup));
    Serial.println("Sunset hour: " + String(sundown));

  } 
  else {
    Serial.println("SunTimes HTTP Error: " + String(httpCode));
  }
  http.end(); //Free the resources
}

void tempMod(){
  
  // Returning watertime based on temperature
  if (temp < 14) {
    watertime = watertime;
  }
  else if (temp < 18) {
    watertime = (basetime+watertime);
  }
  else if (temp < 22) {
    watertime = (basetime+watertime)*1.5;
  }
  else if (temp < 25) {
    watertime = (basetime+watertime)*1.7;
  }
  else if (temp < 28) {
    watertime = (basetime+watertime)*1.9;
  }
  else if (temp < 30) {
    watertime = (basetime+watertime)*2.1;
  }
  else {
    watertime = (basetime+watertime)*2.3;
  }
}

void cloudMod(){

  // Function for modufying watering time depending on clouds
  if (cloud == 0) {
    watertime = watertime*1.7;
  }
  else if (cloud == 1) {
    watertime = watertime*1.4;
  }
  else if (cloud == 2) {
    watertime = watertime*1.2;
  }
  else if (cloud == 3) {
    watertime = watertime*1.1;
  }
  else {
    watertime = watertime;
  }
  
}

void rainMod(){

  // Function for modifying watering time depending on rain
  if (rain == 0) {
    watertime = watertime;
  }
  else if (rain < 0.1) {
    watertime = watertime-1;
  }
  else if (rain < 0.2) {
    watertime = watertime-2;
  }
  else if (rain < 0.3) {
    watertime = watertime-3;
  }
  else if (rain < 0.4) {
    watertime = watertime-4;
  }
  else if (rain < 0.5) {
    watertime = watertime-5;
  }
  else if (rain < 0.6) {
    watertime = watertime-6;
  }
  else if (rain < 0.7) {
    watertime = watertime-7;
  }
  else if (rain < 0.8) {
    watertime = watertime-8;
  }
  else if (rain < 0.9) {
    watertime = watertime-9;
  }
  else  {
    watertime = watertime-10;
  }

}

void windMod(){

  // Function for modifying watering time depending on wind
  if (wind < 3) {
    watertime = watertime;
  }
  else if (wind < 5) {
    watertime = watertime*0.9;
  }
  else if (wind < 7) {
    watertime = watertime*0.8;
  }
  else {
    watertime = watertime*0.7;
  }

}

void setWaterTime(){

  // Function for calculating watering time for the day
  tempMod();
  if ((now_hour > sunup) && (now_hour < sundown)) {
    cloudMod();
  }
  rainMod();
  windMod();
  Serial.print(F("Watertime: "));
  Serial.println(watertime);

}

void doWatering(){

  // Function for performing watering
  startTime = millis();
  if (high_level_relay == 0) {
    digitalWrite(pump_pin, LOW);
  }
  else {
    digitalWrite(pump_pin, HIGH);
  }
  is_watering = 1;
  if (manual_start == 1) {
    Serial.println(F("Manually starting watering"));
  }
  else {
    Serial.println(F("Starting watering"));
  }

}

void stopWatering(){

  // Function for stopping watering 
  if (((millis()-startTime) > (watertime*1000)) && (manual_start == 0) && (is_watering == 1)) {
    if (high_level_relay == 0) {
      digitalWrite(pump_pin, HIGH);
    }
    else {
      digitalWrite(pump_pin, LOW);
    }
    is_watering = 0;
    watertime = basetime;
    Serial.println(F("Stopping watering because watertime is finished"));
  }
  else if (((millis() - startTime) > (1000*maxtime)) && ((millis() - startTime) < 90000000) && (is_watering == 1)) {
    if (high_level_relay == 0) {
      digitalWrite(pump_pin, HIGH);
    }
    else {
      digitalWrite(pump_pin, LOW);
    }
    is_watering = 0;
    Serial.println(F("Stopping watering because time exceeded maximum"));
  }
  else if ((isPressed == 1) && (is_watering == 1)) {
    if (high_level_relay == 0) {
      digitalWrite(pump_pin, HIGH);
    }
    else {
      digitalWrite(pump_pin, LOW);
    }
    is_watering = 0;
    Serial.println(F("Stopping watering manually"));
  }

}

void fetchLocalTime(){

  // Function for obtaining current time
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println(F("Failed to obtain time"));
  }
  now_hour = timeinfo.tm_hour;
  now_minute = timeinfo.tm_min;
  now_second = timeinfo.tm_sec;
  now_day = timeinfo.tm_mday;
}

void fetchWeather(){

  int tries;
  int httpCode;
  int nap;
  String apiPath = "http://opendata-download-metanalys.smhi.se/api/category/mesan1g/version/2/geotype/point/lon/"+String(lon)+"/lat/"+String(lat)+"/data.json";

  // Make HTTP request to SMHI API

  do {
    if (tries < 5) {
      nap = (1000+(1000*tries));
    }
    else {
      nap = 1000;
    }
    delay(nap);
    http.begin(apiPath);
    httpCode = http.GET();
    tries++;
    if (tries > 5) {
      break;
    }
  }
  while (httpCode != HTTP_CODE_OK);

  if (httpCode == HTTP_CODE_OK) {

    // Parse JSON response
    String payload = http.getString();

    static StaticJsonDocument<150> filter;
    filter["timeSeries"][0]["parameters"][0]["name"] = true;
    filter["timeSeries"][0]["parameters"][0]["values"] = true;

    static StaticJsonDocument<50000> json_document;
    deserializeJson( json_document, payload, DeserializationOption::Filter( filter ) );

    Serial.println();

    JsonArray parameters_array = json_document["timeSeries"][0]["parameters"].as<JsonArray>();

    for ( JsonObject parameter : parameters_array )
    {
      if ( !strcmp( parameter["name"].as<const char*>(), "t" ) )
      {
        temp = parameter["values"][0].as<float>();
      }
      else if ( !strcmp( parameter["name"].as<const char*>(), "prec1h" ) )
      {
        rain = parameter["values"][0].as<float>();
      }
      else if ( !strcmp( parameter["name"].as<const char*>(), "tcc" ) )
      {
        cloud = parameter["values"][0].as<int>();
      }
      else if ( !strcmp( parameter["name"].as<const char*>(), "ws" ) )
      {
        wind = parameter["values"][0].as<float>();
      }
      else if ( !strcmp( parameter["name"].as<const char*>(), "msl" ) )
      {
        pres = parameter["values"][0].as<int>();
      }
      else if ( !strcmp( parameter["name"].as<const char*>(), "r" ) )
      {
        humi = parameter["values"][0].as<int>();
      }
      else if ( !strcmp( parameter["name"].as<const char*>(), "Wsymb2" ) )
      {
        symb = parameter["values"][0].as<int>();
      }
    }

    Serial.println(F("."));
    Serial.print(F("Temp: "));
    Serial.print(temp);
    Serial.println(F(" C"));

    Serial.print(F("Rain: "));
    Serial.print(rain);
    Serial.println(F(" mm"));

    Serial.print(F("Clouds: "));
    Serial.print(cloud);
    Serial.println(F(" of 8"));

    Serial.print(F("Wind: "));
    Serial.print(wind);
    Serial.println(F(" m/s"));

    Serial.print(F("Pressure: "));
    Serial.print(pres);
    Serial.println(F(" hPa"));

    Serial.print(F("Humidity: "));
    Serial.print(humi);
    Serial.println(F(" % RH"));

    Serial.print(F("Weather: "));
    Serial.print(symb);
    Serial.println(F(" of 27"));

  } 
  
  else {
    Serial.println("SMHI HTTP Error: " + String(httpCode));
  }
  http.end(); //Free the resources
}

void loop() {
  
  // OTA 
  ArduinoOTA.handle(); 
  if (millis() - loopTime > 1000) {
    // Fetching time
    fetchLocalTime();
  }

  if ((now_hour != last_hour) && (now_hour != 0)) {
    fetchWeather();
    setWaterTime();
    last_hour = now_hour;
  }
  else if (now_hour != last_hour) {
    fetchWeather();
    last_hour = now_hour;
  }

  if ((now_hour == 13) || (now_hour == 23)) {
    if (has_watered == 0) {
      doWatering();
      manual_start = 0;
      has_watered = 1;
    }
  }
  else if (has_watered = 1) {
    has_watered = 0;
  }

  // Reading button state for manual irrigation
  button = digitalRead(manual_pin);

  if ((button == HIGH) && (isPressed == 0)) {
    Serial.println(F("Button pressed"));
    isPressed = 1;
    if (is_watering == 0) {
      manual_start = 1;
      doWatering();
    }
    else if (is_watering == 1) {
      stopWatering();
    }
    delay(1500);
  }
  else if (button == LOW) {
    isPressed = 0;
  }

  if (now_second != last_second) {
    last_second = now_second;
    loopTime = millis();
    stopWatering();
  }

  if (now_minute != last_minute) {
    last_minute = now_minute;
  }

  // Rebooting daily
  if (now_day != start_day) {
    Serial.println(F("New day = Daily reset. Rebooting.."));
    delay(5000);
    ESP.restart();
  }

}
1 Like

May we ask for the exact type of error or better it's number?

Well if I connect to the SMHI api over https (as I should) in the fetchWeather function, while using the geoLocation function (that uses https to get its data) and the sunTimes function (that use http to get its data), I get these errors:

23:47:42.745 -> assert failed: block_merge_prev heap_tlsf.c:344 (block_is_free(prev) && "prev block is not free though marked as such")
23:47:42.745 -> 
23:47:42.745 -> 
23:47:42.745 -> Backtrace: 0x40083b59:0x3ffd8200 0x4008d971:0x3ffd8220 0x400936cd:0x3ffd8240 0x40092911:0x3ffd8370 0x4009311c:0x3ffd8390 0x40093346:0x3ffd83b0 0x4008401d:0x3ffd83d0 0x400936fd:0x3ffd83f0 0x401843b2:0x3ffd8410 0x4015f187:0x3ffd8430 0x40182f92:0x3ffd8450
23:47:42.781 -> 
23:47:42.781 -> 
23:47:42.781 -> 
23:47:42.781 -> 
23:47:42.781 -> ELF file SHA256: a4a28c276dd9053d
23:47:42.781 -> 
23:47:42.923 -> Rebooting...

or this:

23:49:22.125 -> ets Jul 29 2019 12:21:46
23:49:22.125 -> 
23:49:22.125 -> rst:0x8 (TG1WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
23:49:22.125 -> configsip: 0, SPIWP:0xee
23:49:22.125 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
23:49:22.125 -> mode:DIO, clock div:1
23:49:22.125 -> load:0x3fff0030,len:1184
23:49:22.125 -> load:0x40078000,len:13220
23:49:22.125 -> ho 0 tail 12 room 4
23:49:22.160 -> load:0x40080400,len:3028
23:49:22.160 -> entry 0x400805e4

or just this:

23:49:43.176 -> SMHI HTTP Error: -5

But if I skip using the geoLocation function and I use https in the fetchWeather function, I instead get these errors for the sunTimes function, regardless if I use http or https in the sunTimes function:

23:53:09.213 -> SunTimes HTTP Error: 400

The sunTimes function normally uses https to get its json response, which works if I use the geoLocation function and use http only in fetchWeather. But the api used in the fetchWeather function is supposed to be accessed over https according to the documentation, which is why I really wanna find the reason and fix for these weird issues..

Not in the posted code (port 80 is HTTP)!

It uses HTTP to access an API on port 443 which probably fails.

For both functions you free the HTTPClient resources only if the call succeeded or the retry max is reached (which is probable as the above errors show).

Error 400 means "Bad request", so you didn't follow the API rules in your request. As we don't have the API documentation it's pure guessing trying to tell you what you might have done wrong.

Sorry, I mixed those up when posting that reply.
I meant that the geoLocation function uses http and the sunTimes used https. I mean port 80 and port 443. Not sure what you meant regarding using http on port 443 though. Can you explain more please?

Strangely though this works fine if I use http instead of https in the fetchWeather function and if I use the geoLocation function. If I do that then there is no errors and I get the data I need. So I'm thinking theres something else I don't see in the geoLocation or fetchWeather functions that affect the sunTimes function in some way?

Regarding this:

Do you mean I should place the http.end() in the do while loop something like this?:

do {
    if (tries < 5) {
      nap = (1000+(1000*tries));
    }
    else {
      nap = 1000;
    }
    delay(nap);
    http.begin(apiName, apiPort, apiPath);
    httpCode = http.GET();
    tries++;
    http.end();
    if (tries > 5) {
      break;
    }
  }
  while (httpCode != HTTP_CODE_OK);

Or something else?

You have to tell the library to use HTTPS, ti doesn't select that automatically by the port number:

    http.begin(apiName, apiPort, apiPath, true);

I guess the answer to the previous question might change that.

Yes.

I tried adding the ", true" part as you did, but I get an error:

error: no matching function for call to 'HTTPClient::begin(const char [23], const unsigned int&, String&, bool)'
     http.begin(apiName, apiPort, apiPath, true);

What am I not getting? :slight_smile:

My version of the ESP32 HTTPClient library offers such a method:

bool begin(WiFiClient &client, String host, uint16_t port, String uri = "/", bool https = false);

But you're right, you used a deprecated begin() variant, which doesn't provide the WiFiClient. So you have to add that.

What library do you use? I thought I had the "usual" one but don't mind changing to resolve these issues :slight_smile:

The one delivered with the ESP32 core.

Given you use a current version of the core you have to change the begin() call in your code.

    WiFiClientSecure wifiClient;
    wifiClient.setInsecure();
    http.begin(wifiClient, apiName, apiPort, apiPath, true);

Sadly, it still doesn't work :frowning:
This is my updated fetchWeater() function:

void fetchWeather(){

  WiFiClientSecure wifiClient;
  wifiClient.setInsecure();
  int tries;
  int httpCode;
  int nap;
  const char apiName[] = "opendata-download-metanalys.smhi.se";
  const unsigned apiPort = 443;
  String apiPath = "api/category/mesan1g/version/2/geotype/point/lon/"+String(lon)+"/lat/"+String(lat)+"/data.json";
  // String apiPath = "http://opendata-download-metanalys.smhi.se/api/category/mesan1g/version/2/geotype/point/lon/"+String(lon)+"/lat/"+String(lat)+"/data.json";

  // Make HTTP request to SMHI API

  do {
    if (tries < 5) {
      nap = (1000+(1000*tries));
    }
    else {
      nap = 1000;
    }
    delay(nap);
    // http.begin(apiPath);
    http.begin(wifiClient, apiName, apiPort, apiPath, true);
    httpCode = http.GET();
    if (httpCode == HTTP_CODE_OK) {
      // Parse JSON response
      String payload = http.getString();

      static StaticJsonDocument<150> filter;
      filter["timeSeries"][0]["parameters"][0]["name"] = true;
      filter["timeSeries"][0]["parameters"][0]["values"] = true;

      static StaticJsonDocument<50000> json_document;
      deserializeJson( json_document, payload, DeserializationOption::Filter( filter ) );

      Serial.println();

      JsonArray parameters_array = json_document["timeSeries"][0]["parameters"].as<JsonArray>();

      for ( JsonObject parameter : parameters_array )
      {
        if ( !strcmp( parameter["name"].as<const char*>(), "t" ) )
        {
          temp = parameter["values"][0].as<float>();
        }
        else if ( !strcmp( parameter["name"].as<const char*>(), "prec1h" ) )
        {
          rain = parameter["values"][0].as<float>();
        }
        else if ( !strcmp( parameter["name"].as<const char*>(), "tcc" ) )
        {
          cloud = parameter["values"][0].as<int>();
        }
        else if ( !strcmp( parameter["name"].as<const char*>(), "ws" ) )
        {
          wind = parameter["values"][0].as<float>();
        }
        else if ( !strcmp( parameter["name"].as<const char*>(), "msl" ) )
        {
          pres = parameter["values"][0].as<int>();
        }
        else if ( !strcmp( parameter["name"].as<const char*>(), "r" ) )
        {
          humi = parameter["values"][0].as<int>();
        }
        else if ( !strcmp( parameter["name"].as<const char*>(), "Wsymb2" ) )
        {
          symb = parameter["values"][0].as<int>();
        }
      }

      Serial.println(F("."));
      Serial.println("Time: "+String(now_hour)+":"+String(now_minute)+":"+String(now_second));
      Serial.println("Temp: "+String(temp)+" C");
      Serial.println("Rain: "+String(rain)+" mm");
      Serial.println("Clouds: "+String(cloud)+" of 8");
      Serial.println("Wind: "+String(wind)+" m/s");
      Serial.println("Pressure: "+String(pres)+" hPa");
      Serial.println("Humidity: "+String(humi)+" % RH");
      Serial.println("Weather: "+String(symb)+" of 27");

      if (use_webserial == 1) {
        delay(50);
        WebSerial.println("");
        WebSerial.println("Time: "+String(now_hour)+":"+String(now_minute)+":"+String(now_second));
        WebSerial.println("Temp: "+String(temp)+" C");
        WebSerial.println("Rain: "+String(rain)+" mm");
        WebSerial.println("Clouds: "+String(cloud)+" of 8");
        WebSerial.println("Wind: "+String(wind)+" m/s");
        WebSerial.println("Pressure: "+String(pres)+" hPa");
        WebSerial.println("Humidity: "+String(humi)+" % RH");
        WebSerial.println("Weather: "+String(symb)+" of 27");
      }
    }
    else {
      Serial.println("SMHI HTTP Error: " + String(httpCode));
      if (use_webserial == 1) {
        delay(50);
        WebSerial.println("SMHI HTTP Error: " + String(httpCode));
      }
    }
    tries++;
    if (tries > 5) {
      http.end();
      break;
    }
    http.end();
  }
  while (httpCode != HTTP_CODE_OK);
}

And this is the serial print:

00:02:59.260 -> SMHI HTTP Error: 404
00:03:01.304 -> SMHI HTTP Error: -2
00:03:05.102 -> SMHI HTTP Error: 404
00:03:09.181 -> SMHI HTTP Error: -2
00:03:14.978 -> SMHI HTTP Error: 404
00:03:16.059 -> SMHI HTTP Error: -2

The commented out apiPath together with the commented out http.begin manages to get the data by themselves though, but I am supposed to get the data from the API over https and I worry about it suddenly stop working if I don't use https..