文章目录
功能实现:
时间更新
-
时间功能需要在custom.h里面exten data、time
-
未知问题,字符串位置索引错误,把char network 分别给time和data会互相重叠。。
-
解决:使用srting 字符串格式,并且直接传给lvgl为字符串转换为char*的函数 string.(c_str())。
// home_data_str_temp=network_time;//日期暂存 // home_time_str_temp=network_time;//时间暂存 这句话的问题。字符串会先赋值,然后后面的就多出来了。。 大意了。。。。
-
esp32 定时器一共三个,0-3号
-
定时器的开启要在函数初始化结束后。
-
定时器中不能对时间进行校准,不然就会错误。
-
arduino的头文件包含真是吐了!!!
-
时间更新——ticker定时,每秒定时,到60s的时候再刷新。
-
time_hour=atoi(home_data_str.substring(8,10).c_str());//时-数值型 memset(temp, 0, sizeof(temp)); sprintf(temp,"%d",time_minute); String weather_code_str=weatherdata.weather_code; weather_code_str.toInt()
-
String转整数型 使用 String.toInt() (优先)
-
字符串(char*)转整数型 使用 atoi() //遇到问题,不是函数转换的问题,是数值传递时出现问题,要使用int来接收而不是char
-
字符串(char*)剪裁 substring()
-
整形转字符串 sprintf()
-
字符串(char*)清空函数 memset()
-
sting转char* string.c_str()
-
存在偶尔的时间显示错误问题
-
{ 显示错误
-
测试时使用预编译命令
#define test 1 #if(test) //要测试的函数,测试时运行。 不加{} #else //正常的函数,测试时不运行。 #endif
-
-
-
-
-
-
-
- 遇到指针赋值的,一定要确定要赋值的指针是否为static!!!!!局部指针赋值给别人就是错误的!!!
-
-
-
-
-
时间更新函数,结束。
天气信息更新
-
联网才会更新天气和日期,离线模式下,只有时间有效(时钟计时推算,且长时间下有飘逸)
-
错误
void WIFIconnect::connect_server(String city)
{
struct WetherData
{
char city[32];
char code_city[32];
char weather[64];
char high[32];
char low[32];
char humi[32];
};
const char *host = "api.seniverse.com";
const char *privateKey = "SNfnJoTWHKFu_rJrW";
const char *language = "en";
WiFiClient client;
if (!client.connect(host, 80))
{
Serial.println("Connect server failed!"); //网络连接失败,无网络。
return;
}
String getUrl = "/v3/weather/daily.json?key=";
getUrl += privateKey;
getUrl += "&location=";
getUrl += city;
getUrl += "&language=";
getUrl += language;
client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
Serial.println("");
char endOfHeaders[] = "\r\n\r\n";
bool ok = client.find(endOfHeaders);
if (!ok)
{
Serial.println("No response or invalid response!");
}
String line="";
line += client.readStringUntil('\n');
DynamicJsonDocument doc(1400);
DeserializationError error = deserializeJson(doc, line);
if (error)
{
Serial.println("deserialize json failed");
return;
}
struct WetherData weatherdata = {0};
strcpy(weatherdata.city, doc["results"][0]["location"]["name"].as<const char*>());
strcpy(weatherdata.weather, doc["results"][0]["daily"][0]["text_day"].as<const char*>());
strcpy(weatherdata.high, doc["results"][0]["daily"][0]["high"].as<const char*>());
strcpy(weatherdata.low, doc["results"][0]["daily"][0]["low"].as<const char*>());
strcpy(weatherdata.humi, doc["results"][0]["daily"][0]["humidity"].as<const char*>());
Serial.print("City:");
Serial.println(weatherdata.city);
Serial.print("textDay:");
Serial.println(weatherdata.weather);
Serial.print("temp high:");
Serial.println(weatherdata.high);
Serial.print("temp low:");
Serial.println(weatherdata.low);
Serial.print("humi:");
Serial.println(weatherdata.humi);
client.stop();
}
已解决,天气信息json索引错误,有[ ]的要加[0]
strcpy(weatherdata.weather_text, doc["results"][0]["now"]["text"].as<const char*>());
天气图片信息,要注释掉设置尺寸的信息,适应自动调整图片。
lv_obj_set_size(ui->home_img_weather, 51, 51);
-
索引图片正常
-
天气暂无问题
菜单配置
-
回菜单配置,配置一个结构体,结构体里放置需要全局引用的变量。
-
struct Weather_Overall_Data//main.h中 { String lv_home_data; String lv_home_time; char time_hour; char time_minute; char time_sec; }; struct Weather_Overall_Data weather_overall_data//main.cpp中 //指针赋值:把string直接传给string、把char*直接传给string,更加安全。。且不需要使用static修饰。 weather_overall_data.lv_home_time=home_time_str_temp; //strcpy(wether_overall_data.lv_home_time,home_time_str_temp.c_str());//传出参数 //这样做不需要再使用static修饰string //但这样会出现字符串叠加问题 //需要home_data_str_temp为static // lv_home_data=(char*)home_data_str_temp.c_str();//传出参数 // lv_home_time=(char*)home_time_str_temp.c_str();
如果需要在C中使用string,应该包含 string.h 。
-
问题1:没有定义的引用,但这个函数却是有定义的。有可能是在cpp文件里面引用了c文件里的函数,或者是在c文件里面引用了cpp文件里的函数。
-
C里面不能编译CPP文件。c里面不能编译cpp文件
-
c和c++混合编译
-
在C++里面编译C文件的函数 解决办法: 也可以使用在c里面编译C++文件的方法。
-
直接在c++文件里对头文件进行extern"C"{}
extern "C" { #include "a.h" };
-
在C里面编译C++文件里面的函数 解决办法:
-
在c++文件对应的.h 里在函数声明时使用 extern"C"{}
#ifdef __cplusplus extern "C"{ #endif void function(void); #ifdef __cplusplus } #endif
通常一个函数为了兼容c和c++会被这样上面那样写。
其中__cplusplus就是c++的意思。也就是说,这个函数在c++文件中被编译时,是要包含extern“C”{} 的,代表这是一个c函数。
-
另外,需要注意的是!C里面不可以包含具有C++关键字的头文件(如string),如果需要调用这些数据(如string),必须在C++文件里对他封装为函数,再在头文件里对该函数使用extern“C”{}即可。即,使用的时候需要通过函数的形式间接调用数据。(从此处也可以看出来,#include 里面的代码只是替换的作用,并没有被编译,只有在.c或.cpp里面的文件才会被编译为.o)
-
-
-
至此,参数全部可以传出来了并且各函数间可以互相调用。
-
还需要主页间切换
-
把几个菜单都加入group。
按键按下后要释放。,但又不能立马释放。
-
问题 :进入菜单再回来就会出错。
- 原因:动画的问题,动画还没有结束,就对对象刷新或加入组。
- 解决:去掉动画的延迟。
-
问题:图片没有被选中动画(focused),只有点击动画(pressed)和被控动画(check)。
- 使用全屏固定位选中。
- 或者使用背景图片,前景是按键,这样按键就有被选择的框了。(优先)
- 先创建7个图像、再创建7个按键、然后按键按下后跳转到对应的功能。
- 已实现
-
问题:进入菜单后一分钟左右会出现错误,并且重启。
- 原因:定时器更新刷新时间,在没有对象的时候刷新对象。
- 解决:刷新对象的时候判断当前菜单在哪,在主界面的时候再刷新。
- 结构体内添加一个当前菜单位置,lv_home_data_refresh这个函数内部对菜单位置判断即可。
-
开机启动校准一次时间和天气。
-
时间校准时刷新时间和日期数据。
-
天气校准时刷新天气数据:图片、地区、温度。
-
WiFi 配置UI
- 设置ui
- lvgl 加入组(group)是由顺序的,先加入的为第一个组,编码值增加时会进入下一个组。但最后一个加入的组会被先预选中。所以,按照顺加入组即可,将最后一个需要先显示的组放到最后一个即可。(如果这个组时单独的一个的话,并且与其他组没有前后顺序)
- lvgl的switch居然是按键释放的时候动作,所以要在转切屏幕动画结束之前把按键释放了。
Drop down list
-
使用 Drop down list 官方代码
#include "../../../lv_examples.h"
#include <stdio.h>
#if LV_USE_DROPDOWN
static void event_handler(lv_obj_t * obj, lv_event_t event)
{
if(event == LV_EVENT_VALUE_CHANGED) {
char buf[32];
lv_dropdown_get_selected_str(obj, buf, sizeof(buf));
printf("Option: %s\n", buf);
}
}
void lv_ex_dropdown_1(void)
{
/*Create a normal drop down list*/
lv_obj_t * ddlist = lv_dropdown_create(lv_scr_act(), NULL);
lv_dropdown_set_options(ddlist, "Apple\n"
"Banana\n"
"Orange\n"
"Melon\n"
"Grape\n"
"Raspberry");
lv_obj_align(ddlist, NULL, LV_ALIGN_IN_TOP_MID, 0, 20);
lv_obj_set_event_cb(ddlist, event_handler);
}
#endif
list被改变时,调用这个函数:
void ddlist_value_changed(void)
{
char buf[32];
lv_dropdown_get_selected_str(guider_ui.wifi_config_ddlist_1, buf, sizeof(buf));
printf("Option: %s\n", buf);
}
lv_dropdown_get_selected_str函数的使用:
/**
* Get the current selected option as a string
* @param ddlist pointer to ddlist object
* @param buf pointer to an array to store the string
* @param buf_size size of `buf` in bytes. 0: to ignore it.
*/
void lv_dropdown_get_selected_str(const lv_obj_t * ddlist, char * buf, uint32_t buf_size)
-
list添加成员:
-
/** * Add an options to a drop down list from a string. Only works for dynamic options. * @param ddlist pointer to drop down list object * @param option a string without '\n'. E.g. "Four" * @param pos the insert position, indexed from 0, LV_DROPDOWN_POS_LAST = end of string */ void lv_dropdown_add_option(lv_obj_t * ddlist, const char * option, uint32_t pos)
-
pos=0时,默认 后加入的会放在第一个位置
-
优先显示最后一个list。
-
list清空成员
-
/** * Clear any options in a drop down list. Static or dynamic. * @param ddlist pointer to drop down list object */ void lv_dropdown_clear_options(lv_obj_t * ddlist)
WiFi配置
- 界面中的 取消,实际上读取spiffs文件系统中上次配置成功的WiFi信息。
- 界面中的 应用,实际上是将WiFi信息写入到spiffs文件系统中。
lvgl使用虚拟键盘
-
问题:wifi写入密码的时候无法聚焦到键盘上
-
解决: lv_group_focus_obj(kb);强制聚焦。
-
官方源码的解释:
-
static lv_obj_t * kb; static lv_obj_t * ta; static void kb_event_cb(lv_obj_t * keyboard, lv_event_t event) { lv_keyboard_def_event_cb(kb, event);//键盘回调函数 if(event == LV_EVENT_APPLY || event == LV_EVENT_CANCEL) {//如果关闭或应用按键被单击 lv_keyboard_set_textarea(kb, NULL); //取消为键盘分配的输入框 lv_obj_del(kb);//删除键盘对象 kb = NULL;//清除对象 } } static void kb_create(void) { kb = lv_keyboard_create(lv_scr_act(), NULL);//创建一个键盘 lv_keyboard_set_cursor_manage(kb, true);//设置启动键盘的文本提示光标 lv_obj_set_event_cb(kb, kb_event_cb);//这是键盘的回调函数 lv_keyboard_set_textarea(kb, ta);//为键盘分配一个文本区域 ,将单击的字符自动放在此处。 } static void ta_event_cb(lv_obj_t * ta_local, lv_event_t event) { if(event == LV_EVENT_CLICKED && kb == NULL) { //如果输入框被按下,并且键盘对象没有被创建。LV_EVENT_PRESSED kb_create(); } } void lv_ex_keyboard_1(void) { /*Create a text area. The keyboard will write here*/ ta = lv_textarea_create(lv_scr_act(), NULL);//创建到当前屏幕(容器)上 lv_obj_align(ta, NULL, LV_ALIGN_IN_TOP_MID, 0, LV_DPI / 16);//对齐 lv_obj_set_event_cb(ta, ta_event_cb);//输入框回调函数 lv_textarea_set_text(ta, "");//设置输入框内容 lv_coord_t max_h = LV_VER_RES / 2 - LV_DPI / 8;//获得坐标 if(lv_obj_get_height(ta) > max_h) lv_obj_set_height(ta, max_h);//设置高输入框的高 kb_create(); }
-
自己使用只需要把LV_EVENT_CLICKED换成 LV_EVENT_PRESSED就行了,然后添加自己的函数处理一下
-
void password_input(void) { lv_ex_keyboard_1(); lv_group_add_obj(group, ta); lv_group_add_obj(group, kb); lv_group_focus_obj(kb); //已经被聚焦,点一下确认键就可以了。 button_ent_click();//模拟点击确认。 }
-
问题,输入退出后(删除对象),group会聚焦到“应用”上无法聚焦其他控件
-
尝试解决:在删除对象的时候强制聚焦到其他控件上
- 聚焦后无法再移动。
-
尝试解决:重新加入全部的组
- 无响应。
-
尝试解决:在删除对象前强制聚焦到其他控件上
- 正常
- 原因:焦点还focus在键盘上,但是键盘对象却被删除了,导致无法正常退出focus和obj,从而无法切换到group的其他obj。
-
编码长按要有加速
-
关联文本框。
-
把文本框的内容取出来,
-
链接选中的WiFi
-
存入spiffs
-
结束,但有小问题
-
已按照wifi强度顺序添加,并且修复无 法选择WIFI退出。
WIFI问题
-
E (16232) wifi:Set status to INIT 长时间连接WIFI但是不连接网络,就会出错。
-
尝试
-
void setup(void) { Serial.begin(115200); WiFi.begin(ssid, password); delay(1000); WiFi.disconnect(); delay(1000); WiFi.begin(ssid, password); delay(1000); ... }
- 重启路由器可以解决,但是其他设备可以正常连接,说明其实并不是路由器的问题,还是ESP32的问题。
-
-
开机启动画面
-
wifi强度显示。
WiFi.RSSI()
- 返回int型,转换一下就可以
- WIFI强度:~ -80 弱、-81 -71一般、-70 -0 信号强、
- 已添加wifi强度图像显示
- 问题:WIFI显示未连接
- WIFI强度显示正常。
其他
ui已完成
主界面切换ui
- 添加对应的逻辑
- 应用键被按下后,要返回home。
- 添加逻辑函数
- location逻辑完成
逻辑问题
- 更改的WIFI连接上后没问题(能连接上,不判断能否上网,只验证密码是否正确)再保存。
- 更改的location连接问没问题后再保存
LVGL
设置图片动画
//Write animation: menu_img_wifimove in x direction
lv_anim_t menu_img_wifi_x;
lv_anim_init(&menu_img_wifi_x);
lv_anim_set_var(&menu_img_wifi_x, ui->menu_img_wifi);
lv_anim_set_time(&menu_img_wifi_x, 1000);
lv_anim_set_exec_cb(&menu_img_wifi_x, (lv_anim_exec_xcb_t)lv_obj_set_x);
lv_anim_set_values(&menu_img_wifi_x, lv_obj_get_x(ui->menu_img_wifi), 10);
_lv_memcpy_small(&menu_img_wifi_x.path, &lv_anim_path_menu_img_wifi, sizeof(lv_anim_path_cb_t));
lv_anim_start(&menu_img_wifi_x);
主菜单逻辑
- 开机显示开机画面(7张图片每张1s),并等待扫描和连接WIFI。
- 扫描WIFI并等待加入,70次错误后进入主页面
- WiFi错误界面:(账号、密码、地点错误)重试:重启 、 取消:进入主界面。
- 初始化正常,进入主页面,显示天气等数据。
- 在主页持续30秒将进入睡眠模式。
需要做的事情
- 进入主页需要验证是否联网,如果没有网络连接将刷新WIFI和天气图片
- 使用内部RTC进行时间刷新
- 写一个手动更新时间日期的函数和界面(联网自动更新)
- 电量显示
- 睡眠与睡眠唤醒
doing
修改默认启动界面
setup_scr_light_config(ui);
lv_scr_load(ui->image_config);
城市名字打错之后会进入无限循环。
解决:先初始化 mpu,如果开机向下,就重置城市信息。