Feel free to leave comments if you have questions.
2. Project FrameworkThe weather clock project can be roughly divided into three parts:
1. Connecting to Wi-Fi
2. Requesting an API to obtain weather data
3. GUI display
2.1 Connecting to Wi-FiFor the Wi-Fi connection and HTTP request part, I referred to the article “[Tutorial] M61-32S Series Connect to Wi-Fi and Send HTTP Requests.”The Wi-Fi connection code does not require modification.
However, as the author of that post mentioned, there is a chance the HTTP request may freeze. I found that the recv function used is blocking indefinitely; if the request doesn’t return, the program can crash.
I added the following lines before recv to set a timeout, which temporarily solves the issue. My program has run for one to two hours without freezing, but further testing is needed.
// set recv timeoutstruct timeval tv_out;
tv_out.tv_sec = 5;
tv_out.tv_usec = 0;
2.2 Fetching Weather DataI’m using the Xinzhi Weather API to pull a 3-day forecast.The HTTP response is JSON, parsed with the open-source cJSON library.
Many people report that parsing with cJSON can easily cause the program to freeze. This usually happens because the cJSON objects are not deleted in time. You must delete the object after every parse; otherwise, the program may crash after many parses.
cJSON_Delete(root); // Always release memory after calling cJSON_Parse
Parsing code example (abridged):
int weather_info_parse(char *weather_data_buf)
{
// Process the received data accordingly
uint8_t i, j;
uint8_t result_array_size = 0;
uint8_t daily_array_size = 0;
cJSON *root = NULL;
cJSON *item = NULL;
cJSON *results_root = NULL;
cJSON *daily_root = NULL;
root = cJSON_Parse(weather_data_buf);
if (!root)
{
// ESP_LOGI(TAG, "Error before: [%s]\n", cJSON_GetErrorPtr());
LOG_I("Error before: [%s]\r\n", cJSON_GetErrorPtr());
cJSON_Delete(root); /*After each call to the cJSON_Parse function, the memory must be released*/
return -1;
}
// ESP_LOGI(TAG, "%s\r\n", cJSON_Print(root)); /*Print the complete data in JSON format*/
cJSON *Presult = cJSON_GetObjectItem(root, "results"); /*results key-value pairs are arrays,*/
result_array_size = cJSON_GetArraySize(Presult); /*Find out how many elements are in the results key-value pair array*/
// ESP_LOGI(TAG, "Presult array size is %d\n",result_array_size);
for (i = 0; i < result_array_size; i++)
{
cJSON *item_results = cJSON_GetArrayItem(Presult, i);
char *sresults = cJSON_PrintUnformatted(item_results);
results_root = cJSON_Parse(sresults);
if (!results_root)
{
// ESP_LOGI(TAG, "Error before: [%s]\n", cJSON_GetErrorPtr());
LOG_I("Error before: [%s]\r\n", cJSON_GetErrorPtr());
cJSON_Delete(root); /*Free memory after each call to the cJSON_Parse function*/
return -1;
}
/*-------------------------------------------------------------------*/
cJSON *Plocation = cJSON_GetObjectItem(results_root, "location");
item = cJSON_GetObjectItem(Plocation, "id");
user_sen_config.id = cJSON_Print(item);
// ESP_LOGI(TAG, "id:%s\n", user_sen_config.id); /*Print one by one*/
item = cJSON_GetObjectItem(Plocation, "name");
user_sen_config.name = cJSON_Print(item);
// ESP_LOGI(TAG, "name:%s\n", cJSON_Print(item));
item = cJSON_GetObjectItem(Plocation, "country");
user_sen_config.country = cJSON_Print(item);
// ESP_LOGI(TAG, "country:%s\n", cJSON_Print(item));
item = cJSON_GetObjectItem(Plocation, "path");
user_sen_config.path = cJSON_Print(item);
// ESP_LOGI(TAG, "path:%s\n", cJSON_Print(item));
item = cJSON_GetObjectItem(Plocation, "timezone");
user_sen_config.timezone = cJSON_Print(item);
// ESP_LOGI(TAG, "timezone:%s\n", cJSON_Print(item));
item = cJSON_GetObjectItem(Plocation, "timezone_offset");
user_sen_config.timezone_offset = cJSON_Print(item);
// ESP_LOGI(TAG, "timezone_offset:%s\n", cJSON_Print(item));
/*-------------------------------------------------------------------*/
cJSON *Pdaily = cJSON_GetObjectItem(results_root, "daily");
daily_array_size = cJSON_GetArraySize(Pdaily);
// ESP_LOGI(TAG, "Pdaily array size is %d\n",daily_array_size);
for (j = 0; j < daily_array_size; j++)
{
cJSON *item_daily = cJSON_GetArrayItem(Pdaily, j);
char *sdaily = cJSON_PrintUnformatted(item_daily);
daily_root = cJSON_Parse(sdaily);
if (!daily_root)
{
LOG_I("Error before: [%s]\r\n", cJSON_GetErrorPtr());
return -1;
}
item = cJSON_GetObjectItem(daily_root, "date");
user_sen_config.day_config[j].date = item->valuestring; // cJSON_Print(item)
item = cJSON_GetObjectItem(daily_root, "text_day");
user_sen_config.day_config[j].text_day = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "code_day");
user_sen_config.day_config[j].code_day = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "text_night");
user_sen_config.day_config[j].text_night = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "code_night");
user_sen_config.day_config[j].code_night = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "high");
user_sen_config.day_config[j].high = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "low");
user_sen_config.day_config[j].low = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "rainfall");
user_sen_config.day_config[j].rainfall = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "precip");
user_sen_config.day_config[j].precip = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "wind_direction");
user_sen_config.day_config[j].wind_direction = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "wind_direction_degree");
user_sen_config.day_config[j].wind_direction_degree = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "wind_speed");
user_sen_config.day_config[j].wind_speed = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "wind_scale");
user_sen_config.day_config[j].wind_scale = cJSON_Print(item);
item = cJSON_GetObjectItem(daily_root, "humidity");
user_sen_config.day_config[j].humidity = cJSON_Print(item);
cJSON_Delete(daily_root); /*Free memory after each call to cJSON_Parse*/
}
/*-------------------------------------------------------------------*/
item = cJSON_GetObjectItem(results_root, "last_update");
user_sen_config.last_update = cJSON_Print(item);
cJSON_Delete(results_root); /*Free memory after each call to cJSON_Parse*/
}
cJSON_Delete(root); /*Free memory after each call to cJSON_Parse*/
return 0;
}
2.3 GUI DesignAfter enabling LVGL in your project, you may find that neither your own code nor the examples under..\AiPi-Open-Kits\aithinker_Ai-M6X_SDK\examples will run.Don’t panic—your configuration is probably fine.
The GUI was designed using NXP's GUI Guider. Here's how to port its files. Once your program has configured the LVGL components and the demo is running correctly, move the custom and generated folders from the GUI project into your own project and configure the paths in the Cmake.list file.
You may encounter errors with the generated images in the images folder within the generated folder. To resolve the issue, simply replace \lgvl\lvgl.h with lvgl.h.
Add the header file to main.c:
// lvgl
#include "gui_guider.h"
#include "events_init.h"
Instantiate the objects in the main function.
setup_ui(&guider_ui);
events_init(&guider_ui);
Finally, I created a FreeRTOS task to keep LVGL alive:
void lvgl_task(void *pvParameters)
{
while (1)
{
// LOG_I("lvgl_task is runing...\r\n");
lv_task_handler();
vTaskDelay(1);
// bflb_mtimer_delay_ms(1);
}
}
Here is the simple UI I drew in GUI Guider:
Comments