安装HaaS开发环境
安装VSCode
如果网络条件允许可以直接在VSCode官网下载。
安装HaaS Studio
完成后会提示激活Aos插件,确认即可。
启用HaaS进入HaaS Studio启动界面
下载ESP32固件并烧录
采用M5 Core2作为开发板,内置屏幕方便设置,该版本是单独的固件,区别是ESP32-8M。
M5 CORE2是ESP32平台,并非C3或S3。需要注意区分。
烧录完成后,准备工作结束。
关于HaaS UI
在搜索HaaS Studio插件的时候,HaaS UI无法运行在ESP32单片机上,对于硬件需求很高。是基于Vue.js开发的一套物联网UI。
本文不涉及相关内容,为防止迷惑,特此说明。
代码写UI Lite for Python
创建ESP32轻应用工程
示例程序中有3个包含了UI Lite for Python,分别是倒计时计数器、音乐播放器和温湿度面板。
以倒计时计数器为例,看一下代码。
载入LVGL
importlvglaslvisStarted=FalseisAnimationComplete=Falsearc= [None, None, None, None] anim= [None, None, None, None] timeCount= [1, 3, 5, 10] currentSelect=0minuteLabel=NonesecondLabel=NonemillionLabel=Noneanim_timeline=NonestartLabel=NonecurrentValue=0lvglInitialized=FalsedefsetLabelValue(value): globalcurrentValueglobalminuteLabelglobalsecondLabelglobalmillionLabelglobalstartLabelcurrentValue=valuecurrentMillionSecond=value*20minute=currentMillionSecond/1000/60minuteLabel.set_text('%02d'%minute) second=currentMillionSecond/1000%60secondLabel.set_text('%02d'%second) million=value%60millionLabel.set_text('%02d'%million) defset_time_value(obj, v): setLabelValue(v) obj.set_value(v) defreset_button_event_handler(e): globalisStartedglobalisAnimationCompleteglobalcurrentValueglobaltimeCountglobalarcglobalanimglobalanim_timelineglobalstartLabelif (isStarted): returnisAnimationComplete=FalsecurrentValue=timeCount[currentSelect] *60*50arc[currentSelect].set_value(currentValue) anim[currentSelect] =lv.anim_t() anim[currentSelect].init() anim[currentSelect].set_var(arc[currentSelect]) anim[currentSelect].set_time(currentValue*20) anim[currentSelect].set_values(currentValue, 0) anim[currentSelect].set_custom_exec_cb(lambdaa1, val: set_time_value(arc[currentSelect], val)) anim_timeline=lv.anim_timeline_create() lv.anim_timeline_add(anim_timeline, 0, anim[currentSelect]) startLabel.set_text("START") setLabelValue(currentValue) defarc_event_handler(e, index): globalisStartedglobalcurrentSelectglobalarcprint("index: "+str(index) +" currentSelect: "+str(currentSelect)) print("isStarted: "+str(isStarted)) if (isStartedorcurrentSelect==index): returnarc[currentSelect].set_value(0) arc[currentSelect].set_style_arc_width(2, lv.PART.INDICATOR) arc[currentSelect].set_style_arc_width(2, lv.PART.MAIN) currentSelect=indexarc[currentSelect].set_style_arc_width(8, lv.PART.INDICATOR) arc[currentSelect].set_style_arc_width(8, lv.PART.MAIN) reset_button_event_handler(e) defstart_button_event_handler(e): globalisStartedglobalisAnimationCompleteglobalanim_timelineglobalstartLabelglobalanimglobalcurrentSelectglobalcurrentValueif (isAnimationComplete): returnif (isStarted): isStarted=Falselv.anim_timeline_stop(anim_timeline) lv.anim_timeline_del(anim_timeline) anim_timeline=NonestartLabel.set_text("RESUME") anim[currentSelect] =lv.anim_t() anim[currentSelect].init() anim[currentSelect].set_var(arc[currentSelect]) anim[currentSelect].set_time(currentValue*20) anim[currentSelect].set_values(currentValue, 0) anim[currentSelect].set_custom_exec_cb(lambdaa1, val: set_time_value(arc[currentSelect],val)) anim_timeline=lv.anim_timeline_create() lv.anim_timeline_add(anim_timeline, 0, anim[currentSelect]) else: isStarted=Truelv.anim_timeline_start(anim_timeline) startLabel.set_text("PAUSE") classCountDown: defcreatePage(self, value1=1, value2=3, value3=5, value4=10): globalisStartedglobalisAnimationCompleteglobalarcglobalanimglobaltimeCountglobalcurrentSelectglobalminuteLabelglobalsecondLabelglobalmillionLabelglobalanim_timelineglobalstartLabelglobalcurrentValuegloballvglInitializediflvglInitialized==False: importdisplay_driverlvglInitialized=Trueprint("Enter count down") timeCount= [value1, value2, value3, value4] # init scrscr=lv.obj() win=lv.obj(scr) win.set_size(scr.get_width(), scr.get_height()) win.set_style_border_opa(0, 0) win.set_style_radius(0, 0) win.set_style_bg_color(lv.color_black(), 0) win.clear_flag(lv.obj.FLAG.SCROLLABLE) isStarted=FalsecurrentSelect=0# count downfunc_col_dsc= [40, 5, 30, 5, 20, lv.GRID_TEMPLATE.LAST] func_row_dsc= [30, lv.GRID_TEMPLATE.LAST] timeContainer=lv.obj(win) timeContainer.set_style_bg_opa(0, 0) timeContainer.set_style_border_opa(0, 0) timeContainer.set_layout(lv.LAYOUT_GRID.value) timeContainer.set_style_grid_column_dsc_array(func_col_dsc, 0) timeContainer.set_style_grid_row_dsc_array(func_row_dsc, 0) timeContainer.set_grid_align(lv.GRID_ALIGN.SPACE_BETWEEN, lv.GRID_ALIGN.SPACE_BETWEEN) timeContainer.set_style_pad_all(0, 0) timeContainer.set_size(240, 70) timeContainer.center() minuteLabel=lv.label(timeContainer) minuteLabel.set_style_text_font(lv.font_montserrat_48, 0) minuteLabel.set_style_text_color(lv.color_white(), 0) minuteLabel.set_grid_cell(lv.GRID_ALIGN.START, 0, 1, lv.GRID_ALIGN.CENTER, 0, 1) signLabel=lv.label(timeContainer) signLabel.set_style_text_font(lv.font_montserrat_48, 0) signLabel.set_style_text_color(lv.color_white(), 0) signLabel.set_text(":") signLabel.set_grid_cell(lv.GRID_ALIGN.CENTER, 1, 1, lv.GRID_ALIGN.CENTER, 0, 1) secondLabel=lv.label(timeContainer) secondLabel.set_style_text_font(lv.font_montserrat_48, 0) secondLabel.set_style_text_color(lv.color_white(), 0) secondLabel.set_grid_cell(lv.GRID_ALIGN.CENTER, 2, 1, lv.GRID_ALIGN.CENTER, 0, 1) signLabel=lv.label(timeContainer) signLabel.set_style_text_font(lv.font_montserrat_48, 0) signLabel.set_style_text_color(lv.color_white(), 0) signLabel.set_text(":") signLabel.set_grid_cell(lv.GRID_ALIGN.CENTER, 3, 1, lv.GRID_ALIGN.CENTER, 0, 1) millionLabel=lv.label(timeContainer) millionLabel.set_style_text_font(lv.font_montserrat_36, 0) millionLabel.set_style_text_color(lv.color_white(), 0) millionLabel.set_grid_cell(lv.GRID_ALIGN.END, 4, 1, lv.GRID_ALIGN.START, 0, 1) setLabelValue(timeCount[currentSelect] *60*50) startButton=lv.btn(win) startButton.align(lv.ALIGN.CENTER, 0, 40) startButton.set_size(126, 54) startButton.set_style_radius(45, lv.PART.MAIN) startButton.set_style_shadow_opa(0, 0) startButton.set_style_bg_color(lv.color_make(0xFF, 0xA8, 0x48), lv.PART.MAIN) startButton.align(lv.ALIGN.BOTTOM_LEFT, 12, -12) startButton.add_event_cb(start_button_event_handler, lv.EVENT.CLICKED, None) startLabel=lv.label(startButton) startLabel.set_text("START") startLabel.set_style_text_color(lv.color_black(), 0) startLabel.set_style_text_font(lv.font_montserrat_20, 0) startLabel.center() resetButton=lv.btn(win) resetButton.align(lv.ALIGN.CENTER, 0, 40) resetButton.set_size(126, 54) resetButton.set_style_radius(45, lv.PART.MAIN) resetButton.set_style_shadow_opa(0, 0) resetButton.set_style_bg_color(lv.color_white(), lv.PART.MAIN) resetButton.align(lv.ALIGN.BOTTOM_RIGHT, -12, -12) resetButton.add_event_cb(reset_button_event_handler, lv.EVENT.CLICKED, None) resetLabel=lv.label(resetButton) resetLabel.set_text("REST") resetLabel.set_style_text_color(lv.color_black(), 0) resetLabel.set_style_text_font(lv.font_montserrat_20, 0) resetLabel.center() # select timecol_dsc= [75, 75, 75, 75, lv.GRID_TEMPLATE.LAST] row_dsc= [60, 80, 60, lv.GRID_TEMPLATE.LAST] funcContainer=lv.obj(win) funcContainer.set_layout(lv.LAYOUT_GRID.value) funcContainer.set_style_bg_opa(0, 0) funcContainer.set_style_border_opa(0, 0) funcContainer.set_style_grid_column_dsc_array(col_dsc, 0) funcContainer.set_style_grid_row_dsc_array(row_dsc, 0) funcContainer.set_grid_align(lv.GRID_ALIGN.SPACE_BETWEEN, lv.GRID_ALIGN.SPACE_BETWEEN) funcContainer.set_size(300, 90) funcContainer.set_style_align(lv.ALIGN.TOP_MID, 0) maxMillionSecond=timeCount[0] *60*50arc[0] =lv.arc(funcContainer) arc[0].set_style_arc_color(lv.color_white(), lv.PART.INDICATOR) arc[0].set_style_arc_color(lv.color_make(0x33, 0x33, 0x33), lv.PART.MAIN) arc[0].set_range(0, maxMillionSecond) arc[0].set_size(55, 55) arc[0].set_rotation(90) arc[0].set_bg_angles(0, 360) arc[0].remove_style(None, lv.PART.KNOB) arc[0].set_value(maxMillionSecond) arc[0].set_style_arc_width(8, lv.PART.INDICATOR) arc[0].set_style_arc_width(8, lv.PART.MAIN) arc[0].set_grid_cell(lv.GRID_ALIGN.CENTER, 0, 1, lv.GRID_ALIGN.CENTER, 0, 1) arc[0].clear_flag(lv.obj.FLAG.CLICKABLE) totalTime=lv.label(funcContainer) totalTime.set_text(str(timeCount[0])) totalTime.set_style_text_font(lv.font_montserrat_18, 0) totalTime.set_style_text_color(lv.color_white(), 0) totalTime.set_grid_cell(lv.GRID_ALIGN.CENTER, 0, 1, lv.GRID_ALIGN.CENTER, 0, 1) totalTime.add_flag(lv.obj.FLAG.CLICKABLE) totalTime.add_event_cb(lambdae: arc_event_handler(e, 0), lv.EVENT.CLICKED, None) totalTime.set_ext_click_area(30) anim[0] =lv.anim_t() anim[0].init() anim[0].set_var(arc[0]) anim[0].set_time(maxMillionSecond*20) anim[0].set_values(maxMillionSecond, 0) anim[0].set_custom_exec_cb(lambdaa1, val: set_time_value(arc[0], val)) anim_timeline=lv.anim_timeline_create() lv.anim_timeline_add(anim_timeline, 0, anim[0]) arc[1] =lv.arc(funcContainer) arc[1].set_style_arc_color(lv.color_white(), lv.PART.INDICATOR) arc[1].set_style_arc_color(lv.color_make(0x33, 0x33, 0x33), lv.PART.MAIN) arc[1].set_range(0, maxMillionSecond) arc[1].set_size(55, 55) arc[1].set_rotation(90) arc[1].set_bg_angles(0, 360) arc[1].remove_style(None, lv.PART.KNOB) arc[1].set_value(0) arc[1].set_style_arc_width(2, lv.PART.INDICATOR) arc[1].set_style_arc_width(2, lv.PART.MAIN) arc[1].set_grid_cell(lv.GRID_ALIGN.CENTER, 1, 1, lv.GRID_ALIGN.CENTER, 0, 1) arc[1].clear_flag(lv.obj.FLAG.CLICKABLE) totalTime=lv.label(funcContainer) totalTime.set_text(str(timeCount[1])) totalTime.set_style_text_font(lv.font_montserrat_18, 0) totalTime.set_style_text_color(lv.color_white(), 0) totalTime.set_grid_cell(lv.GRID_ALIGN.CENTER, 1, 1, lv.GRID_ALIGN.CENTER, 0, 1) totalTime.add_flag(lv.obj.FLAG.CLICKABLE) totalTime.add_event_cb(lambdae: arc_event_handler(e, 1), lv.EVENT.CLICKED, None) totalTime.set_ext_click_area(30) arc[2] =lv.arc(funcContainer) arc[2].set_style_arc_color(lv.color_white(), lv.PART.INDICATOR) arc[2].set_style_arc_color(lv.color_make(0x33, 0x33, 0x33), lv.PART.MAIN) arc[2].set_range(0, maxMillionSecond) arc[2].set_size(55, 55) arc[2].set_rotation(90) arc[2].set_bg_angles(0, 360) arc[2].remove_style(None, lv.PART.KNOB) arc[2].set_value(0) arc[2].set_style_arc_width(2, lv.PART.INDICATOR) arc[2].set_style_arc_width(2, lv.PART.MAIN) arc[2].set_grid_cell(lv.GRID_ALIGN.CENTER, 2, 1, lv.GRID_ALIGN.CENTER, 0, 1) arc[2].clear_flag(lv.obj.FLAG.CLICKABLE) totalTime=lv.label(funcContainer) totalTime.set_text(str(timeCount[2])) totalTime.set_style_text_font(lv.font_montserrat_18, 0) totalTime.set_style_text_color(lv.color_white(), 0) totalTime.set_grid_cell(lv.GRID_ALIGN.CENTER, 2, 1, lv.GRID_ALIGN.CENTER, 0, 1) totalTime.add_flag(lv.obj.FLAG.CLICKABLE) totalTime.add_event_cb(lambdae: arc_event_handler(e, 2), lv.EVENT.CLICKED, None) totalTime.set_ext_click_area(30) arc[3] =lv.arc(funcContainer) arc[3].set_style_arc_color(lv.color_white(), lv.PART.INDICATOR) arc[3].set_style_arc_color(lv.color_make(0x33, 0x33, 0x33), lv.PART.MAIN) arc[3].set_range(0, maxMillionSecond) arc[3].set_size(55, 55) arc[3].set_rotation(90) arc[3].set_bg_angles(0, 360) arc[3].remove_style(None, lv.PART.KNOB) arc[3].set_value(0) arc[3].set_style_arc_width(2, lv.PART.INDICATOR) arc[3].set_style_arc_width(2, lv.PART.MAIN) arc[3].set_grid_cell(lv.GRID_ALIGN.CENTER, 3, 1, lv.GRID_ALIGN.CENTER, 0, 1) arc[3].clear_flag(lv.obj.FLAG.CLICKABLE) totalTime=lv.label(funcContainer) totalTime.set_text(str(timeCount[3])) totalTime.set_style_text_font(lv.font_montserrat_18, 0) totalTime.set_style_text_color(lv.color_white(), 0) totalTime.set_grid_cell(lv.GRID_ALIGN.CENTER, 3, 1, lv.GRID_ALIGN.CENTER, 0, 1) totalTime.add_flag(lv.obj.FLAG.CLICKABLE) totalTime.add_event_cb(lambdae: arc_event_handler(e, 3), lv.EVENT.CLICKED, None) totalTime.set_ext_click_area(30) # load contentlv.scr_load(scr)
运行示例
安装SquareLine Studio
SquareLine Studio 是一个可视化工具,可使用 LVGL 图形库开发 UI,且支持多个平台,如 MacOS、Windows 和 Linux。在该工具中,我们通过拖放就可以在屏幕上添加和移动小控件,图像和字体的处理也变得十分简单。
安装完成后的界面
30天无限制试用,注册个人账号可免费试用。
配置显示支持
屏幕类型
需要修改项目的默认配置,屏幕的宽度、高度、色深。
M5 Core2 的宽度应当设置为320 高度240 色深16bit
输出类型
语言切换为MicroPython。
LVGL版本需要选择8.1,否则会发生错误。
拖拽UI组件
左侧为图层和UI组件选择,也可以在Assets中加载自己设计的UI组件。
定义事件
可以在EVENT处添加事件,对应元素响应、显示或消失、或者执行一个函数。这个函数的内容可以在导出后的源文件中编辑。
编辑器提供的示例Thermostat,可以作为参考。
导出MicroPython文件
以下python文件会被创建
ui.py
UI 代码ui_helper.py
在ui.py
中加载ui_images.py
UI图像ui_font_*.bin
字体ui_events.py
自定义的事件函数
配置MicroPython UI
import lvgl
之后,调用初始化函数lv.init()
- 设置显示驱动
import ui
效果展示
制作了一个Wi-Fi搜索,并提供虚拟键盘输入密码配网。总共有4个界面,这是第二个。
为了方便调试,显示了帧率和内存、CPU占用率。
写在最后
尝试了很多次才成功加载并与HaaS Python兼容,虽然HaaS Python兼容MicroPython,但在一些地方还是有不同定义,需要耐心修改。由于HaaS Python和SquareLine Studio都出现了更新,新建项目遇到的困难减少了很多。
在LVGL的官网看到了久违的AliOS的Logo,SquareLine Studio也是LVGL官方推出的编辑器。希望后续HaaS能够直接整合工作流,方便UI开发。不需要再像我这样折腾了。写于2022年底。