Blocking program, or the LCD [almost Solved]

Hi! I kept working on this program, and with help from other people. I basically combined two programs. The thing is that the program freezes, or at least the LCD (it's a 16x1 but I command it like a 16x2), as you can see in the picture. After 1-2 hours of operation on the LCD I see hieroglyphs, as I usually say. The time should be displayed for 50s, and the weather for 10s. The mainboard is an ESP32.
I put about 4 messages at certain key moments, those messages appear on the Serial Monitor, but I don't understand what is happening with the LCD.
I also tested the programs separately for two days, no problem. One is for displaying clock + day (NTP clock), and the other is for displaying local weather + city using a private API.
What do you think would be the problem? In the part where I did the 50s-10s division?

#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino_JSON.h>
#include "time.h"
#include "sntp.h"
#include <LiquidCrystal_I2C.h>

//0x3F or 0x27
LiquidCrystal_I2C lcd(0x27, 16, 2);   //LCD Object

const char* ssid       = "@@@@@";
const char* password   = "@@@@@";

const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
const long  gmtOffset_sec = 7200;
const int   daylightOffset_sec = 3600;

char *daynames[7] = {
  "Duminica",
  "    Luni",
  "   Marti",
  "Miercuri",
  "     Joi",
  "  Vineri",
  " Sambata"
}; // put them in this order as tm struct 0 = Sunday
//*****************************************************************************************************
byte degree[8] = {
  0B00111,
  0B00101,
  0B00111,
  0B00000,
  0B00000,
  0B00000,
  0B00000,
  0B00000
}; // create degree sign
// Set your own API here
String openWeatherMapApiKey = "@@@@@@";

// Set your country code and city name here
String city = "Sibiu";
String countryCode = "RO";

// THE DEFAULT TIMER IS SET TO 10 SECONDS FOR TESTING PURPOSES
// For a final application, check the API call limits per hour/minute to avoid getting blocked/banned
unsigned long lastTime = 0;

// Timer set to 1 hr in ms
unsigned long timerDelay = 5220000;

// set boolean firsttime
bool firstTime = true;
String jsonBuffer;
JSONVar myObject = JSONVar();
//*****************************************************************************************************
int counter1 = 0;
int counter2 = 0;
unsigned long startTime = 0;
unsigned long startTime2 = 0;

void printLocalTime()
{
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("No time available (yet)");
    return;
  }
  //lcd.print(&timeinfo, "%A%H:%M:%S"); // e.g Tuesday 10:30:05
  //lcd.print(&timeinfo, "%d %B %Y");    //e.g November 22 2022

  //Display Time
  lcd.setCursor(0, 0);
  lcd.print(&timeinfo, "%H:%M:%S"); // 10:30:05

  // Display Day
  char *day_string = daynames[timeinfo.tm_wday];
  char buff[64] = "";
  snprintf(buff, 64, "Day: %s", day_string);
  Serial.println(buff);
  lcd.setCursor(0, 1);
  lcd.print(day_string);

  Serial.println("Message 1");
  
} // printLocalTime

// Callback function (get's called when time adjusts via NTP)
void timeavailable(struct timeval *t)
{
  Serial.println("Got time adjustment from NTP!");
  printLocalTime();
}

void setup()
{
  Serial.begin(115200);
  // Setup LCD with backlight and initialize
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, degree);

  // set notification call-back function
  sntp_set_time_sync_notification_cb( timeavailable );
  /**
     NTP server address could be aquired via DHCP,

     NOTE: This call should be made BEFORE esp32 aquires IP address via DHCP,
     otherwise SNTP option 42 would be rejected by default.
     NOTE: configTime() function call if made AFTER DHCP-client run
     will OVERRIDE aquired NTP server address
  */
  sntp_servermode_dhcp(1);    // (optional)
  /**
     This will set configured ntp servers and constant TimeZone/daylightOffset
     should be OK if your time zone does not need to adjust daylightOffset twice a year,
     in such a case time adjustment won't be handled automagicaly.
  */
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2);

  //connect to WiFi
  Serial.printf("Connecting to %s ", ssid);
  lcd.clear();
  lcd.print("Connecting to ");
  lcd.setCursor(0, 1);
  lcd.print(ssid);
  delay(1000);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" CONNECTED");
  lcd.clear();
  lcd.print("CONNECTED");
  delay(2000);
  lcd.clear();

} // setup

void loop()
{
  unsigned long currentTime = millis();
  if (currentTime - startTime >= 1000)
  {
    counter1++;
    counter2++;
    startTime = currentTime;
  }
  // display local time for 50s
  if (counter1 < 50)
  {
    printLocalTime();
    Serial.println("Message 2");
  }
  else if (counter1 == 50)
  {
    counter2 = 0;
    lcd.setCursor(0, 0);
    lcd.print("                ");
  }
  // display local weather for 10s
  else if (counter2 < 10)
  {
    lcd.setCursor(0, 0);
    lcd.print(myObject["main"]["temp"]);
    lcd.write((byte)0);
    lcd.print("C");
    lcd.setCursor(0, 1);
    lcd.print("in ");
    lcd.print(city);
    Serial.println("Message 3");
  }
  else if (counter2 == 10)
  {
    counter1 = 0;
  }

  if (((millis() - lastTime) > timerDelay) || firstTime)
  {
    // Check WiFi connection status
    if (WiFi.status() == WL_CONNECTED)
    {
      String serverPath = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&units=metric&APPID=" + openWeatherMapApiKey;

      jsonBuffer = httpGETRequest(serverPath.c_str());
      Serial.println(jsonBuffer);
      myObject = JSON.parse(jsonBuffer);

      // JSON.typeof(jsonVar) can be used to get the type of the var
      if (JSON.typeof(myObject) == "undefined")
      {
        Serial.println("Parsing input failed!");
        return;
      }
      Serial.println("Message 4");
      // Dump to serial
      Serial.print("JSON object = ");
      Serial.println(myObject);
      Serial.print("Temperature: ");
      Serial.println(myObject["main"]["temp"]);
      Serial.print("Pressure: ");
      Serial.println(myObject["main"]["pressure"]);
      Serial.print("Humidity: ");
      Serial.println(myObject["main"]["humidity"]);
      Serial.print("Wind Speed: ");
      Serial.println(myObject["wind"]["speed"]);
    }
    else
    {
      Serial.println("WiFi Disconnected");
    }
    // Reset timer
    lastTime = millis();
    // disable firsttime
    firstTime = false;
  }

} // loop

String httpGETRequest(const char *serverName)
{
  WiFiClient client;
  HTTPClient http;

  // Your Domain name with URL path or IP address with path
  http.begin(client, serverName);

  // Send HTTP POST request
  int httpResponseCode = http.GET();

  String payload = "{}";

  if (httpResponseCode > 0)
  {
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  }
  else
  {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
  }
  // Free resources
  http.end();
  return payload;
}

I will take a SWAG and say since the program ran for two days it is OK. With that in mind that leaves a hardware problem. To look into that post an annotated schematic (language of electronics) showing all connections, power, ground, power sources and external wiring. Also include links to technical information on each of the hardware devices. I do not do well with word problems or frizzies. It sounds like EMI but I do not really know.

Since you are using strings etc add somewhere in your code a statement to print out the free RAM. The Arduino sometimes has problems with memory leaks when using strings. That number should remain about the same, if it keeps decreasing you have a leak problem and the stack will eventually be overwritten causing the program to go bonkers. It is impossible to tell what it will do, it is different on each piece of code.

1 Like

Right now, you're using multiple String objects and concatenating them like this

String serverPath = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&units=metric&APPID=" + openWeatherMapApiKey;

Each + operation creates a new String object behind the scenes, which could take up more memory over time.

A more efficient way to create the URL is to use a single character array and fill it just once using the snprintf function. snprintf allows you to format a string and store it in a character array. This way, you don't create multiple temporary String objects, saving memory.

Here's how you could do it:

char serverPath[256];  // Make sure the size is enough to hold the entire URL
snprintf(serverPath, sizeof(serverPath), "http://api.openweathermap.org/data/2.5/weather?q=%s,%s&units=metric&APPID=%s", city.c_str(), countryCode.c_str(), openWeatherMapApiKey.c_str());

This method is generally more efficient and helps to prevent potential memory issues.

1 Like

I understand the hardware concerns, but, as I said, I tested other programs, fixed on the same hardware, no problem.
The circuit contains, as I said, an ESP32 (I don't have memory problems, the program is not large), LCD16x1 which I use as a 16x2, with I2C module. In fact, I was using a 5V/650mA adapter, which I replaced with a more powerful one.

There are many things I don't know, I managed to combine two programs, and with help. That 50-10 division still seems to work, because I always and correctly see Messages 1/2/3/4 on the Serial Monitor. Something else that tells me something I don't notice.
I modified the program, as you suggested, I replaced String serverPath with snprintf(serverPath,.... , and I have an error when compiling:

" request for member 'c_str' in 'serverPath', which is of non-class type 'char [256]'
jsonBuffer = httpGETRequest(serverPath.c_str());
"

It suggest that serverPath variable is not of a type that has a c_str() method, meaning it's not an Arduino String

  1. Make sure that serverPath is not being redeclared somewhere else in the code as a C-style string (char array).
  2. If you're cutting and pasting code, make sure that the full String serverPath = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&units=metric&APPID=" + openWeatherMapApiKey; line is intact and not interrupted or altered.

So, this line disappears:

`String serverPath = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&units=metric&APPID=" + openWeatherMapApiKey;

And I replace it with this:
snprintf(serverPath, sizeof(serverPath), "http://api.openweathermap.org/data/2.5/weather?q=%s,%s&units=metric&APPID=%s", city.c_str(), countryCode.c_str(), openWeatherMapApiKey.c_str());

And right after it I have:
jsonBuffer = httpGETRequest(serverPath.c_str());
Where jsonBuffer is a String.

Elsewhere the serverPath no longer appears.

Could you repost the code to try to find where the error is?

Here is what I have for the code you posted at the beginning:

The timeavailable function is missing the return type: The timeavailable function is defined without a return type. It should have a void return type since it doesn't return anything.
The setup() function is missing closing braces: Make sure to add closing braces (}) at the end of the setup() function.
The loop() function is missing closing braces: Similar to the previous issue, make sure to add closing braces (}) at the end of the loop() function.
The httpGETRequest() function is called before it is defined: In the loop() function, the httpGETRequest() function is called but it is defined after the loop() function. Move the definition of the httpGETRequest() function above the loop() function, or add a forward declaration at the beginning of the code.
The use of String for JSON parsing: The code uses the String class to store the JSON response and parse it using the Arduino_JSON library. This can lead to memory fragmentation and unstable behavior on microcontrollers with limited resources like Arduino. It is recommended to use a more memory-efficient approach, such as using char arrays or using a streaming JSON parser like ArduinoJson or cJSO

It is not a matter of how much RAM-usage is reported from the compiler

It is a matter of how often you assign a new string while the program is running.

Each time a String is assigned a new value
A new piece of RAM is occupied. This means all RAM will be occupied after enough time.
And then the code starts to overwrite RAM that is used for other variables.

Another solution is to use SafeStrings. The name is program. SafeStrings are safe to use and never cause such memory-problems like Strings.

best regards Stefan

I think that setup() and loop() are ok.

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

About JSON and parsing, I have to say that I have no ideas, I just found two programs on the internet and I kept trying to make them work together. Should serverPath be converted to String?

#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino_JSON.h>
#include "time.h"
#include "sntp.h"
#include <LiquidCrystal_I2C.h>

//0x3F or 0x27
LiquidCrystal_I2C lcd(0x27, 16, 2);   //LCD Object

const char* ssid       = "@@@";
const char* password   = "@@@";

const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
const long  gmtOffset_sec = 7200;
const int   daylightOffset_sec = 3600;

char *daynames[7] = {
  "Duminica",
  "    Luni",
  "   Marti",
  "Miercuri",
  "     Joi",
  "  Vineri",
  " Sambata"
}; // put them in this order as tm struct 0 = Sunday
//*****************************************************************************************************
byte degree[8] = {
  0B00111,
  0B00101,
  0B00111,
  0B00000,
  0B00000,
  0B00000,
  0B00000,
  0B00000
}; // create degree sign
// Set your own API here
String openWeatherMapApiKey = "@@@";

// Set your country code and city name here
String city = "Sibiu";
String countryCode = "RO";

// THE DEFAULT TIMER IS SET TO 10 SECONDS FOR TESTING PURPOSES
// For a final application, check the API call limits per hour/minute to avoid getting blocked/banned
unsigned long lastTime = 0;

// Timer set to 1 hr in ms
unsigned long timerDelay = 5220000;

// set boolean firsttime
bool firstTime = true;
String jsonBuffer;
JSONVar myObject = JSONVar();
char serverPath[256];  // Make sure the size is enough to hold the entire URL *********************************************************
//*****************************************************************************************************
int counter1 = 0;
int counter2 = 0;
unsigned long startTime = 0;
unsigned long startTime2 = 0;

void printLocalTime()
{
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("No time available (yet)");
    return;
  }
  //lcd.print(&timeinfo, "%A%H:%M:%S"); // e.g Tuesday 10:30:05
  //lcd.print(&timeinfo, "%d %B %Y");    //e.g November 22 2022

  //Display Time
  lcd.setCursor(0, 0);
  lcd.print(&timeinfo, "%H:%M:%S"); // 10:30:05

  // Display Day
  char *day_string = daynames[timeinfo.tm_wday];
  char buff[64] = "";
  snprintf(buff, 64, "Day: %s", day_string);
  Serial.println(buff);
  lcd.setCursor(0, 1);
  lcd.print(day_string);

  Serial.println("Message 1");

} // printLocalTime

// Callback function (get's called when time adjusts via NTP)
void timeavailable(struct timeval *t)
{
  Serial.println("Got time adjustment from NTP!");
  printLocalTime();
}
String httpGETRequest(const char *serverName)
{
  WiFiClient client;
  HTTPClient http;

  // Your Domain name with URL path or IP address with path
  http.begin(client, serverName);

  // Send HTTP POST request
  int httpResponseCode = http.GET();

  String payload = "{}";

  if (httpResponseCode > 0)
  {
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  }
  else
  {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
  }
  // Free resources
  http.end();
  return payload;
}
void setup()
{
  Serial.begin(115200);
  // Setup LCD with backlight and initialize
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, degree);

  // set notification call-back function
  sntp_set_time_sync_notification_cb( timeavailable );
  /**
     NTP server address could be aquired via DHCP,

     NOTE: This call should be made BEFORE esp32 aquires IP address via DHCP,
     otherwise SNTP option 42 would be rejected by default.
     NOTE: configTime() function call if made AFTER DHCP-client run
     will OVERRIDE aquired NTP server address
  */
  sntp_servermode_dhcp(1);    // (optional)
  /**
     This will set configured ntp servers and constant TimeZone/daylightOffset
     should be OK if your time zone does not need to adjust daylightOffset twice a year,
     in such a case time adjustment won't be handled automagicaly.
  */
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2);

  //connect to WiFi
  Serial.printf("Connecting to %s ", ssid);
  lcd.clear();
  lcd.print("Connecting to ");
  lcd.setCursor(0, 1);
  lcd.print(ssid);
  delay(1000);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" CONNECTED");
  lcd.clear();
  lcd.print("CONNECTED");
  delay(2000);
  lcd.clear();

} // setup

void loop()
{
  unsigned long currentTime = millis();
  if (currentTime - startTime >= 1000)
  {
    counter1++;
    counter2++;
    startTime = currentTime;
  }
  // display local time for 50s
  if (counter1 < 50)
  {
    printLocalTime();
    Serial.println("Message 2");
  }
  else if (counter1 == 50)
  {
    counter2 = 0;
    lcd.setCursor(0, 0);
    lcd.print("                ");
  }
  // display local weather for 10s
  else if (counter2 < 10)
  {
    lcd.setCursor(0, 0);
    lcd.print(myObject["main"]["temp"]);
    lcd.write((byte)0);
    lcd.print("C");
    lcd.setCursor(0, 1);
    lcd.print("in ");
    lcd.print(city);
    Serial.println("Message 3");
  }
  else if (counter2 == 10)
  {
    counter1 = 0;
  }

  if (((millis() - lastTime) > timerDelay) || firstTime)
  {
    // Check WiFi connection status
    if (WiFi.status() == WL_CONNECTED)
    {
      //String serverPath = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&units=metric&APPID=" + openWeatherMapApiKey;
      
      snprintf(serverPath, sizeof(serverPath), "http://api.openweathermap.org/data/2.5/weather?q=%s,%s&units=metric&APPID=%s", city.c_str(), countryCode.c_str(), openWeatherMapApiKey.c_str());
      
      jsonBuffer = httpGETRequest(serverPath.c_str());
      Serial.println(jsonBuffer);
      myObject = JSON.parse(jsonBuffer);

      // JSON.typeof(jsonVar) can be used to get the type of the var
      if (JSON.typeof(myObject) == "undefined")
      {
        Serial.println("Parsing input failed!");
        return;
      }
      Serial.println("Message 4");
      // Dump to serial
      Serial.print("JSON object = ");
      Serial.println(myObject);
      Serial.print("Temperature: ");
      Serial.println(myObject["main"]["temp"]);
      Serial.print("Pressure: ");
      Serial.println(myObject["main"]["pressure"]);
      Serial.print("Humidity: ");
      Serial.println(myObject["main"]["humidity"]);
      Serial.print("Wind Speed: ");
      Serial.println(myObject["wind"]["speed"]);
    }
    else
    {
      Serial.println("WiFi Disconnected");
    }
    // Reset timer
    lastTime = millis();
    // disable firsttime
    firstTime = false;
  }

} // loop

Try to replace : char serverPath[256];
By : char serverPath[496];
The serverPath string you provided has a length of 62 characters. Since each character requires 8 bits to store, you would need a total of 62 * 8 = 496 bits to store the serverPath string.

If you create an array of chars. Each char has already 8 bits.
So defining it with the number of bytes is sufficient.

But you have to add one byte for the terminating zero
and
you have to make sure that the array of char holds enough bytes even for longest string you want to store
and
you have to make sure to never write behind the boundaries of your array
this is the reason why I recommend using SafeStrings

The more Strings
the more RAM-memory is used over time.
Do you see the CAPITAL "S"
The evil thing is the variable type Strings

To say it very clear:
Using Strings without knowing in detail how to avoid RAM-overfilling is dangerous.

You have basically three choices

  1. Using Strings with the described problems
  2. Using char-arrays and investing time into learning how to do careful boundary-checking
  3. using SafeStrings and investing time into learning the syntax how to use SafeStrings

best regards Stefan

Hi @cristian10001,

I checked your sketch and came across these lines

// Timer set to 1 hr in ms 
unsigned long timerDelay = 5220000; 

which defines the next time (after firstTime) when the controller tries to retrieve data from the weather server.

  • 5220000 msec -> 5220 secs -> 87 min -> 1 hr and 27 mins

Maybe it's just a coincidence but it matches quite nicely with your "1-2 hours" operation ...

I like to recommend the old fashioned way of systematic debugging:

  • Remove/comment out the LCD parts and just display the data via Serial
  • Reduce the time between calls of the weather page
  • Check if any problems occur

If this runs without problems:

  • Add the LCD parts again
  • Check for any problems

P.S.: I managed to run your code on Wokwi and made a number of changes:

/*
   
   Forum: https://forum.arduino.cc/t/blocking-program-or-the-lcd/1167850/14
   Wokwi: https://wokwi.com/projects/375742712230180865

*/

#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino_JSON.h>
#include "time.h"
#include "sntp.h"
#include <LiquidCrystal_I2C.h>

//0x3F or 0x27
LiquidCrystal_I2C lcd(0x27, 16, 2);   //LCD Object

// Set your own API here
constexpr char OPENWEATHERMAPAPI[] = "@@@";
// Set your country code and city name here
constexpr char CITY[]              = "London";
constexpr char COUNTRYCODE[]       = "GB";
String serverPath = String("http://api.openweathermap.org/data/2.5/weather?q=") + CITY + "," + COUNTRYCODE + "&units=metric&APPID=" + OPENWEATHERMAPAPI;

const char* ssid       = "Wokwi-GUEST";
const char* password   = "";

const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
const long  gmtOffset_sec = 7200;
const int   daylightOffset_sec = 3600;

// Timer set to 1 hr in ms                      -> That should read 60 min x 60 sec x 1000 = 3600000
constexpr unsigned long TIMERDELAY = 5220000;   // This is actually 1 hr and 27 mins

char *daynames[7] = {
  "Duminica",
  "    Luni",
  "   Marti",
  "Miercuri",
  "     Joi",
  "  Vineri",
  " Sambata"
}; // put them in this order as tm struct 0 = Sunday
//*****************************************************************************************************
byte degree[8] = {
  0B00111,
  0B00101,
  0B00111,
  0B00000,
  0B00000,
  0B00000,
  0B00000,
  0B00000
}; // create degree sign

struct weatherData {
  String temp;
  String pressure;
  String humidity;
  String windspeed;
} weather;



//*****************************************************************************************************

void setup()
{
  Serial.begin(115200);
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, degree);
  Serial.println(serverPath);

  // sntp_set_time_sync_notification_cb( onTimeAvailable );
  sntp_servermode_dhcp(1);    // (optional)
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2);

  Serial.printf("Connecting to %s ", ssid);
  lcd.clear();
  lcd.print("Connecting to ");
  lcd.setCursor(0, 1);
  lcd.print(ssid);
  delay(1000);

  WiFi.begin(ssid, password, 6);  // Added channel 6 to decrease connection time on WOKWI
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println(" CONNECTED");
  lcd.clear();
  lcd.print("CONNECTED IP =");
  lcd.setCursor(0, 1);
  lcd.print(WiFi.localIP());
  delay(2000);
  lcd.clear();
} // setup

void loop()
{
  displayDataOnLCD();
  readWeatherData();
}


void readWeatherData() {
  static boolean firstTime = true;
  static unsigned long lastTime = 0;
  String jsonBuffer;
  JSONVar myObject = JSONVar();
  if (((millis() - lastTime) > TIMERDELAY) || firstTime)
  {
    // Check WiFi connection status
    if (WiFi.status() == WL_CONNECTED)
    {

      jsonBuffer = httpGETRequest(serverPath.c_str());
      Serial.println(jsonBuffer);
      myObject = JSON.parse(jsonBuffer);

      // JSON.typeof(jsonVar) can be used to get the type of the var
      if (JSON.typeof(myObject) == "undefined")
      {
        Serial.println("Parsing input failed!");
        return;
      }

      weather.temp      = JSON.stringify(myObject["main"]["temp"]);
      weather.pressure  = JSON.stringify(myObject["main"]["pressure"]);
      weather.humidity  = JSON.stringify(myObject["main"]["humidity"]);
      weather.windspeed = JSON.stringify(myObject["wind"]["speed"]);

      // Dump to serial
      Serial.print("JSON object = ");
      Serial.println(myObject);
      Serial.print("Temperature: ");
      Serial.println(weather.temp);
      Serial.print("Pressure: ");
      Serial.println(weather.pressure);
      Serial.print("Humidity: ");
      Serial.println(weather.humidity);
      Serial.print("Wind Speed: ");
      Serial.println(weather.windspeed);
    }
    else
    {
      Serial.println("WiFi Disconnected");
    }
    lastTime = millis();
    firstTime = false;
  }
}


void printLocalTime()
{
  static boolean timeAvailable = true;
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    if (timeAvailable) {
      lcd.clear();
      Serial.println("No time available (yet)");
      lcd.print("Time not");
      lcd.setCursor(0, 1);
      lcd.print("available (yet)");
    }
    timeAvailable = false;
    return;
  }
  if (!timeAvailable) {
    lcd.clear(); // Clear LCD when time becomes available (again)
  }
  timeAvailable = true;
  lcd.setCursor(0, 0);
  lcd.print(&timeinfo, "%H:%M:%S"); // 10:30:05
  Serial.println(&timeinfo, "%H:%M:%S");

  // Display Day
  Serial.print("Day : ");
  Serial.println(daynames[timeinfo.tm_wday]);
  lcd.setCursor(0, 1);
  lcd.print(daynames[timeinfo.tm_wday]);

} // printLocalTime


void displayDataOnLCD() {
  static uint16_t counter = 0;
  static unsigned long startTime = 0;
  byte mode;
  unsigned long currentTime = millis();
  if (currentTime - startTime >= 1000)
  {
    counter++;
    // display local time for 50s
    if (counter >= 60) {
      counter = 0; // Reset counter when weather was displayed for 10 secs
      lcd.clear(); // Clear LDC for local time display
    }
    if (counter < 50 ) {
      mode = 0;    // Display local time for 50 secs (0..49)
    }
    if (counter == 50) {
      mode = 1;    // Clear LCD and start to display weather
    }
    if (counter > 50)  {
      mode = 2;    // Display weather for 10 secs (50 ... 59)
    }
    switch (mode) {
      case 0 : // Display local time
        printLocalTime();
        break;
      case 1:   // Clear LCD first
        lcd.clear();
      // and directly apply case 2 -> no break; !!!!
      case 2 : // Display local weather for 10s
        lcd.setCursor(0, 0);
        lcd.print(weather.temp);
        lcd.write((byte)0);
        lcd.print("C");
        lcd.setCursor(0, 1);
        lcd.print("in ");
        lcd.print(CITY);
        break;
    }
    startTime = currentTime;
  }
}

String httpGETRequest(const char *serverName)
{
  WiFiClient client;
  HTTPClient http;

  // Your Domain name with URL path or IP address with path
  http.begin(client, serverName);

  // Send HTTP POST request
  int httpResponseCode = http.GET();

  String payload = "{}";
  // Weather API seems to return 200 if data are valid ...
  if (httpResponseCode >= 200 && httpResponseCode <= 299 )
  {
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  }
  else
  {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
  }
  // Free resources
  http.end();
  return payload;
}

See here: https://wokwi.com/projects/375742712230180865

If you fill in your personal data in these lines of the code above it should run for you:

constexpr char OPENWEATHERMAPAPI[] = "@@@";
// Set your country code and city name here
constexpr char CITY[]              = "London";
constexpr char COUNTRYCODE[]       = "GB";
String serverPath = String("http://api.openweathermap.org/data/2.5/weather?q=") + CITY + "," + COUNTRYCODE + "&units=metric&APPID=" + OPENWEATHERMAPAPI;

const char* ssid       = "Wokwi-GUEST";
const char* password   = "";

You may remove the ",6" in this line

WiFi.begin(ssid, password, 6);

it just speeds up the use in Wokwi.

Some of my observations regarding your original code from post #1:

  • I have separated the data retrieval and the lcd print function; this way I could verify that the problem was independent from the JSON part.
  • The main problem seems to come from the callback function (void timeavailable(struct timeval *t)) which I removed as it is not required for your purposes. I have not yet investigated why it collided. Maybe someone else knows already or has an idea ...
  • The data from the openweathermap API were accepted even under error conditions (e.g. httpResponseCode == 401). Now the payload is only retrieved if the httpResponseCode is within the 2xx range (200..299). If no data are avaliable the return of the parser will be "null" instead of any value.
  • The data retrieved via API are now made available in a global struct (weather.temp etc.) for further use.
  • The lcd print routine uses a simple state machine, prints only once a second and is completely self contained regarding what data and how long they are displayed. Using that principle it should be easy (at least easier) to add further information to the display later.

I hope that the code runs also well in a "real" environment ...

Good luck!
ec2021

P.P.S.: Just if one is interested in a sync callback:

//*****************************************************************************************************
void timeSyncCallback(struct timeval *tv)
{
  Serial.println("\n----Time Sync-----");
  Serial.println(ctime(&tv->tv_sec));
}

void setup()
{
  Serial.begin(115200);
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, degree);
  Serial.println(serverPath);
 
  sntp_set_time_sync_notification_cb(timeSyncCallback);
  sntp_set_sync_interval(20000);

The snippet above shows how to implement a 20 sec update of the ntp time and a call and realisation of the callback function that does not collide. Finally it looks as if a call to "getLocalTime()" inside the callback routine fails ... because it is to quick. The update of the local time seems not to be yet finished ...

Hi!
I have to wait for the weekend, I have a little more free time. I ran the program, I waited to see how it behaves so I can describe it to you. Obviously I have questions.
First, regarding the time period during which weather data is collected from the server, I set 87 minutes because I use a free service, limited to 1000 calls in 24 hours, otherwise I risk being banned. Then, agreed, when it comes to debugging, there really aren't many options, and printing some messages on Serial Monitor can help, and since I don't know programming, I really didn't think of anything else, and for this program, that's what I tried to do.

I ran the program in the circuit, I notice a delay of about 2-3s on the clock, it was like that before, sometimes. I think it also depends on how quickly the ESP32 connects to WiFi and then synchronizes. I let the circuit run for 2-3 hours, no more strange characters appeared. Now I want to make a case. It remains to be seen if in a month it still works well, and I will see what I can do. Or I'll just put it in a box under the table.

A few questions:

  • is it possible that the clock is delayed due to the WiFi connection? I don't want a Swiss watch, but it's good to know if later it wouldn't work well, and it might delay excessively.
  • again, why would it be necessary that readWeatherData(); to always be called in loop(), but not printLocalTime()? Even if printLocalTime() is always present in displayDataOnLCD() .
    As for programming, I can't say that I have great skill, it's a hobby for me.

Thank you for your time, to be sure that it took some time to rewrite the program.

Thanks for the reply and info. I will check them.
I have come across char arrays more often, and maybe it would be better to use them.

I am quite pleased with how it turned out, I also made the case from foam board and Plexiglas.
I also have this sketch that prints a fly-in effect, and to which I'm trying to add a fly-out effect, in the end it will come out a bit more fun.

Thanks again!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.