历时两个月,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三色灯的显示颜色。
图1 总结项目显示效果
2、ESP32端开发说明
虽然是86板的测评,不过项目用到了ESP32,所以本节也做一下简要说明。本项目采用果云ESP32F开发板作为TCP Server,话说这还是本人的第一块ESP32开发板,虽然外围资源不算丰富,但是给本人打开了物联网的大门。
图2 ESP32F板实物连接图
图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 编辑