如何用ESP32+LVGL实现给路由器加装屏幕

1 篇文章 0 订阅
1 篇文章 0 订阅

买了个AX3000,发现面板空荡荡的,想起之前那个跑路讯K30路由器有个小led觉得很实用,就想着能不能把新路由也改装下。
在这里插入图片描述
先放个改装后的图片
在这里插入图片描述

一、方案设计

先把整体的改装思路用流程图简单画一下,确定可行性再逐个逐个问题解决:

选择主控芯片
连接WIFI读取设备信息
定时把总体速度和前6个上网最快的显示
组装
选择屏幕
确定尺寸开孔

读取路由器信息这个上网找了一些资料,发现小米路由器有相关的API可以取得当前所有用户的网速信息,这就好办了。

api路径:
http//:192.168.31.1/cgi-bin/luci/;stok=ea690e3c8bd3285d80cfb19fdddddd/api

接口信息
接口信息(图)

不过url里有一个stok的随机码,这个码是怎么获取的呢?本着自己做信息安全行业的从业者,这点认证协议可难不倒我。于是对路由器登陆进行抓包最后在分析:
在这里插入图片描述
从抓包结果来看应该就是路由器带上加密后的密码然后在加上nonce做时间验证交给路由器,路由器认证过后就返回token,后面就通过token去操纵路由器了。既然password加密是加密的,肯定是在本地进行的,查看js代码就能看到加密的方式了,下面是加密脚本,可以在路由器的web页面查看源代码找到:

    var Encrypt = {
        key: 'a2ffa5c9be07488bbb04a3a47d3c5fxx',
        iv: '64175472480004614961023454661220',
        nonce: null,
        init: function () {
            var nonce = this.nonceCreat();
            this.nonce = nonce;
            return this.nonce;
        },
        nonceCreat: function () {
            var type = 0;
            var deviceId = '';
            var time = Math.floor(new Date().getTime() / 1000);
            var random = Math.floor(Math.random() * 10000);
            return [type, deviceId, time, random].join('_');
        },
        oldPwd: function (pwd) {
            return CryptoJS.SHA1(this.nonce + CryptoJS.SHA1(pwd + this.key).toString()).toString();
        },
        newPwd: function (pwd, newpwd) {
            var key = CryptoJS.SHA1(pwd + this.key).toString();
            key = CryptoJS.enc.Hex.parse(key).toString();
            key = key.substr(0, 32);
            key = CryptoJS.enc.Hex.parse(key);
            var password = CryptoJS.SHA1(newpwd + this.key).toString();
            var iv = CryptoJS.enc.Hex.parse(this.iv);
            var aes = CryptoJS.AES.encrypt(
                password,
                key,
                {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7}
            ).toString();
            return aes;
        }
    };

代码的意思就是通过获取系统时间戳,再拼接一个五位数的随机字符串行程了nonce,再把密码SHA运算一次之后再链接上nonce进行一次SHA得到加密后的密码。好了现在数据的取得方式原理已经清晰了,后面就是接口返回数据解析的问题。通过观察返回数据是规范的json,所以也没难度,基本就是说软件层面上的问题是可行的。
再研究硬件层面,硬件层面主要是单片机芯片的选取,屏幕的选取还有打孔装上去的可行性。因为硬件的改动不像代码,可以一次次的调,所以得花费点心思。
首先芯片选型,现在最成熟的物联网单片机非ESP32无疑了,价格低性能高,而且现在支持python了(不过后来还是太天真,以为python能搞定。最后还是用回了C)。然后选择屏幕,因为第一次做物联网开发,想着简单点能看就行,就选了个小屏幕。如图:
在这里插入图片描述
但是越折腾欲望越大,最后选了2.8英寸的SPI屏幕,整个UI也重新设计。
最后是打孔,拆开路由器发现里面还有很大空间,塞下一块屏幕问题不大。如图:
在这里插入图片描述

二、代码编写

前面说到自己一开始计划用python编写的,可是后来发现单片机的mpython系统的技术支持文档太少了,特别是显示屏的驱动,基本都是只有arduino的。可是我已经好久没有写过C++了,这是整个代码编写最痛苦的一部分。单纯的类型转换已经把我搞傻了,而且arduino每次改代码就要重新编译上传一次,所以整个代码编写用了我接近一个月的时间。不过arduino有个好处就是类库全,真的太全了。很多你想到的都有,下面进入正题,我将挑取一些代码块进行思路说明,整体代码在后面链接下载。

2.1功能性代码块

连接wifi代码:

void conet_wifi(){
  WiFiClient client;
  Serial.print("Connecting to WiFi");
  int i=0;
  WiFi.begin("你的第一个wifi名", "你的wifi密码");
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
    Serial.print(".");
    i++;
    if (i>50){
      i=0;
      WiFi.begin("备选wifi名", "备选wifi密码");
      Serial.print("wifi2");
      while (i<100 && WiFi.status() != WL_CONNECTED){
        delay(100);
        Serial.print(".");
        i++;
      }
    }
  }
  //WiFiClient client;
  //Serial.println(client.connect("192.168.31.1"));
  Serial.println(" Connected!");
}

token获取代码块:


NTPClient timeClient(ntpUDP, NTP_ADDRESS, NTP_OFFSET, NTP_INTERVAL);
//

String toSHA1(const unsigned char encrypt[])//SHA1加密
{
    mbedtls_sha1_context sha1_ctx;;

    //const unsigned char encrypt[] = "hello_worled1234";
    unsigned char decrypt[32];

    mbedtls_sha1_init(&sha1_ctx);
    mbedtls_sha1_starts(&sha1_ctx);
    mbedtls_sha1_update(&sha1_ctx, encrypt, strlen((char *)encrypt));
    mbedtls_sha1_finish(&sha1_ctx, decrypt);

    String a;
    String b;
    for(int i = 0; i < 20; i++)
    {
        b=decrypt[i];
        a = a+tohex(b.toInt());
    }

    printf("\r\n");
    mbedtls_sha1_free(&sha1_ctx);
    return a;
}


String tohex(int n)//10进制转16进制
 {
  if (n == 0) {
    return "00"; //n为0
  }
  String result = "";
  char _16[] = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
  };
  const int radix = 16;
  while (n) {
    int i = n % radix;          // 余数
    result = _16[i] + result;   // 将余数对应的十六进制数字加入结果
    n /= radix;                 // 除以16获得商,最为下一轮的被除数
  }
  if (result.length() < 2) {
    result = '0' + result; //不足两位补零
  }
  result.toLowerCase();
  return result;
}

String getToken(String host){
  String aimurl = "http://"+host + "/cgi-bin/luci/api/xqsystem/login";
  timeClient.begin();
  timeClient.update();
  timeClient.update();
  timeClient.update();
  timeClient.update();//更新系统时间
  const unsigned char pwd1[]="[你的路由器密码]a2ffa5c9be07488bbb04a3a47d3c5f6a";//密码加KWY,因为key是固定的
  String nonce =  "0__" +  (String)timeClient.getEpochTime() + "_" + (String)random(1000, 10000);
  String hexpwd1 = toSHA1(pwd1);
  String Spwd2 = nonce+hexpwd1;
  String hexpwd2 = toSHA1((const unsigned char *)Spwd2.c_str());
  String token;//运算密码
  Serial.println(nonce);
  Serial.println(hexpwd2);
  HTTPClient httpClient;
  httpClient.begin(aimurl); 
  httpClient.addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.0; rv:72.0)", false, true);
  httpClient.addHeader("Accept", "*/*", false, true);
  httpClient.addHeader("Accept-Encoding", "identity", false, true);
  //httpClient.addHeader("Content-Length:", "99", false, true);
  httpClient.addHeader("Accept-Language", "zh-CN", false, true);
  httpClient.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8", false, true);
  httpClient.addHeader("Origin", "http://192.168.31.1", false, true);
  httpClient.addHeader("Referer", "http://192.168.31.1/cgi-bin/luci/web", false, true);
  httpClient.addHeader("X-Requested-With", "XMLHttpRequest", false, true);
  String postData = "username=admin&password=" + hexpwd2 + "&logtype=2&nonce="+ nonce;
  Serial.println(aimurl);
  Serial.println(postData);
  if(httpClient.POST(postData)>0){//获取token
    token = httpClient.getString();
    StaticJsonDocument<256> docs;
    DeserializationError error = deserializeJson(docs, token);
    if (error) {
      Serial.print("deserializeJson() failed: ");
      Serial.println(error.c_str());
      return "err";
    } 
  token = (const char*)docs["token"];}
  Serial.println(token);
  return token;
}

因为ESP32不支持保存时间,但路由器需要一个和客户端相差不多的时间戳才认可对方是合法设备,所以要用timeClient这个类库同步网上的NTP时间,才能得到nonce。然后就是sha运算,这里用了ESP官方提供的sha运算方法,但是这个方法转换出来的是十进制的哈希值,但是设备要16进制的哈希值,所以我写了个tohex的函数进行转换。最后得出了nonce和加密后的passwd,带上自己设计好的http包头发给路由器。这个非常重要,因为我测试了很多次,http包头有些字段如果没带上的话,路由器也会反不回正确的token。

解析返回数据:


String getRoute_info(String host,String token){//获取路由器接所有信息
  String url = "http://"+host + "/cgi-bin/luci/;stok="+token+"/api";
  String info = "";
  HTTPClient httpClient;
  httpClient.begin(url); 
  httpClient.setTimeout(5000);
  if(httpClient.GET()>0){
    info = httpClient.getString();
  }else {info = "err";}//改名
  info.replace("C0:E7:BF:B5:09:5", "MiTV");
  info.replace("XiaoAiToXueX08A", "XiAi");
  info.replace("zhanndeM", "n_Pc");
  info.replace("DESKTOP-NG0I0F0", "hang_pc");
  info.replace("LAPTOP-5TQEH7OR", "hang_book");
  info.replace("2109119BC","Lian");
  info.replace("2A:21:02:ED:28", "Dby");
  info.replace("4A:D2:D3:5:E8:2F", "Hang");
  info.replace("HNR550F50G", "SHARPTV");
  info.replace("android-812aaa9f9858c", "Do");
  info.replace("HUAWEI_nova_3i-7029b802", "dan");
  Serial.println("getRouteinfo finish");
  
  return info;
  
  }

void flashData(String Apidata){///序列化网速数据
      if(Apidata == "err")return;
      DynamicJsonDocument doc(5000);
      DeserializationError error = deserializeJson(doc, Apidata);
      if (error) {
        Serial.print("deserializeJson() failed: ");
        Serial.println(error.c_str());
        return;
      } 
      JsonArray dev = doc["dev"];
      //JsonObject wan = doc["wan"];
      //wan_dspeed_souce = (const char*)wan["downspeed"]; 
      //wan_uspeed_souce = (const char*)wan["upspeed"];
      int temp = dev.size();
       wan_dspeed = "0";清空总速度,后面累加
       wan_uspeed = "0";
      for(int i=0;i<temp;i++){
        dev_lis[i][0]=(const char*)dev[i]["devname"];
        dev_lis[i][1]=(const char*)dev[i]["upspeed"];
        dev_lis[i][2]=(const char*)dev[i]["downspeed"];
        dev_lis[i][3]=(const char*)dev[i]["upload"];
        dev_lis[i][4]=(const char*)dev[i]["download"];
        wan_uspeed = dev_lis[i][1].toInt()+wan_uspeed.toInt();
        wan_dspeed = dev_lis[i][2].toInt()+wan_dspeed.toInt();
        }
     wan_dspeed_souce = wan_dspeed; //取得当前总速度
     wan_uspeed_souce = wan_uspeed;
     wan_dspeed = ChangeData(wan_dspeed);
     wan_uspeed = ChangeData(wan_uspeed);
     String s[5];///开始排序
     for(int i=0; i<temp-1; i++){
      for(int j=0; j<(temp-1)-i; j++){
       
      if((dev_lis[j+1][2]).toInt() > (dev_lis[j][2]).toInt()){
       for(int m=0; m<5; m++){
        s[m] = dev_lis[j][m];
       }
       
       for(int n=0; n<5; n++){
        dev_lis[j][n] = dev_lis[j+1][n];
       }
       for(int p=0; p<5; p++){
        dev_lis[j+1][p] = s[p]; 
       }
      }else{
       continue; 
      } 
         }
     }

      for(int i=0;i<temp;i++){///转换网速
        dev_lis[i][1]=ChangeData(dev_lis[i][1]);
        dev_lis[i][2]=ChangeData(dev_lis[i][2]);
        dev_lis[i][3]=ChangeData(dev_lis[i][3]);
        dev_lis[i][4]=ChangeData(dev_lis[i][4]);
        Serial.println( dev_lis[i][0] );
        
      }

      doc.clear();
      dev.clear();
     Serial.println( "flashData finish" );
  }

String ChangeData(String data){
  
  String speed="";
  if ((data.toFloat()/1024)<1024){speed = (String(data.toFloat()/1024))+" KB";}
  else if((data.toFloat()/1048576)<1024){speed = String((data.toFloat()/1048576))+" MB";}
  else if((data.toFloat()/1073741824)<1024){speed = String((data.toFloat()/1073741824))+" GB";}
  return speed;
  }

返回来的是json,一开始我用了cjson这个库,坑是真的大。没做好内存回收老是内存溢出,后来用回了arduinoJson这个库就好多了。json解析出来后用相应的数组进行赋值,因为我设计的是只显示前六位用户所以定义了6个数组。最后因为路由器返回的网速是比特为单位的,很大不直观。所以我写了个chngedata的函数去转换并返回网速。

2.2UI代码块

UI这块做起来也是很头痛的部分,在网上找显示图案的源代码大多数都是底层的,变换数字都要重新刷背景。这些对于我这种非专业的开发来说,难度实在是太高了。通过不断的深入查找资料,lvgl进入了我的视线。先看一下介绍:

LVGL的作者是来自匈牙利的Gabor Kiss-Vamosikisvegabor,LVGL用C语言编写,以实现最大的兼容性(与C ++兼容),模拟器可在没有嵌入式硬件的PC上启动嵌入式GUI设计,同时LVGL作为一个图形库,它自带着接近三十多种小工具可以供开发者使用。这些强大的构建块按钮搭配上带有非常丝滑的动画以及可以做到平滑滚动的高级图形,同时兼具着不高的配置要求以及开源属性,显著的优势使得LVGL蔚然成风,成为广大开发者在选择GUI时的第一选择。

看,这不就是我苦苦找寻的结果么。这里单独感谢一下百问网的老师,把lvgl带入国内嵌入式开发圈出了很大贡献。单纯把sdk文档翻译成中文免费给大家用这一块就已经很伟大了,更不用不说在哔哩哔哩上免费的录制使用视频了。
lvgl配合官方号提供的SquareLine Studio图形化界面设计软件,直接就可以让我们可以像windows的VC那样通过图形化的拖动进行界面设计。让我可以把更加多的精力放到算法代码上。首先是界面的设计,由于写文章的时候软件已经过期打不开了,借用调试时候的界面说一下
在这里插入图片描述
一开始是想把网速前6的用户显示出来的,不过发现屏幕还是太小。我的初衷是平时扫路过路由器扫一眼大概了解下有谁在上网,总体网速怎么样就行了。于是就再重新设计了界面,用两个大仪表盘显示上行下行的速度,再显示前三用户网速就可以了。下面是代码块:


//*LVGL相关定义*//
static const uint16_t screenWidth  = 240;
static const uint16_t screenHeight = 320;
static lv_disp_draw_buf_t draw_buf;
//static lv_color_t buf_1[screenWidth * 10];
static lv_anim_t a;//动画
static lv_color_t *disp_draw_buf_1;
static lv_color_t *disp_draw_buf_2;
static lv_disp_drv_t disp_drv;
static bool lock = false;
LV_IMG_DECLARE(cherry_img);///定义图片
LV_IMG_DECLARE(xiaoai_img);
LV_IMG_DECLARE(dao_img);
LV_IMG_DECLARE(dboy_img);
LV_IMG_DECLARE(hang_img);
LV_IMG_DECLARE(lian_img);
LV_IMG_DECLARE(router_img);
LV_IMG_DECLARE(tv_img);
LV_IMG_DECLARE(sudan_img);
LV_IMG_DECLARE(unknow_img);
LV_IMG_DECLARE(upload_img);
LV_IMG_DECLARE(download_img);
/ VARIABLES 
lv_obj_t * ui_Screen2;
lv_obj_t * ui_Arc_Group;
lv_obj_t * ui_Arc1;
lv_obj_t * ui_Temp_Bg;
lv_obj_t * ui_Temp_Num_Bg;
lv_obj_t * ui_Label1;
lv_obj_t * ui_Arc_Group1;
lv_obj_t * ui_Arc2;
lv_obj_t * ui_Temp_Bg1;
lv_obj_t * ui_Temp_Num_Bg1;
lv_obj_t * ui_Label2;
lv_obj_t * ui_list;
lv_obj_t * ui_Image1;
lv_obj_t * ui_name;
lv_obj_t * ui_total;
lv_obj_t * ui_speed;
lv_obj_t * ui_list1;
lv_obj_t * ui_Image2;
lv_obj_t * ui_name1;
lv_obj_t * ui_total1;
lv_obj_t * ui_speed1;
lv_obj_t * ui_list2;
lv_obj_t * ui_Image3;
lv_obj_t * ui_name2;
lv_obj_t * ui_total2;
lv_obj_t * ui_speed2;
lv_obj_t * ui_Label3;
lv_obj_t * ui_Label4;
lv_obj_t * ui_Image4;//上传图标
lv_obj_t * ui_Image5;//下载图标
/ TEST LVGL SETTINGS 
//#if LV_COLOR_DEPTH != 16
//    #error "LV_COLOR_DEPTH should be 16bit to match SquareLine Studio's settings"
//#endif
//#if LV_COLOR_16_SWAP !=0
//    #error "#error LV_COLOR_16_SWAP should be 0 to match SquareLine Studio's settings"
//#endif

/ ANIMATIONS 

/ FUNCTIONS 

/ SCREENS 
void ui_Screen2_screen_init(void)
{

    // ui_Screen2

    ui_Screen2 = lv_obj_create(NULL);

    lv_obj_clear_flag(ui_Screen2, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_bg_color(ui_Screen2, lv_color_hex(0x4B4B55), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Screen2, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_color(ui_Screen2, lv_color_hex(0x2D323C), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_color(ui_Screen2, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_Screen2, 255, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Arc_Group

    ui_Arc_Group = lv_obj_create(ui_Screen2);

    lv_obj_set_width(ui_Arc_Group, 110);
    lv_obj_set_height(ui_Arc_Group, 110);

    lv_obj_set_x(ui_Arc_Group, -60);
    lv_obj_set_y(ui_Arc_Group, -100);

    lv_obj_set_align(ui_Arc_Group, LV_ALIGN_CENTER);

    lv_obj_clear_flag(ui_Arc_Group, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_bg_color(ui_Arc_Group, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Arc_Group, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_width(ui_Arc_Group, 0, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Arc1

    ui_Arc1 = lv_arc_create(ui_Arc_Group);

    lv_obj_set_width(ui_Arc1, 110);
    lv_obj_set_height(ui_Arc1, 110);

    lv_obj_set_x(ui_Arc1, 0);
    lv_obj_set_y(ui_Arc1, 0);

    lv_obj_set_align(ui_Arc1, LV_ALIGN_CENTER);

    lv_arc_set_range(ui_Arc1, 0, 100);
    lv_arc_set_value(ui_Arc1, 50);
    lv_arc_set_bg_angles(ui_Arc1, 120, 60);

    lv_obj_set_style_radius(ui_Arc1, 350, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_Arc1, lv_color_hex(0x1E232D), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Arc1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_pad_left(ui_Arc1, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_pad_right(ui_Arc1, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_pad_top(ui_Arc1, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_pad_bottom(ui_Arc1, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_color(ui_Arc1, lv_color_hex(0x0F1215), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_opa(ui_Arc1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_width(ui_Arc1, 7, LV_PART_MAIN | LV_STATE_DEFAULT);

    lv_obj_set_style_arc_color(ui_Arc1, lv_color_hex(0x1BE276), LV_PART_INDICATOR | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_opa(ui_Arc1, 255, LV_PART_INDICATOR | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_width(ui_Arc1, 7, LV_PART_INDICATOR | LV_STATE_DEFAULT);

    lv_obj_set_style_bg_color(ui_Arc1, lv_color_hex(0xFFFFFF), LV_PART_KNOB | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Arc1, 0, LV_PART_KNOB | LV_STATE_DEFAULT);

    // ui_Temp_Bg

    ui_Temp_Bg = lv_obj_create(ui_Arc_Group);

    lv_obj_set_width(ui_Temp_Bg, 60);
    lv_obj_set_height(ui_Temp_Bg, 60);

    lv_obj_set_x(ui_Temp_Bg, 0);
    lv_obj_set_y(ui_Temp_Bg, 0);

    lv_obj_set_align(ui_Temp_Bg, LV_ALIGN_CENTER);

    lv_obj_clear_flag(ui_Temp_Bg, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_radius(ui_Temp_Bg, 280, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_Temp_Bg, lv_color_hex(0x646464), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Temp_Bg, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_color(ui_Temp_Bg, lv_color_hex(0x3C414B), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_dir(ui_Temp_Bg, LV_GRAD_DIR_VER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_color(ui_Temp_Bg, lv_color_hex(0x2D323C), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_opa(ui_Temp_Bg, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_width(ui_Temp_Bg, 2, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_color(ui_Temp_Bg, lv_color_hex(0x050A0F), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_opa(ui_Temp_Bg, 255, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Temp_Num_Bg

    ui_Temp_Num_Bg = lv_obj_create(ui_Temp_Bg);

    lv_obj_set_width(ui_Temp_Num_Bg, 50);
    lv_obj_set_height(ui_Temp_Num_Bg, 50);

    lv_obj_set_x(ui_Temp_Num_Bg, 0);
    lv_obj_set_y(ui_Temp_Num_Bg, 0);

    lv_obj_set_align(ui_Temp_Num_Bg, LV_ALIGN_CENTER);

    lv_obj_clear_flag(ui_Temp_Num_Bg, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_radius(ui_Temp_Num_Bg, 200, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_Temp_Num_Bg, lv_color_hex(0x0C191E), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Temp_Num_Bg, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_color(ui_Temp_Num_Bg, lv_color_hex(0x191C26), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_dir(ui_Temp_Num_Bg, LV_GRAD_DIR_VER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_color(ui_Temp_Num_Bg, lv_color_hex(0x5A646E), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_opa(ui_Temp_Num_Bg, 255, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Label1

    ui_Label1 = lv_label_create(ui_Temp_Num_Bg);

    lv_obj_set_width(ui_Label1, 43);
    lv_obj_set_height(ui_Label1, 21);

    lv_obj_set_x(ui_Label1, 0);
    lv_obj_set_y(ui_Label1, 0);

    lv_obj_set_align(ui_Label1, LV_ALIGN_CENTER);

    lv_label_set_text(ui_Label1, "999.0 KB/s");

    lv_obj_set_style_text_color(ui_Label1, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_Label1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_align(ui_Label1, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_font(ui_Label1, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Arc_Group1

    ui_Arc_Group1 = lv_obj_create(ui_Screen2);

    lv_obj_set_width(ui_Arc_Group1, 110);
    lv_obj_set_height(ui_Arc_Group1, 110);

    lv_obj_set_x(ui_Arc_Group1, 60);
    lv_obj_set_y(ui_Arc_Group1, -100);

    lv_obj_set_align(ui_Arc_Group1, LV_ALIGN_CENTER);

    lv_obj_clear_flag(ui_Arc_Group1, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_bg_color(ui_Arc_Group1, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Arc_Group1, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_width(ui_Arc_Group1, 0, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Arc2

    ui_Arc2 = lv_arc_create(ui_Arc_Group1);

    lv_obj_set_width(ui_Arc2, 110);
    lv_obj_set_height(ui_Arc2, 110);

    lv_obj_set_x(ui_Arc2, 0);
    lv_obj_set_y(ui_Arc2, 0);

    lv_obj_set_align(ui_Arc2, LV_ALIGN_CENTER);

    lv_arc_set_range(ui_Arc2, 0, 100);
    lv_arc_set_value(ui_Arc2, 23);
    lv_arc_set_bg_angles(ui_Arc2, 120, 60);

    lv_obj_set_style_radius(ui_Arc2, 350, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_Arc2, lv_color_hex(0x1E232D), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Arc2, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_pad_left(ui_Arc2, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_pad_right(ui_Arc2, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_pad_top(ui_Arc2, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_pad_bottom(ui_Arc2, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_color(ui_Arc2, lv_color_hex(0x0F1215), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_opa(ui_Arc2, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_width(ui_Arc2, 7, LV_PART_MAIN | LV_STATE_DEFAULT);

    lv_obj_set_style_arc_color(ui_Arc2, lv_color_hex(0xCB3636), LV_PART_INDICATOR | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_opa(ui_Arc2, 255, LV_PART_INDICATOR | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_width(ui_Arc2, 7, LV_PART_INDICATOR | LV_STATE_DEFAULT);

    lv_obj_set_style_bg_color(ui_Arc2, lv_color_hex(0xFFFFFF), LV_PART_KNOB | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Arc2, 0, LV_PART_KNOB | LV_STATE_DEFAULT);

    // ui_Temp_Bg1

    ui_Temp_Bg1 = lv_obj_create(ui_Arc_Group1);

    lv_obj_set_width(ui_Temp_Bg1, 60);
    lv_obj_set_height(ui_Temp_Bg1, 60);

    lv_obj_set_x(ui_Temp_Bg1, 0);
    lv_obj_set_y(ui_Temp_Bg1, 0);

    lv_obj_set_align(ui_Temp_Bg1, LV_ALIGN_CENTER);

    lv_obj_clear_flag(ui_Temp_Bg1, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_radius(ui_Temp_Bg1, 280, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_Temp_Bg1, lv_color_hex(0x646464), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Temp_Bg1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_color(ui_Temp_Bg1, lv_color_hex(0x3C414B), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_dir(ui_Temp_Bg1, LV_GRAD_DIR_VER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_color(ui_Temp_Bg1, lv_color_hex(0x2D323C), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_opa(ui_Temp_Bg1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_width(ui_Temp_Bg1, 2, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_color(ui_Temp_Bg1, lv_color_hex(0x050A0F), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_opa(ui_Temp_Bg1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Temp_Num_Bg1

    ui_Temp_Num_Bg1 = lv_obj_create(ui_Temp_Bg1);

    lv_obj_set_width(ui_Temp_Num_Bg1, 50);
    lv_obj_set_height(ui_Temp_Num_Bg1, 50);

    lv_obj_set_x(ui_Temp_Num_Bg1, 0);
    lv_obj_set_y(ui_Temp_Num_Bg1, 0);

    lv_obj_set_align(ui_Temp_Num_Bg1, LV_ALIGN_CENTER);

    lv_obj_clear_flag(ui_Temp_Num_Bg1, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_radius(ui_Temp_Num_Bg1, 200, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_Temp_Num_Bg1, lv_color_hex(0x0C191E), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Temp_Num_Bg1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_color(ui_Temp_Num_Bg1, lv_color_hex(0x191C26), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_dir(ui_Temp_Num_Bg1, LV_GRAD_DIR_VER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_color(ui_Temp_Num_Bg1, lv_color_hex(0x5A646E), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_opa(ui_Temp_Num_Bg1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Label2

    ui_Label2 = lv_label_create(ui_Temp_Num_Bg1);

    lv_obj_set_width(ui_Label2, 40);
    lv_obj_set_height(ui_Label2, 21);

    lv_obj_set_x(ui_Label2, 0);
    lv_obj_set_y(ui_Label2, 0);

    lv_obj_set_align(ui_Label2, LV_ALIGN_CENTER);

    lv_label_set_text(ui_Label2, "888 KB/s");

    lv_obj_set_style_text_color(ui_Label2, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_Label2, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_align(ui_Label2, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_font(ui_Label2, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_list

    ui_list = lv_obj_create(ui_Screen2);

    lv_obj_set_width(ui_list, 220);
    lv_obj_set_height(ui_list, 50);

    lv_obj_set_x(ui_list, 10);
    lv_obj_set_y(ui_list, 0);

    lv_obj_set_align(ui_list, LV_ALIGN_LEFT_MID);

    lv_obj_clear_flag(ui_list, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_radius(ui_list, 11, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_list, lv_color_hex(0x5A656A), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_list, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_color(ui_list, lv_color_hex(0x1D323C), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_dir(ui_list, LV_GRAD_DIR_VER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_color(ui_list, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_list, 255, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Image1
    LV_IMG_DECLARE(Cherry);
    ui_Image1 = lv_img_create(ui_list);

    lv_obj_set_width(ui_Image1, 25);
    lv_obj_set_height(ui_Image1, 25);

    lv_obj_set_x(ui_Image1, -1);
    lv_obj_set_y(ui_Image1, 0);

    lv_obj_set_align(ui_Image1, LV_ALIGN_LEFT_MID);

    lv_obj_add_flag(ui_Image1, LV_OBJ_FLAG_ADV_HITTEST);
    lv_obj_clear_flag(ui_Image1, LV_OBJ_FLAG_SCROLLABLE);
    lv_img_set_src(ui_Image1, getIMG(String("unknow")));
    
    // ui_name

    ui_name = lv_label_create(ui_list);

    lv_obj_set_width(ui_name, 55);
    lv_obj_set_height(ui_name, 10);
    lv_label_set_long_mode(ui_name, LV_LABEL_LONG_DOT);
    lv_obj_set_x(ui_name, 35);
    lv_obj_set_y(ui_name, -6);

    lv_label_set_text(ui_name, "Xiaoaitongxue");

    lv_obj_set_style_text_font(ui_name, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_total

    ui_total = lv_label_create(ui_list);

    lv_obj_set_width(ui_total, 50);
    lv_obj_set_height(ui_total, 12);

    lv_obj_set_x(ui_total, 35);
    lv_obj_set_y(ui_total, 11);

    lv_label_set_long_mode(ui_total, LV_LABEL_LONG_SCROLL_CIRCULAR);
    lv_label_set_text(ui_total, "111111111111111111");

    lv_obj_set_style_text_color(ui_total, lv_color_hex(0x898888), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_total, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_font(ui_total, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_speed

    ui_speed = lv_label_create(ui_list);

    lv_obj_set_width(ui_speed, LV_SIZE_CONTENT);
    lv_obj_set_height(ui_speed, LV_SIZE_CONTENT);

    lv_obj_set_x(ui_speed, 111);
    lv_obj_set_y(ui_speed, 0);

    lv_obj_set_align(ui_speed, LV_ALIGN_LEFT_MID);

    lv_label_set_long_mode(ui_speed, LV_LABEL_LONG_SCROLL);
    lv_label_set_text(ui_speed, "222222222");

    lv_obj_set_style_text_font(ui_speed, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_list1

    ui_list1 = lv_obj_create(ui_Screen2);

    lv_obj_set_width(ui_list1, 220);
    lv_obj_set_height(ui_list1, 50);

    lv_obj_set_x(ui_list1, 10);
    lv_obj_set_y(ui_list1, 62);

    lv_obj_set_align(ui_list1, LV_ALIGN_LEFT_MID);

    lv_obj_clear_flag(ui_list1, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_radius(ui_list1, 11, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_list1, lv_color_hex(0x5A656A), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_list1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_color(ui_list1, lv_color_hex(0x1D323C), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_dir(ui_list1, LV_GRAD_DIR_VER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_color(ui_list1, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_list1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Image2

    ui_Image2 = lv_img_create(ui_list1);

    lv_obj_set_width(ui_Image2, 25);
    lv_obj_set_height(ui_Image2, 25);

    lv_obj_set_x(ui_Image2, -1);
    lv_obj_set_y(ui_Image2, 0);

    lv_obj_set_align(ui_Image2, LV_ALIGN_LEFT_MID);

    lv_obj_add_flag(ui_Image2, LV_OBJ_FLAG_ADV_HITTEST);
    lv_obj_clear_flag(ui_Image2, LV_OBJ_FLAG_SCROLLABLE);
    lv_img_set_src(ui_Image2, getIMG("unknow"));
    // ui_name1

    ui_name1 = lv_label_create(ui_list1);

    lv_obj_set_width(ui_name1, 55);
    lv_obj_set_height(ui_name1, 10);

    lv_obj_set_x(ui_name1, 35);
    lv_obj_set_y(ui_name1, -6);

    lv_label_set_long_mode(ui_name1, LV_LABEL_LONG_CLIP);
    lv_label_set_text(ui_name1, "Xiaoaitongxue");

    lv_obj_set_style_text_font(ui_name1, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_total1

    ui_total1 = lv_label_create(ui_list1);

    lv_obj_set_width(ui_total1, 50);
    lv_obj_set_height(ui_total1, 12);

    lv_obj_set_x(ui_total1, 35);
    lv_obj_set_y(ui_total1, 11);

    lv_label_set_long_mode(ui_total1, LV_LABEL_LONG_SCROLL_CIRCULAR);
    lv_label_set_text(ui_total1, "Totol");

    lv_obj_set_style_text_color(ui_total1, lv_color_hex(0x898888), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_total1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_font(ui_total1, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_speed1

    ui_speed1 = lv_label_create(ui_list1);

    lv_obj_set_width(ui_speed1, LV_SIZE_CONTENT);
    lv_obj_set_height(ui_speed1, LV_SIZE_CONTENT);

    lv_obj_set_x(ui_speed1, 111);
    lv_obj_set_y(ui_speed1, 0);

    lv_obj_set_align(ui_speed1, LV_ALIGN_LEFT_MID);

    lv_label_set_long_mode(ui_speed1, LV_LABEL_LONG_SCROLL);
    lv_label_set_text(ui_speed1, "222222222");

    lv_obj_set_style_text_font(ui_speed1, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_list2

    ui_list2 = lv_obj_create(ui_Screen2);

    lv_obj_set_width(ui_list2, 220);
    lv_obj_set_height(ui_list2, 50);

    lv_obj_set_x(ui_list2, 10);
    lv_obj_set_y(ui_list2, 125);

    lv_obj_set_align(ui_list2, LV_ALIGN_LEFT_MID);

    lv_obj_clear_flag(ui_list2, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_radius(ui_list2, 11, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_list2, lv_color_hex(0x5A656A), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_list2, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_color(ui_list2, lv_color_hex(0x1D323C), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_grad_dir(ui_list2, LV_GRAD_DIR_VER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_color(ui_list2, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_list2, 255, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Image3

    ui_Image3 = lv_img_create(ui_list2);

    lv_obj_set_width(ui_Image3, 25);
    lv_obj_set_height(ui_Image3, 25);

    lv_obj_set_x(ui_Image3, -1);
    lv_obj_set_y(ui_Image3, 0);

    lv_obj_set_align(ui_Image3, LV_ALIGN_LEFT_MID);

    lv_obj_add_flag(ui_Image3, LV_OBJ_FLAG_ADV_HITTEST);
    lv_obj_clear_flag(ui_Image3, LV_OBJ_FLAG_SCROLLABLE);
    lv_img_set_src(ui_Image3, getIMG("download_img"));
    // ui_name2

    ui_name2 = lv_label_create(ui_list2);

    lv_obj_set_width(ui_name2, 55);
    lv_obj_set_height(ui_name2, 10);

    lv_obj_set_x(ui_name2, 35);
    lv_obj_set_y(ui_name2, -6);

    lv_label_set_long_mode(ui_name2, LV_LABEL_LONG_CLIP);
    lv_label_set_text(ui_name2, "Xiaoaitongxue");

    lv_obj_set_style_text_font(ui_name2, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_total2

    ui_total2 = lv_label_create(ui_list2);

    lv_obj_set_width(ui_total2, 50);
    lv_obj_set_height(ui_total2, 12);

    lv_obj_set_x(ui_total2, 35);
    lv_obj_set_y(ui_total2, 11);

    lv_label_set_long_mode(ui_total2, LV_LABEL_LONG_SCROLL_CIRCULAR);
    lv_label_set_text(ui_total2, "Totol");

    lv_obj_set_style_text_color(ui_total2, lv_color_hex(0x898888), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_total2, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_font(ui_total2, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_speed2

    ui_speed2 = lv_label_create(ui_list2);

    lv_obj_set_width(ui_speed2, LV_SIZE_CONTENT);
    lv_obj_set_height(ui_speed2, LV_SIZE_CONTENT);

    lv_obj_set_x(ui_speed2, 111);
    lv_obj_set_y(ui_speed2, 0);

    lv_obj_set_align(ui_speed2, LV_ALIGN_LEFT_MID);

    lv_label_set_long_mode(ui_speed2, LV_LABEL_LONG_SCROLL);
    lv_label_set_text(ui_speed2, "222222222");

    lv_obj_set_style_text_font(ui_speed2, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT);
    ui_Image4 = lv_img_create(ui_Screen2);

    lv_obj_set_width(ui_Image4, 25);
    lv_obj_set_height(ui_Image4, 25);

    lv_obj_set_x(ui_Image4, 60);
    lv_obj_set_y(ui_Image4, -57);

    lv_obj_set_align(ui_Image4, LV_ALIGN_CENTER);

    lv_obj_add_flag(ui_Image4, LV_OBJ_FLAG_ADV_HITTEST);
    lv_obj_clear_flag(ui_Image4, LV_OBJ_FLAG_SCROLLABLE);
    lv_img_set_src(ui_Image4, &upload_img);
   // ui_Image5

    ui_Image5 = lv_img_create(ui_Screen2);

    lv_obj_set_width(ui_Image5, 25);
    lv_obj_set_height(ui_Image5, 25);

    lv_obj_set_x(ui_Image5, -60);
    lv_obj_set_y(ui_Image5, -57);

    lv_obj_set_align(ui_Image5, LV_ALIGN_CENTER);

    lv_obj_add_flag(ui_Image5, LV_OBJ_FLAG_ADV_HITTEST);
    lv_obj_clear_flag(ui_Image5, LV_OBJ_FLAG_SCROLLABLE);
    lv_img_set_src(ui_Image5, &download_img);

}
void ui_init(void)
{
    lv_disp_t * dispp = lv_disp_get_default();
    lv_theme_t * theme = lv_theme_default_init(dispp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),false, LV_FONT_DEFAULT);
    lv_disp_set_theme(dispp, theme);
    ui_Screen2_screen_init();
    lv_disp_load_scr(ui_Screen2);
}

lvgl虽然是面向对象思路进行编写的,但是arduino其实还是面向过程多一点,所以前面要给一堆元素进行定,而不是像类库那样进行封装。定义好元素后就是用ui_Screen2_screen_init函数对对象进行设置长宽高,具体就不展开说了,慢慢读代码。这些代码其实都是用SquareLine Studio生成出来的,我摘取了其中核心部分自己再改动来用的。
画面设计好后就要把动画做起来,图片可能是静态的看不到,其实我上面两个仪表盘是会动的,lvgl很这么强大的ui工具,肯定提供了动画函数。


static void set_angle(void * obj, int32_t v)
{
    lv_arc_set_value((lv_obj_t *)obj, v);
}
void lv_example_meter_2(int start_u,int end_u,int start_d,int end_d)///绘制动画
{
    lv_anim_set_var(&a, ui_Arc2);
    lv_anim_set_time(&a, 1000);
    lv_anim_set_values(&a, start_u, end_u);
    lv_anim_start(&a);
    lv_anim_set_var(&a, ui_Arc1);
    lv_anim_set_exec_cb(&a, set_angle);
    lv_anim_set_values(&a, start_d, end_d); 
    lv_anim_start(&a);
}

void printTXT(lv_obj_t * obj_u,lv_obj_t * obj_d,char a[],char b[]){
    lv_label_set_text(obj_u,a);
    lv_label_set_text(obj_d,b);
 
}

void flashScreenData(){
    lv_label_set_text(ui_Label2,(char*)(String(wan_uspeed)+"/s").c_str());
    lv_label_set_text(ui_Label1,(char*)(String(wan_dspeed)+"/s").c_str());
    lv_img_set_src(ui_Image1, getIMG(dev_lis[0][0]));
    lv_label_set_text(ui_name,(char*)dev_lis[0][0].c_str());
    lv_label_set_text(ui_speed,(char*)String(LV_SYMBOL_UP":"+dev_lis[0][1]+"/s\n"LV_SYMBOL_DOWN":"+dev_lis[0][2]+"/s").c_str());
    lv_label_set_text(ui_total,(char*)String("all_down:"+dev_lis[0][4]+"  all_up:"+dev_lis[0][3]).c_str());
    lv_img_set_src(ui_Image2, getIMG(dev_lis[1][0]));
    lv_label_set_text(ui_name1,(char*)dev_lis[1][0].c_str());
    lv_label_set_text(ui_speed1,(char*)String(LV_SYMBOL_UP":"+dev_lis[1][1]+"/s\n"LV_SYMBOL_DOWN":"+dev_lis[1][2]+"/s").c_str());
    lv_label_set_text(ui_total1,(char*)String("all_down:"+dev_lis[1][4]+"  all_up:"+dev_lis[1][3]).c_str());
    lv_img_set_src(ui_Image3, getIMG(dev_lis[2][0]));
    lv_label_set_text(ui_name2,(char*)dev_lis[2][0].c_str());
    lv_label_set_text(ui_speed2,(char*)String(LV_SYMBOL_UP":"+dev_lis[2][1]+"/s\n"LV_SYMBOL_DOWN":"+dev_lis[2][2]+"/s").c_str());
    lv_label_set_text(ui_total2,(char*)String("all_down:"+dev_lis[2][4]+"  all_up:"+dev_lis[2][3]).c_str());
 
}

2.3驱动代码

前面网速信息获取了,UI界面设计好了,怎样把他们结合起来让他跑起来呢?lvgl有个比较麻烦的地方就是要实时和屏幕进行同步,所有要把他放在main函数里,不过我们程序也要实时的的跟路由器获取网速数据,如果都串在一个函数里的话,那获取网速的时候就会形成阻塞,屏幕的显示会卡主。所以后来我引入了多线程解决了问题,不过esp的多线程有个缺点,没有锁线程的函数,所以自己在代码里模拟了”锁“这个动作,比较粗暴。导致后面数据显示的时候有残影。
屏幕驱动代码块:

/* More data bus class: https://github.com/moononournation/Arduino_GFX/wiki/Data-Bus-Class */
Arduino_DataBus *bus =  new Arduino_ESP32SPI(27 /* DC */, 4 /* CS */, 21 /* SCK */, 2 /* MOSI */, 18 /* MISO */, HSPI /* spi_num */);

/* More display class: https://github.com/moononournation/Arduino_GFX/wiki/Display-Class */
Arduino_GFX *gfx = new Arduino_ST7789(bus, 13 /* RST */, 0 /* rotation */, true /* IPS */);

void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
   uint32_t w = (area->x2 - area->x1 + 1);
   uint32_t h = (area->y2 - area->y1 + 1);

#if (LV_COLOR_16_SWAP != 0)
   gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#else
   gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#endif

   lv_disp_flush_ready(disp);
}
void setup()
{
    Serial.begin( 115200 ); /* prepare for possible serial debug */

    String LVGL_Arduino = "Hello test! ";
    LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

#ifdef GFX_BL
   pinMode(GFX_BL, OUTPUT);
   digitalWrite(GFX_BL, HIGH);
#endif
    Serial.println( LVGL_Arduino );
    Serial.println( "I am LVGL_Arduino" );
    gfx->begin(); // tft初始化
    lv_init();   // LVGL 初始化
    //lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * 5); //初始化图像缓冲区,参数1: 要初始化的lv_disp_draw_buf_t类型指针变量; 参数2:绘制图像缓冲区,参数3:第二个图像缓存区(如果没设置就NULL);参数4:两个缓存取的字节和,这里只有一个缓冲区所以是 buf[screenWidth * 10] 的大小;
    disp_draw_buf_1 = (lv_color_t *)heap_caps_malloc(sizeof(lv_color_t) * screenWidth * 10, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
    disp_draw_buf_2 = (lv_color_t *)heap_caps_malloc(sizeof(lv_color_t) * screenWidth * 10, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
    lv_disp_draw_buf_init(&draw_buf, disp_draw_buf_1, disp_draw_buf_2, screenWidth * 10);
    /*初始化显示*/

    lv_disp_drv_init(&disp_drv);

    disp_drv.hor_res = screenWidth;  //屏幕宽度
    disp_drv.ver_res = screenHeight; //屏幕高度
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.draw_buf = &draw_buf;
    //disp_drv.sw_rotate = 0;
    //disp_drv.full_refresh = 1;//强行刷新

   // disp_drv.rotated = LV_DISP_ROT_180;
    lv_disp_drv_register(&disp_drv);

//    static lv_indev_drv_t indev_drv;
//    lv_indev_drv_init( &indev_drv );
//    indev_drv.type = LV_INDEV_TYPE_POINTER;
//    lv_indev_drv_register( &indev_drv );
    //lv_disp_set_rotation(NULL,LV_DISP_ROT_NONE);

    ui_init();///初始化ui
    lv_anim_init(&a);//初始化动画
    lv_anim_set_exec_cb(&a, set_angle);
    conet_wifi();
    token = getToken("自己的路由器url");

    lv_timer_t * timer = lv_timer_create(my_timer,1000, NULL);
    lv_timer_ready(timer);

    xTaskCreatePinnedToCore(Task1code, "Task1", 10000, NULL, 1, NULL,  0); 
    xTaskCreatePinnedToCore(Task2code, "Task2", 10000, NULL, 1, NULL,  1); 
    
}

我的屏幕用的是ST7789的芯片,所以用了Arduino_GFX这个库,这个库是一个香港哥们写的,arduino_ide还没有集成,可以到这里下载:Arduino_GFX库
这个库比原生的e_TFT好用很多,直接在上面写好针脚就可以用了。单独说一个坑点,接线屏幕的时候一定要把BL线接上,要不背光不会亮。BL如果没定义可以接到5V5的电源接口,这个我第一次测试屏幕的时候搞了很久,差点都放弃了,后来问了店家才把这个问题解决。

再说一下setup这个函数,其实就是初始化了屏幕驱动,再把lvgl定义好的画面通过驱动绘制到屏幕上。有了设置函数,就可以用多线程把两个功能同时跑起来了。

void Task1code( void * parameter) {
  while(1){

  String demo ="{\"dev\":[{\"mac\":\"34:A8:EB:45:32:48\",\"maxdownloadspeed\":\"10805299\",\"isap\":0,\"upload\":\"325028457\",\"upspeed\":\""+(String)random(1000, 12392828)+"\",\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"online\":\"963502\",\"devname\":\"iPad\",\"maxuploadspeed\":\"218461\",\"download\":\"18395321280\"},{\"mac\":\"8C:7A:3D:04:8F:0E\",\"maxdownloadspeed\":\"6858444\",\"isap\":0,\"upload\":\"55277244\",\"upspeed\":\""+(String)random(1000, 12392828)+"\",\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"online\":\"21867\",\"devname\":\"2109119BC\",\"maxuploadspeed\":\"90081\",\"download\":\"3018158158\"},{\"mac\":\"C0:E7:BF:B5:09:65\",\"maxdownloadspeed\":\"4001943\",\"isap\":0,\"upload\":\"1144517008\",\"upspeed\":\"1180\",\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"online\":\"151531\",\"devname\":\"C0:E7:BF:B5:09:65\",\"maxuploadspeed\":\"294133\",\"download\":\"1791896891\"},{\"mac\":\"EC:41:18:7F:4A:66\",\"maxdownloadspeed\":\"3967861\",\"isap\":0,\"upload\":\"138558825\",\"upspeed\":\""+(String)random(1000, 12392828)+"\",\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"online\":\"963519\",\"devname\":\"XiaoAiTongXueX08A\",\"maxuploadspeed\":\"60177\",\"download\":\"951616701\"},{\"mac\":\"D0:50:99:9E:81:E2\",\"maxdownloadspeed\":\"3182331\",\"isap\":0,\"upload\":\"53572892\",\"upspeed\":\""+(String)random(1000, 12392828)+"\",\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"online\":\"50924\",\"devname\":\"DESKTOP-NG0I0F0\",\"maxuploadspeed\":\"102468\",\"download\":\"921821949\"},{\"mac\":\"4A:D2:D3:5B:E8:2F\",\"maxdownloadspeed\":\"10539182\",\"isap\":0,\"upload\":\"10028722\",\"upspeed\":\""+(String)random(1000, 12392828)+"\",\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"online\":\"15190\",\"devname\":\"4A:D2:D3:5B:E8:2F\",\"maxuploadspeed\":\"81967\",\"download\":\"291275054\"},{\"mac\":\"5C:02:14:99:1A:55\",\"maxdownloadspeed\":\"14203\",\"isap\":8,\"upload\":\"101259236\",\"upspeed\":\""+(String)random(1000, 12392828)+"\",\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"online\":\"963519\",\"devname\":\"MiWiFi-RA80\",\"maxuploadspeed\":\"106492\",\"download\":\"207680652\"},{\"mac\":\"28:6C:07:89:0D:02\",\"maxdownloadspeed\":\"196\",\"isap\":0,\"upload\":\"25378907\",\"upspeed\":\""+(String)random(1000, 12392828)+"\",\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"online\":\"963519\",\"devname\":\"lumi-gateway-v3_miio52267911\",\"maxuploadspeed\":\"639\",\"download\":\"6847660\"},{\"mac\":\"28:6C:07:F4:2E:95\",\"maxdownloadspeed\":\"170\",\"isap\":0,\"upload\":\"4276408\",\"upspeed\":\""+(String)random(1000, 12392828)+"\",\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"online\":\"963520\",\"devname\":\"智能插座\",\"maxuploadspeed\":\"226\",\"download\":\"5100182\"},{\"mac\":\"\",\"maxdownloadspeed\":\"0\",\"upload\":5596457,\"download\":6587521,\"ip\":\"\",\"downspeed\":"+(String)random(1000, 12392828)+",\"online\":\"0\",\"devname\":\"Others\",\"maxuploadspeed\":\"0\",\"upspeed\":"+(String)random(1000, 12392828)+"}],\"code\":0,\"mem\":{\"usage\":0.63,\"total\":\"256MB\",\"hz\":\"1333MHz\",\"type\":\"DDR3\"},\"temperature\":0,\"count\":{\"all_without_mash\":23,\"online\":11,\"all\":24,\"online_without_mash\":10},\"hardware\":{\"mac\":\"5C:02:14:50:78:51\",\"platform\":\"RA80\",\"version\":\"1.0.30\",\"channel\":\"release\",\"sn\":\"31693/F1TQ62113\"},\"upTime\":\"963687.35\",\"cpu\":{\"core\":2,\"hz\":\"1000MHz\",\"load\":0},\"wan\":{\"downspeed\":\""+(String)random(1000, 12392828)+"\",\"maxdownloadspeed\":\"12392828\",\"devname\":\"nil\",\"upload\":\"25878036264\",\"upspeed\":\""+(String)random(1000, 12392828)+"\",\"maxuploadspeed\":\"9228057\",\"download\":\"220315680110\"}}";
  String info = getRoute_info("路由器url",token);
  if(lock == false){///如果在刷新屏幕就等一下
    lock = true;
  flashData(info);
    lock = false;}else{
    sleep(500);
    flashData(info);
    }
  //flashData(demo);
  delay(10);
  //test_timer();
  //Serial.println( wan_dspeed );
  Serial.println( LV_LOG_LEVEL_TRACE );
  }
}

void Task2code( void * pvParameters ){
    while(1){
    //Serial.println( "code2" );
    lv_timer_handler();
    #if LV_USE_LOG != 0
    lv_log_register_print_cb( my_print ); /* register print function for debugging */
    
//    LV_ASSERT_MEM_INTEGRITY();
    //Serial.println( "lvgl err" );
    //LV_USE_LOG = 0;
//    lv_mem_monitor_t mon;
//    lv_mem_monitor(&mon);
//    printf("used: %6d (%3d %%), frag: %3d %%, biggest free: %6d\n",
//                           (int)mon.total_size - mon.free_size, mon.used_pct, mon.frag_pct,
//                           (int)mon.free_biggest_size);     
    
    #endif

    delay(5);
    }
}
void loop()
{
    //lv_timer_handler();
    delay( 5 );
}

上面就是大概的源代码解析,当然里面还有些图片定义的源代码,可能要自己读一下才知道怎么用,实现原理就是用lvgl的官方图片生成出二维码数组,然后将其引入到程序里面。lvgl在线图片转换工具,图片转换参数要注意一下自己屏幕支持颜色。

三、硬件部分

在所有代码编写好后,就可以呈现出自己想要的功能了,如下图
在这里插入图片描述
不过要把他装到路由器上才能算大功告成,路由器打孔这部分我找了很多店家,一听你只打一个孔都不管你了。最后找到一个比较热心的店家,搜索”小包子作坊数控加工CNC“给我成功大了个孔,不算贵60一个(辛辛苦苦写了的代码,不能让他太丑啊)。我自己不太熟这方面,就直接把屏幕寄了过去,让店家帮我量好再开孔。这里给出他给我开孔的长宽高参数
在这里插入图片描述
着急的等待了几天之后终于拿到开孔后的外壳啦!
在这里插入图片描述
屏幕固定也是个难题,因为外壳没有预留螺丝孔的,所以想通过按压方式固定是不可能的了,后来选择了用热熔胶固定这个做法比较靠谱。
在这里插入图片描述
esp的电源线的话有两种方案,第一个可以共用路由器的电源。另外一个是直接接入数据线,然后从网卡的网卡那里的小口子把线引出来。我为了省事用了第二种方案,也方便后期再调试,不过忘了拍照就没放照片了。
安装好后上电效果还不错
在这里插入图片描述
在这里插入图片描述
把他放回原位,毫无违和感
在这里插入图片描述
至此,所有工作都已经完成,下面是整个项目的源代码:
项目源代码

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值