透明小时钟开发流程与总结

功能实现:

时间更新

  • 时间功能需要在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);

主菜单逻辑

  1. 开机显示开机画面(7张图片每张1s),并等待扫描和连接WIFI。
  2. 扫描WIFI并等待加入,70次错误后进入主页面
  3. WiFi错误界面:(账号、密码、地点错误)重试:重启 、 取消:进入主界面。
  4. 初始化正常,进入主页面,显示天气等数据。
  5. 在主页持续30秒将进入睡眠模式。

需要做的事情

  1. 进入主页需要验证是否联网,如果没有网络连接将刷新WIFI和天气图片
  2. 使用内部RTC进行时间刷新
  3. 写一个手动更新时间日期的函数和界面(联网自动更新)
  4. 电量显示
  5. 睡眠与睡眠唤醒

doing

修改默认启动界面

	setup_scr_light_config(ui);
	lv_scr_load(ui->image_config);

城市名字打错之后会进入无限循环。

解决:先初始化 mpu,如果开机向下,就重置城市信息。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值