【平头哥Sipeed LicheeRV 86 Panel测评】测评项目总结报告——桌面日历天气助手

sonicfirr   2022-4-30 10:38 楼主

       历时两个月,LicheeRV 86 Panel的测评工作将在本篇收尾。两个月的学习,促使本人全心投入到Linux学习、了解了RISC-V架构、熟悉了lvgl框架,第一次接触到WSL、Waft、Linux显示和触摸驱动文件等技术细节,可谓收获颇丰,在此感谢平头哥,感谢EEWORLD,感谢OCC。
       本例的演示视频EEWORLD链接:https://training.eeworld.com.cn/course/27374 。
       后续本人计划继续板子的学习,目前的思路是转向内核环节,再转向Waft(有点坐等版本稳定的意思)框架。本次测评本人的发帖如下:
EEWORLD链接——》
一、开箱测评
二、资料链接整理
三、镜像烧写测试
四、交叉编译环境构建
五、Waft初体验
六、Windows构建Waft环境
七、fbviewer和ts_test命令测试
八、lvgl库初体验
九、lvgl再使用
十、lvgl定时器
十一、lvgl和fork()实现TCP Client
十二、lvgl和pthread实现TCP Client
十三、利用TCP封装HTTP包请求天气信息
十四、lvgl显示图片和本地时间
十五、lvgl日历控件和显示天气
 


1、总结项目说明
       总项目在LicheeRV 86 Panel的Tina Linux平台,基于lvgl框架,结合Linux API:多线程及信号量和条件变量、Socket编程之TCP等技术实现。主要功能是:
       1)应用启动后,利用C time标准库API请求本地时间并开启1s周期的lvgl定时器,使之实现钟表和日历显示,日历使用lvgl的calendar控件;
       2)然后每正分钟,利用TCP封装HTTP向心知天气平台请求实时天气数据,并利用cJSON库对响应数据进行解析,然后显示实时天气信息;
       3)同时启动果云ESP32F开发板作为TCP Server,点击86板屏幕上Connect按钮,会建立86板与ESP32F板的TCP连接;
       4)连接建立后,再启动一个lvgl定时器,使得86板每10s向ESP32F板发送一帧请求包“reqdata\r\n”,ESP32F板收到请求包后会开启一次DHT11温湿度采集并将测量值以JSON格式发回到86板;
       5)另外,86板UI中包含一个滚动框(roller控件),可以选择LED颜色,一旦选定后会向ESP32F板发送对应颜色的全大写单词作为控制命令,ESP32F板接到命令后调整板载RGB三色灯的显示颜色。

 

image-20220430103027-1.png

图1 总结项目显示效果

 

2、ESP32端开发说明
       虽然是86板的测评,不过项目用到了ESP32,所以本节也做一下简要说明。本项目采用果云ESP32F开发板作为TCP Server,话说这还是本人的第一块ESP32开发板,虽然外围资源不算丰富,但是给本人打开了物联网的大门。

 

image-20220430103157-2.png

2 ESP32F板实物连接图

 

image-20220430103157-3.png

3 ESP32F板电路原理图

 

       ESP32的开发环境为Arduino IDE V1.8.13,安装了乐鑫的arduino-esp32 pack V1.0.6,TCP Server基于pack附带案例“WiFi->SimpleWiFiServer”修改完成。DHT11的驱动是本人做STM32教学时编写的,使用宏定义实现移植性,移植到ESP32 Arduino平台后,封装成了C++类——名为“AITA_DHT”。
       ESP32F的工程“TCPServer”包含三个文件:TCPServer.ino、AITA_DHT.h和AITA_DHT.cpp,具体代码如下:

 

// by author. AITA_DHT.h
#ifndef __AITA_DHT_H_
#define __AITA_DHT_H_

/* Includes ------------------------------------------------------- */
#include <Arduino.h>

/* Exported macro ------------------------------------------------- */
#define SUCC        0   //成功编码
#define INITTOUT    1   //初始化超时错误编码
#define CHECKSUM    2   //校验和错误编码

#define MAXCOUNT    100 //溢出次数,超过此值则超时
#define DHT_PIN     0   //控制DHT11的IO编号,此处为:GPIO0
#define IO_OUTMODE  pinMode(DHT_PIN, OUTPUT);//初始化GPIO0为输出
#define IO_INMODE   pinMode(DHT_PIN, INPUT);  //设置IO位输入
#define IO_SET      digitalWrite(DHT_PIN, HIGH);//IO输出高
#define IO_CLR      digitalWrite(DHT_PIN,  LOW);//IO输出低
#define READ_IO     digitalRead(DHT_PIN)        //读取IO输入

#define DELAY_MS    delay              //ms级延时函数
#define DELAY_US    delayMicroseconds  //us级延时函数

/* Exported class declaration ------------------------------------- */
class AITA_DHT {
  public:
  uint8_t dht11Read(uint8_t *temp, uint8_t *humi);
  
  uint8_t humidity_integer;
  uint8_t temperature_integer;
  uint8_t humidity_decimal;
  uint8_t temperature_decimal;
  uint8_t checksum;
};

#endif


//by author. AITA_DHT.cpp
/* Includes ------------------------------------------------------- */
#include "AITA_DHT.h"

/* Private functions ---------------------------------------------- */
//DHT11初始化函数
uint8_t dht11_init(void) {
    uint8_t errcode = SUCC;
    uint8_t i = 0;

    IO_OUTMODE     //设置IO输出
    IO_SET         //IO输出1
    DELAY_MS(2);   //延时2ms,为下降沿做准备
    IO_CLR         //IO输出0,产生下降沿

    DELAY_MS(20);  //延时20ms,主机下降沿并保持,18ms以上的低电平启动DHT11
    IO_SET         //再次拉高IO,等待DHT11拉低以作应答

    IO_INMODE      //设置IO输入
    DELAY_US(40);  //延时40us,等待DHT11输入的低电平应答信号

    //等待DHT11拉低IO,最高持续MAXCOUNT个us
    while(READ_IO==1 && i<MAXCOUNT) {
        i++;
        DELAY_US(1);   
    }
    if(i>=MAXCOUNT) {
        errcode = INITTOUT;
        return errcode;
    } else i = 0;
    //DHT11拉低IO持续80us,后会在拉高IO持续80us作为完整的应答
    while (READ_IO==0 && i<MAXCOUNT) {
        i++;
        DELAY_US(1);
    }
    if(i>=MAXCOUNT) {
        errcode = INITTOUT;
        return errcode;
    }
    return errcode;
}
//读取一个bit函数
uint8_t dht11_readBit(void) {
    uint8_t i = 0;
    //等待IO变低
    while(READ_IO==1 && i<MAXCOUNT) {
        i++;
        DELAY_US(1);
    }
    i = 0;
    //等待IO变高——先低再高表示开始传输一个bit
    while (READ_IO==0 && i<MAXCOUNT) {
        i++;
        DELAY_US(1);
    }
    DELAY_US(40);    //等待40us(0 bit)
    if(READ_IO==1) return 1;
    else return 0;
}
//读取一个Byte函数
uint8_t dht11_readByte(void) {
    uint8_t i = 0;
    uint8_t data = 0;
    for(i=0; i<8; i++) {
        data <<= 1;
        data |= dht11_readBit();
    } 
    return data;
}

/* Class functions ------------------------------------------------ */
//读取温湿度值
uint8_t AITA_DHT::dht11Read(uint8_t *temp, uint8_t *humi) {
    uint8_t buf[5];
    uint8_t i;
    
    if(!dht11_init()) {
        for(i=0; i<5; i++) {
            buf = dht11_readByte();
        }
        
        humidity_integer = buf[0];
        humidity_decimal = buf[1];
        temperature_integer = buf[2];
        temperature_decimal = buf[3];
        checksum = buf[0]+buf[1]+buf[2]+buf[3];
        
        if((buf[0]+buf[1]+buf[2]+buf[3]) == buf[4]) {
            *humi = buf[0];
            *temp = buf[2];
        } else {
            return CHECKSUM;
        }
        
    } else {
        return INITTOUT;
    }
    return SUCC;
}


//by author. TCPServer.ino
/*
 需要配置ssid和password为使用热点的内容。
 而ESP32的IP地址需要在系统运行初始时通过串口监视器获取,
 建立一个TCP Server端口号1234,
 有client接入后,如果收到命令“reqdata”则开启一次DHT11检测,并回传检测值。
 如果收到命令“BLACK……WHITE”则控制RGB LED显示对应颜色。
 手机或PC处于相同网段,利用网络调试助手连接,则可得到ESP32发送过来的温湿度值。
 同时,调试助手发送的信息,ESP32的串口可以获取到。
*/

/* Includes ------------------------------------------------------- */
#include "AITA_DHT.h"
#include <WiFi.h> 
#include <Arduino.h> 

/* Private Macros ------------------------------------------------- */
#define MAX_SRV_CLIENTS 3     //Client同时连接数 
#define CONLB           32    //GPIO32 LED Blue
#define CONLG           33    //GPIO33 LED Green
#define CONLR           27    //GPIO27 LED Red

#define BLACK           {digitalWrite(CONLR, 1);digitalWrite(CONLG, 1);digitalWrite(CONLB, 1);}
#define RED             {digitalWrite(CONLR, 0);digitalWrite(CONLG, 1);digitalWrite(CONLB, 1);}
#define GREEN           {digitalWrite(CONLR, 1);digitalWrite(CONLG, 0);digitalWrite(CONLB, 1);}
#define BLUE            {digitalWrite(CONLR, 1);digitalWrite(CONLG, 1);digitalWrite(CONLB, 0);}
#define YELLOW          {digitalWrite(CONLR, 0);digitalWrite(CONLG, 0);digitalWrite(CONLB, 1);}
#define AQUA            {digitalWrite(CONLR, 1);digitalWrite(CONLG, 0);digitalWrite(CONLB, 0);}
#define PURPLE          {digitalWrite(CONLR, 0);digitalWrite(CONLG, 1);digitalWrite(CONLB, 0);}
#define WHITE           {digitalWrite(CONLR, 0);digitalWrite(CONLG, 0);digitalWrite(CONLB, 0);}

/* Object definition ---------------------------------------------- */
AITA_DHT dht11;  //GPIO0作DHT11数据IO("AITA_DHT.h"中宏DHT_PIN)
uint8_t temperature, humidity;

const char *ssid = "yourssid";    //Wi-Fi ssid即热点名称
const char *password = "password";//Wi-Fi密码
WiFiServer server(1234);          //Server端口1234
WiFiClient serverClients[MAX_SRV_CLIENTS];//Client数组

/* setup() & loop() ----------------------------------------------- */
void setup() {
  Serial.begin(115200);
  delay(10);
  pinMode(CONLR, OUTPUT);
  pinMode(CONLG, OUTPUT);
  pinMode(CONLB, OUTPUT);
  BLACK
  
  /* 设置ESP为STA模式的TCPServer */
  WiFi.mode(WIFI_STA);                  //STA模式
  WiFi.begin(ssid, password);           //连接AP
  while (WiFi.status() != WL_CONNECTED) //等待连接成功
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  server.begin();           //开启TCPServer
  server.setNoDelay(true);  //关闭小包合并包功能,不会延时发送数据
  Serial.println("Server started"); 
  Serial.println(WiFi.localIP());       //串口打印IP地址
}

void loop() {
  delay(1);
  uint8_t i;
//有客户端接入  
  if(server.hasClient()) { 
    //客户端加入数组serverClients
    for(i=0; i<MAX_SRV_CLIENTS; i++) {
      if(!serverClients || !serverClients.connected()) {
        if(serverClients) serverClients.stop();//未联接即释放
        serverClients = server.available();       //分配新的
        continue;
      }
    }
  }
  
  for(i=0; i<MAX_SRV_CLIENTS; i++) {
    if(serverClients && serverClients.connected()) {
      if(serverClients.available()) { 
        String order = "";
        
        //接收到客户端发送的信息,通过串口输出
        while(serverClients.available()) {
          char c = serverClients.read();
          if(c != '\r') order = order + c;          
          Serial.write(c);
        }
        
        if(order.length() > 1024) order = "";

        if(order.indexOf("reqdata") != -1) {
          if(!dht11.dht11Read(&temperature, &humidity)) {    
            for(i=0; i<MAX_SRV_CLIENTS; i++) {
              if(serverClients && serverClients.connected()) {
                serverClients.printf("{\"humi\":%d,\"temp\":%d}", humidity, temperature);
                delay(1);
              }
            }
          } 
          order = "";
        } else if(order.indexOf("BLACK") != -1) {
          BLACK 
          order = "";
        } else if(order.indexOf("RED") != -1) {
          RED
          order = "";
        } else if(order.indexOf("GREEN") != -1) {
          GREEN 
          order = "";
        } else if(order.indexOf("BLUE") != -1) {
          BLUE 
          order = "";
        } else if(order.indexOf("YELLOW") != -1) {
          YELLOW 
          order = "";
        } else if(order.indexOf("AQUA") != -1) {
          AQUA 
          order = "";
        } else if(order.indexOf("PURPLE") != -1) {
          PURPLE 
          order = "";
        } else if(order.indexOf("WHITE") != -1) {
          WHITE 
          order = "";
        }
      }
    }
  }
}

 

       ESP32F板在上电后,连接WiFi并建立TCP Server,端口号1234,之后持续监听Client的接入请求。当有Client接入后,判断其发来的命令:
       1)收到“reqdata\r\n”则采集DHT11,并封装为如:{"humi":48,"temp":25},这样的JSON格式数据回传;
       2)收到“BLACK”则控制RGB LED熄灭,其它则是点亮对应颜色,如:“RED”点亮红色,“YELLOW”点亮红绿色组成黄光……

 

3、LicheeRV 86 Panel端开发说明
       86板端采用lvgl框架,实际代码是综合了本人第十二~十五篇测评报告的案例而成。
       开发环境为Win10 2021WSL(Win10 2004以上就可以使用WSL),IDE为VS Code,交叉工具链为d1阿里小程序_sdk版本。
       WSL的安装过程参考:https://docs.microsoft.com/zh-cn/windows/wsl/install 。
       写下此篇时发现OCC上交叉工具链的资源已经更新了,以本人在测评期间的经验,工具链版本兼容性问题还是挺多的,所以想尝试本例的朋友,可以到本人百度云下载老版工具链(当然,也可以实验一下新版工具链是否可用)。
       链接:https://pan.baidu.com/s/1fwHdCxlXdnr6Mi2S8_-QCg  
       提取码:scuu 

 

       项目的lvgl框架是由同样参与测评的博主manhuami2007所提供,项目上传在EEWORLD下载专区:https://download.eeworld.com.cn/detail/manhuami2007/623017 。本人在使用这版Demo时遇到了几处Makefile错误,调整过程请参考本人的测评帖子“八、lvgl库初体验”。
       环境搭建后,大家应该可以在Windows通过VS Code打开项目,然后VS Code可以新建WSL控制台,后续的命令行操作就简单了——先source命令生效环境变量,然后在项目根目录执行make all。具体过程请参考本人的测评帖子“九、lvgl再使用”。
       案例需要cJSON库,源文件cJSON.c和cJSON.h放在项目根目录(main.c同目录下)。外加项目启用了Linux子线程,cJSON需要math库,所以根目录Makefile进行了修改,具体如下——其中 BIN = aa_demo 表示最后生成的可执行文件名为“aa_demo”,大家可以根据喜好修改名称,生成文件也位于根目录。

 

#
# Makefile
CC ?= $$CC
LVGL_DIR_NAME ?= lvgl
LVGL_DIR ?= ${shell pwd}
CFLAGS ?= -O3 -g0 -I$(LVGL_DIR)/ -Wall -Wshadow -Wundef -Wmissing-prototypes -Wno-discarded-qualifiers -Wall -Wextra -Wno-unused-function -Wno-error=strict-prototypes -Wpointer-arith -fno-strict-aliasing -Wno-error=cpp -Wuninitialized -Wmaybe-uninitialized -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wformat-security -Wno-ignored-qualifiers -Wno-error=pedantic -Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body -Wtype-limits -Wshift-negative-value -Wstack-usage=2048 -Wno-unused-value -Wno-unused-parameter -Wno-missing-field-initializers -Wuninitialized -Wmaybe-uninitialized -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wpointer-arith -Wno-cast-qual -Wmissing-prototypes -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wno-discarded-qualifiers -Wformat-security -Wno-ignored-qualifiers -Wno-sign-compare
LDFLAGS ?= -lm
BIN = aa_demo

#Collect the files to compile
MAINSRC = ./main.c ./cJSON.c

include $(LVGL_DIR)/lvgl/lvgl.mk
include $(LVGL_DIR)/lv_drivers/lv_drivers.mk

OBJEXT ?= .o

AOBJS = $(ASRCS:.S=$(OBJEXT))
COBJS = $(CSRCS:.c=$(OBJEXT))

MAINOBJ = $(MAINSRC:.c=$(OBJEXT))

SRCS = $(ASRCS) $(CSRCS) $(MAINSRC)
OBJS = $(AOBJS) $(COBJS)

## MAINOBJ -> OBJFILES

all: default

%.o: %.c
    @$(CC)  $(CFLAGS) -c $< -o $@
    [url=home.php?mod=space&uid=43340]@echo[/url] "CC $<"
    
default: $(AOBJS) $(COBJS) $(MAINOBJ)
    $(CC) -o $(BIN) $(MAINOBJ) $(AOBJS) $(COBJS) $(LDFLAGS)  -lpthread -lm

clean: 
    rm -f $(BIN) $(AOBJS) $(COBJS) $(MAINOBJ)

 

       main.c的代码比较多,具体如下——其中连接的头文件“aita_logo.h”是天津Logo的图片码,生成方式请参考本人的测评帖子“十四、lvgl显示图片和本地时间”。
       另外,宏定义API_KEY是心知天气的请求API,请大家自行去心知天气网站申请(免费版即可)。宏定义HTTP_IP和HTTP_PORT是心知天气的请求端口,应该不会经常有变化(个人经验,心知天气的URL请求接口可是老变的)。宏定义SERVER_IP和SERVER_PORT是ESP32的TCP Server端口,大家请根据自己的情况进行调整。宏定义PERIOD是86板向ESP32请求温湿度数据的周期,单位是ms。
       参数宏AITA_COLOR是本人测试过程中发现的一个情况,86板使用lvgl API “lv_color_make()函数”建立颜色变量(lv_color_t类型)时,red字节和blue字节需要调换位置。有关这个现象本人猜想86板的屏幕是BGR格式的,不过猜想暂未验证。本例AITA_COLOR没有使用到。
       本例请求心知天气实时数据为JSON格式,ESP32发送的温湿度也是JSON格式,为了保存解析后的数据,创建了结构体weather_t和sensor_t。

 

/* Includes ------------------------------------------------------- */
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/indev/evdev.h"
#include "cJSON.h"
#include "aita_logo.h"

/* Private macro -------------------------------------------------- */
#define AITA_DISP_BUF_SIZE     (128 * 1024)
#define AITA_SCREEN_WIDTH      480
#define AITA_SCREEN_HEIGHT     480
#define AITA_TITLE_STRING      "AITA Weather for LicheeRV with LVGL"
#define HTTP_IP                "116.62.81.138"
#define HTTP_PORT              80
#define NOW                    "now.json"
#define API_KEY                "xinzhitianqiapi"
#define CITY                   "tianjin"
#define REQ_PACK               "GET https://api.thinkpage.cn/v3/weather/%s?key=%s&location=%s&language=en&unit=c\r\n\r\n"
#define N                      1024
#define OFF                    0
#define ON                     1
#define PERIOD                 10000
#define SERVER_IP              "192.168.31.240"
#define SERVER_PORT            1234
#define SEND_MSG               "reqdata\r\n"
#define AITA_COLOR(r, g, b)    lv_color_make(b, g, r)
#define errlog(errmsg)         do{ perror(errmsg);\
    printf("----%s----%s----%d----\n", __FILE__, __func__, __LINE__);\
    return;\
    } while(0)

/* typedef struct ------------------------------------------------- */
//struct for weather data
typedef struct {
    char id[16];
    char name[32];
    char country[16];
    char path[64];
    char timezone[32];
    char tz_offset[16];
    char text[16];
    char code[4];
    char temp[8];
    char last_update[32];
} weather_t;
//struct for sensor data which received from ESP32 by TCP
typedef struct {   
    int humi;
    int temp;
} sensor_t;

/* Global variables ----------------------------------------------- */
lv_indev_t *aita_indev;   //pointer of indev
lv_obj_t *sys_scr;        //pointer of system screen instance
pthread_t lvgl_tid;       //lvgl thread id
pthread_mutex_t lvgl_mutex;  //mutex for lvgl tick
lv_obj_t *head_label;     //pointer of title label instance

lv_obj_t *main_label;     //pointer of main label instance
char main_label_text[32]; //main label text string for datetime
lv_obj_t *calendar;       //pointer of calendar instance
lv_timer_t *sec_timer;    //pointer of timer instance for 1-s timing
struct tm today;          //
lv_obj_t *weather_label;  //pointer of weather label instance
pthread_t reqweather_tid; //request weather thread id
pthread_mutex_t cond_mutex;  //mutex for 1-min cond
pthread_cond_t  min_cond; //1-min cond

lv_obj_t *logo_img;       //pointer of city logo image instance
lv_img_dsc_t img_dsc_city = {//image descriptor for logo_img
  .header.always_zero = 0,
  .header.w = 180,
  .header.h = 100,
  .data_size = 18000 * LV_COLOR_SIZE / 8,
  .header.cf = LV_IMG_CF_TRUE_COLOR,
  .data = tj_logo,
}; //ARGB8888 image 180*100 which code array is 'tj_logo'

lv_obj_t *led_roller;     //pointer of roller instance for choicing led color
lv_obj_t *temp_label;     //pointer of label instance for indoor temperature
lv_obj_t *humi_label;     //pointer of label instance for indoor humidity
lv_obj_t *conn_btn;       //pointer of conn button(tcp client) for instance
lv_obj_t *conn_btn_label; //pointer of label instance for conn button
int conn_flag = OFF;      //flag of tcp connect status
int sockfd;               //socket file descriptor
struct sockaddr_in server;//tcp server ip;
socklen_t addrlen = sizeof(server);
lv_timer_t *tcp_timer;    //pointer of timer instance for tcp polling
pthread_t tcprecv_tid;    //tcp recv thread id
char recv_buf[N] = "";    //recv buffer

/* Private function prototypes ------------------------------------ */
void aita_InitLVGL(void);
void aita_CreateMainUI(void);
void *thread_lvgl(void *arg);

void aita_InitTimer(void);
void aita_GetTime(void);
void sec_timer_cb(lv_timer_t *timer);
void *thread_reqweather(void *arg);
void aita_ParseJsonNow(char *msg, weather_t *w);
void aita_PrintWeather(weather_t *w);

void aita_InitSocket(void);
void conn_btn_click_cb(lv_event_t *e);
void tcp_timer_cb(lv_timer_t *timer);
void *thread_tcprecv(void *arg);
void aita_ParseJsonSensor(char *msg, sensor_t *s);
void led_roller_event_cb(lv_event_t *e);

/* Private functions ---------------------------------------------- */
int main(void) {
    void *retval;

//by author. initialize lvgl including displaybuffer, device for disp & input
    aita_InitLVGL();
    
//by author. initialize and register event device
//these code must be in main(), otherwise the touch will fail.
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER; //choice touchpad
    indev_drv.read_cb = evdev_read;         //input callback
    aita_indev = lv_indev_drv_register(&indev_drv);

//by author. create the main view when the demo starts up    
    aita_CreateMainUI();

//by author. create a timer
    aita_InitTimer();

//by author. create mutex for lvgl
    if(pthread_mutex_init(&lvgl_mutex, NULL) != 0) {
        errlog("initialize lvgl mutex error");
    }

//by author. create mutex for 1-min cond
    if(pthread_mutex_init(&cond_mutex, NULL) != 0) {
        errlog("initialize cond mutex error");
    }

//by author. create condition for 1-min
    if(pthread_cond_init(&min_cond, NULL) != 0) {
        errlog("initialize 1 minute condition error");
    }

//by author. create lvgl thread
    if(pthread_create(&lvgl_tid, NULL, thread_lvgl, (void *)0) != 0) {
        errlog("create lvgl thread error");
    }

//by author. create request weather thread
    if(pthread_create(&reqweather_tid, NULL, thread_reqweather, (void *)0) != 0) {
        errlog("create request weather thread error");
    }

//by author. wait for thread exit, this demo should never be here.
    pthread_join(lvgl_tid, &retval);
    printf("lvgl thread exit, return value: %s\n", (char *)retval);
    pthread_join(reqweather_tid, &retval);
    printf("request weather thread exit, return value: %s\n", (char *)retval);
    pthread_mutex_destroy(&lvgl_mutex);
    pthread_mutex_destroy(&cond_mutex);
    pthread_cond_destroy(&min_cond);
    return 0;
}

/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
    static uint64_t start_ms = 0;
    if(start_ms == 0) {
        struct timeval tv_start;
        gettimeofday(&tv_start, NULL);
        start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
    }

    struct timeval tv_now;
    gettimeofday(&tv_now, NULL);
    uint64_t now_ms;
    now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;

    uint32_t time_ms = now_ms - start_ms;
    return time_ms;
}

void aita_InitLVGL(void) {
    /*LittlevGL init*/
    lv_init();

    /*Linux frame buffer device init*/
    fbdev_init(); //by author. initialize framebuffer device for display
    evdev_init(); //by author. initialize event device for touchpad

    /*A small buffer for LittlevGL to draw the screen's content*/
    static lv_color_t buf[AITA_DISP_BUF_SIZE];
    /*Initialize a descriptor for the buffer*/
    static lv_disp_draw_buf_t disp_buf;
    lv_disp_draw_buf_init(&disp_buf, buf, NULL, AITA_DISP_BUF_SIZE);
    /*Initialize and register a display driver*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.draw_buf   = &disp_buf;
    disp_drv.flush_cb   = fbdev_flush;
    disp_drv.hor_res    = 480;
    disp_drv.ver_res    = 480;
    lv_disp_drv_register(&disp_drv);
}

void aita_CreateMainUI(void) {
//by author. create system screen which is basic graphic level
    sys_scr = lv_obj_create(lv_scr_act());
    lv_obj_set_size(sys_scr, AITA_SCREEN_WIDTH, AITA_SCREEN_HEIGHT);

//by author. create the main title label
    head_label = lv_label_create(sys_scr);
    lv_label_set_text(head_label, AITA_TITLE_STRING);
    lv_obj_align(head_label, LV_ALIGN_TOP_MID, 0, 10);

//by author. create the city logo image
    logo_img = lv_img_create(sys_scr);
    lv_img_set_src(logo_img, &img_dsc_city);
    lv_obj_align(logo_img, LV_ALIGN_TOP_LEFT, 10, 40);

//by author. get local time and show string
    aita_GetTime();
    main_label = lv_label_create(sys_scr);
    lv_label_set_text(main_label, main_label_text);
    lv_obj_align(main_label, LV_ALIGN_TOP_LEFT, 200, 40);
    lv_obj_set_style_text_font(main_label, &lv_font_montserrat_20, 0);

//by author. create the weather label
    weather_label = lv_label_create(sys_scr);
    lv_label_set_text(weather_label, "         ");
    lv_obj_align(weather_label, LV_ALIGN_TOP_LEFT, 200, 120);

//by author. create the calendar
    calendar = lv_calendar_create(sys_scr);
    lv_obj_set_size(calendar, 235, 235);
    lv_obj_align(calendar, LV_ALIGN_BOTTOM_LEFT, 10, -50);
    lv_calendar_set_today_date(calendar, today.tm_year+1900, today.tm_mon+1, today.tm_mday);
    lv_calendar_set_showed_date(calendar, today.tm_year+1900, today.tm_mon+1);
    lv_calendar_header_arrow_create(calendar);

//by author. create led roller
    led_roller = lv_roller_create(sys_scr);
    lv_obj_set_size(led_roller, 170, 40);
    lv_obj_align_to(led_roller, calendar, LV_ALIGN_OUT_RIGHT_TOP, 25, 0);
    lv_roller_set_options(led_roller, 
        "RED\nGREEN\nBLUE\nYELLOW\nAQUA\nPURPLE\nWHITE\nBLACK", LV_ROLLER_MODE_INFINITE);
    lv_roller_set_visible_row_count(led_roller, 1);
    lv_obj_add_event_cb(led_roller, led_roller_event_cb, LV_EVENT_ALL, NULL);

//by author. create sensor data showing label
    humi_label = lv_label_create(sys_scr);
    lv_obj_align_to(humi_label, calendar, LV_ALIGN_OUT_RIGHT_TOP, 25, 90);
    lv_label_set_text(humi_label, "indoor humidity:   68%%");
    temp_label = lv_label_create(sys_scr);
    lv_obj_align_to(temp_label, calendar, LV_ALIGN_OUT_RIGHT_TOP, 25, 55);
    lv_label_set_text(temp_label, "indoor temprature: 25C");

//by author. create the conn button for connecting a tcp server
    conn_btn = lv_btn_create(sys_scr);
    lv_obj_set_size(conn_btn, 170, 50);
    lv_obj_align_to(conn_btn, calendar, LV_ALIGN_OUT_RIGHT_BOTTOM, 25, 0);
//by author. register clicked-event callback for the "conn_btn" object
    lv_obj_add_event_cb(conn_btn, conn_btn_click_cb, LV_EVENT_CLICKED, NULL);
    conn_btn_label = lv_label_create(conn_btn);
    lv_label_set_text(conn_btn_label, "Connect");
    lv_obj_center(conn_btn_label);
}

//by author. initialize timer for 1s timing
void aita_InitTimer(void) {
//by author. sec_timer startup immediately
    sec_timer = lv_timer_create(sec_timer_cb, 1000, NULL);
    lv_timer_set_repeat_count(sec_timer, -1);
//by author. tcp_timer will startup when connection is done
    tcp_timer = lv_timer_create(tcp_timer_cb, PERIOD, NULL);
    lv_timer_set_repeat_count(tcp_timer, -1);
    lv_timer_pause(tcp_timer);
}

//by author. lvgl core thread function
void *thread_lvgl(void *arg) {
    while(1) {
        pthread_mutex_lock(&lvgl_mutex);
        lv_task_handler();
        pthread_mutex_unlock(&lvgl_mutex);
        usleep(5000); /* sleep for 5 ms */
    }
}

//by author. sec_timer callback which refresh date string
void sec_timer_cb(lv_timer_t *timer) {
    aita_GetTime();
    lv_label_set_text(main_label, main_label_text);
    if(today.tm_sec == 0) {
        //by author. send condition signal per whole minute
        pthread_cond_signal(&min_cond);
    }
}

//by author. get local time string
void aita_GetTime(void) {
    time_t    tsec;
    struct tm *tlocal;
    tsec = time(NULL);
    tlocal = localtime(&tsec);
    today = *tlocal;
    memset(main_label_text, 0, 32);
    strftime(main_label_text, 32, "%Y-%m-%d %a %H:%M:%S", tlocal);
}

//by author. request weather thread function
void *thread_reqweather(void *arg) {
    int sockfd;
    struct sockaddr_in serveraddr;
    socklen_t addrlen = sizeof(serveraddr);
    char sendbuf[N] = "";
    char recvbuf[N] = "";
    weather_t weather = {0};
    char w_string[64] = "";

    while(1) {
        pthread_mutex_lock(&cond_mutex);
        pthread_cond_wait(&min_cond, &cond_mutex);
        pthread_mutex_unlock(&cond_mutex);     
    //create socket
        if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            errlog("socket error");
        }
    //connect to server of seniverse.com
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = inet_addr(HTTP_IP);
        serveraddr.sin_port = htons(HTTP_PORT);
        if((connect(sockfd, (struct sockaddr*)&serveraddr, addrlen)) < 0) {
            errlog("connect error");
        }
    //build & send request package
        memset(sendbuf, 0, N);
        sprintf(sendbuf, REQ_PACK, NOW, API_KEY, CITY);
        if(send(sockfd, sendbuf, N, 0) < 0) {
            errlog("send error");
        }
    //waiting server response
        if(recv(sockfd, recvbuf, N, 0) < 0) {
            errlog("recv error");
        }
        printf("recv: %s\n", recvbuf);
    //parse & print data
        aita_ParseJsonNow(recvbuf, &weather);
        aita_PrintWeather(&weather);
        close(sockfd);
        memset(recvbuf, 0, N);
    //show weather string
        memset(w_string, 0, 64);
        sprintf(w_string, "weather:%s temperatur:%s", weather.text, weather.temp);
        pthread_mutex_lock(&lvgl_mutex);
        lv_label_set_text(weather_label, w_string);
        pthread_mutex_unlock(&lvgl_mutex);       
    }
}
//by author. parse now-response-json-pack to weather_t variable
void aita_ParseJsonNow(char *msg, weather_t *w) {
    cJSON *json, *ja, *jo, *josub, *item;
    json = cJSON_Parse(msg); //parse string to cJSON type
    if(json == NULL) {
        printf("json type cast error: %s", cJSON_GetErrorPtr());
        return;
    } else {
        printf("parse now pack\n");
        if((ja=cJSON_GetObjectItem(json, "results")) != NULL) { //get results array
            if((jo=cJSON_GetArrayItem(ja, 0)) != NULL) {        //get array[0](the only item)
                //get location object
                if((josub=cJSON_GetObjectItem(jo, "location")) != NULL) {
                    if((item=cJSON_GetObjectItem(josub, "id")) != NULL) {
                        memcpy(w->id, item->valuestring, strlen(item->valuestring));
                    }
                    if((item=cJSON_GetObjectItem(josub, "name")) != NULL) {
                        memcpy(w->name, item->valuestring, strlen(item->valuestring));
                    }
                    if((item=cJSON_GetObjectItem(josub, "country")) != NULL) {
                        memcpy(w->country, item->valuestring, strlen(item->valuestring));
                    }
                    if((item=cJSON_GetObjectItem(josub, "path")) != NULL) {
                        memcpy(w->path, item->valuestring, strlen(item->valuestring));
                    }
                    if((item=cJSON_GetObjectItem(josub, "timezone")) != NULL) {
                        memcpy(w->timezone, item->valuestring, strlen(item->valuestring));
                    }
                    if((item=cJSON_GetObjectItem(josub, "timezone_offset")) != NULL) {
                        memcpy(w->tz_offset, item->valuestring, strlen(item->valuestring));
                    }
                }
                //get now object
                if((josub=cJSON_GetObjectItem(jo, "now")) != NULL) {
                    if((item=cJSON_GetObjectItem(josub, "text")) != NULL) {
                        memcpy(w->text, item->valuestring, strlen(item->valuestring));
                    }
                    if((item=cJSON_GetObjectItem(josub, "code")) != NULL) {
                        memcpy(w->code, item->valuestring, strlen(item->valuestring));
                    }
                    if((item=cJSON_GetObjectItem(josub, "temperature")) != NULL) {
                        memcpy(w->temp, item->valuestring, strlen(item->valuestring));
                    }
                }
                //get last_update object
                if((josub=cJSON_GetObjectItem(jo, "last_update")) != NULL) {
                    memcpy(w->last_update, josub->valuestring, strlen(josub->valuestring));                 
                }
            }
        }
    }
    //delete original json pack free memory
    cJSON_Delete(json);
    return;
}
//by author. print weather_t varible's fields
void aita_PrintWeather(weather_t *w) {
    printf("id: %s\n", w->id);
    printf("name: %s\n", w->name);
    printf("country: %s\n", w->country);
    printf("path: %s\n", w->path);
    printf("timezone: %s\n", w->timezone);
    printf("timezone_offset: %s\n", w->tz_offset);
    printf("text: %s\n", w->text);
    printf("code: %s\n", w->code);
    printf("temperature: %s\n", w->temp);
    printf("last_update: %s\n", w->last_update);
}

//by author. led roller's callback will send
//LED-color-changing-order to ESP32 tcp server
void led_roller_event_cb(lv_event_t *e) {
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t *obj = lv_event_get_target(e);
    if(code == LV_EVENT_VALUE_CHANGED) {
        char buf[32];
        lv_roller_get_selected_str(obj, buf, sizeof(buf));
        LV_LOG_USER("Selected led color: %s\n", buf);
        int len = strlen(buf);
        buf[len] = '\r';
        buf[len+1] = '\n';
        if(conn_flag == ON) {
            if(send(sockfd, buf, len+2, 0) < 0) errlog("send led color-change order error");
        }
    }
}
//by author. initialize the socket
void aita_InitSocket(void) {
    if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0) errlog("socket error");
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(SERVER_IP);
    server.sin_port = htons(SERVER_PORT);
    printf("sockfd: %d\n", sockfd);
}
//by author. tcp_timer callback which sends msg to server
void tcp_timer_cb(lv_timer_t *timer) {    
    if(send(sockfd, SEND_MSG, sizeof(SEND_MSG), 0) < 0) errlog("send error");
}
//by author. conn_btn callback which conn/disconn server
void conn_btn_click_cb(lv_event_t *e) {
    printf("Conn Btn Clicked\n");
    if(conn_flag == OFF) {
        aita_InitSocket();
        printf("server ip: %s, port: %d\n", SERVER_IP, SERVER_PORT);
        if(connect(sockfd, (struct sockaddr*)&server, addrlen) < 0) errlog("connect error");
        printf("connected\n");
        lv_timer_resume(tcp_timer);
        conn_flag = ON;
        lv_label_set_text(conn_btn_label, "Disconnect");
        //by author. create a thread for tcp recv
        if(pthread_create(&tcprecv_tid, NULL, thread_tcprecv, (void *)0) != 0) {
            errlog("create tcp receive thread error");
        }
    } else {
        lv_timer_pause(tcp_timer);
        pthread_cancel(tcprecv_tid);
        close(sockfd);
        conn_flag = OFF;
        lv_label_set_text(conn_btn_label, "Connect");
    }
}
//by author. tcp receive thread function
void *thread_tcprecv(void *arg) {
    char str[32];
    sensor_t sensor;
    while(1) {
        if(read(sockfd, recv_buf, N) < 0) errlog("recv error");
        printf("server: %s\n", recv_buf);        
        aita_ParseJsonSensor(recv_buf, &sensor);       
        pthread_mutex_lock(&lvgl_mutex);
        memset(str, 0, 32);
        sprintf(str, "indoor humidity:   %d%%", sensor.humi);
        lv_label_set_text(humi_label, str);
        memset(str, 0, 32);
        sprintf(str, "indoor temprature: %dC", sensor.temp);
        lv_label_set_text(temp_label, str);
        pthread_mutex_unlock(&lvgl_mutex);
        memset(recv_buf, 0, N);
    }
}
//by author. parse json-pack to sensor_t variable
//json-pack which is like {"humi":58,"temp":25}
//is sensor data sent by esp32 tcp server
void aita_ParseJsonSensor(char *msg, sensor_t *s) {
    cJSON *json, *jhumi, *jtemp;
    json = cJSON_Parse(msg);
    if(json == NULL) {
        printf("json type cast error: %s", cJSON_GetErrorPtr());
        return;
    } else {
        printf("parse sensor pack\n");
        if((jhumi=cJSON_GetObjectItem(json, "humi")) != NULL) {
            s->humi = jhumi->valueint;
        }
        if((jtemp=cJSON_GetObjectItem(json, "temp")) != NULL) {
            s->temp = jtemp->valueint;
        }
        printf("parse sensor done\n");
    }
    //delete original json pack free memory
    cJSON_Delete(json);
    return;
}
本帖最后由 sonicfirr 于 2022-4-30 20:40 编辑

回复评论 (2)

LicheeRV 86 Panel的测评,1-15篇测评不容易,收藏

点赞  2022-4-30 16:00

这么系统学习下来,收货肯定很大,楼主加油。

加油!在电子行业默默贡献自己的力量!:)
点赞  2022-5-5 09:33
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复