UI Lite for Python 可视化开发实战

简介: HaaS Python的目标是帮助中小开发者聚焦业务,实现设备安全上云,加速设备创新迭代,真正做到“Python也可以轻松开发智能设备”。HaaS Python 轻应用在2022年终于开始支持ESP32的开发板了,解决了开发板向生成转换时的顾虑。Python轻应用通过LVGL 8.1整合 UI得到了提升。UI Lite for Python以LVGL 8.1为基础进行打造,阿里云的官方文档提供了一个官方文档的链接。也提供了各种UI组件的展示,用代码进行 UI 设计让我们的同事非常头大。于是我找到了可视化的UI开发工具SquareLine Studio。

安装HaaS开发环境

安装VSCode

如果网络条件允许可以直接在VSCode官网下载。

VSCode启动界面.jpg

安装HaaS Studio

HaaS Studio setup.jpg

完成后会提示激活Aos插件,确认即可。

启用HaaS进入HaaS Studio启动界面

开发环境安装完成.jpg

下载ESP32固件并烧录

采用M5 Core2作为开发板,内置屏幕方便设置,该版本是单独的固件,区别是ESP32-8M。

image.png

M5 CORE2是ESP32平台,并非C3或S3。需要注意区分。

固件烧录工具.jpg

烧录完成后,准备工作结束。

关于HaaS UI

在搜索HaaS Studio插件的时候,HaaS UI无法运行在ESP32单片机上,对于硬件需求很高。是基于Vue.js开发的一套物联网UI。

ESP32不要安装.jpg

本文不涉及相关内容,为防止迷惑,特此说明。


代码写UI Lite for Python

创建ESP32轻应用工程

image.png

示例程序中有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)

运行示例

image.png

安装SquareLine Studio


SquareLine Studio 是一个可视化工具,可使用 LVGL 图形库开发 UI,且支持多个平台,如 MacOS、Windows 和 Linux。在该工具中,我们通过拖放就可以在屏幕上添加和移动小控件,图像和字体的处理也变得十分简单。

image.png

安装完成后的界面

image.png

30天无限制试用,注册个人账号可免费试用。


配置显示支持

屏幕类型

image.png

需要修改项目的默认配置,屏幕的宽度、高度、色深。

M5 Core2 的宽度应当设置为320 高度240 色深16bit


输出类型

语言切换为MicroPython。

LVGL版本需要选择8.1,否则会发生错误。


拖拽UI组件

image.png

左侧为图层和UI组件选择,也可以在Assets中加载自己设计的UI组件。


定义事件

image.png

可以在EVENT处添加事件,对应元素响应、显示或消失、或者执行一个函数。这个函数的内容可以在导出后的源文件中编辑。


image.png

编辑器提供的示例Thermostat,可以作为参考。


导出MicroPython文件

以下python文件会被创建

  • ui.py UI 代码
  • ui_helper.pyui.py 中加载
  • ui_images.py UI图像
  • ui_font_*.bin 字体
  • ui_events.py 自定义的事件函数

配置MicroPython UI

  • import lvgl 之后,调用初始化函数 lv.init()
  • 设置显示驱动
  • import ui


效果展示


M5Core2.jpg

制作了一个Wi-Fi搜索,并提供虚拟键盘输入密码配网。总共有4个界面,这是第二个。

为了方便调试,显示了帧率和内存、CPU占用率。


写在最后

尝试了很多次才成功加载并与HaaS Python兼容,虽然HaaS Python兼容MicroPython,但在一些地方还是有不同定义,需要耐心修改。由于HaaS Python和SquareLine Studio都出现了更新,新建项目遇到的困难减少了很多。

在LVGL的官网看到了久违的AliOS的Logo,SquareLine Studio也是LVGL官方推出的编辑器。希望后续HaaS能够直接整合工作流,方便UI开发。不需要再像我这样折腾了。写于2022年底。

目录
相关文章
|
5天前
|
前端开发
Element UI 【实战】纯前端对表格数据进行增删改查(内含弹窗表单、数据校验、时间日期格式)
Element UI 【实战】纯前端对表格数据进行增删改查(内含弹窗表单、数据校验、时间日期格式)
27 6
|
1天前
|
数据采集 机器学习/深度学习 数据可视化
关于Python数据分析项目的简要概述:从CSV加载数据,执行数据预处理,进行数据探索,选择线性回归模型进行训练,评估模型性能并优化,最后结果解释与可视化。
【7月更文挑战第5天】这是一个关于Python数据分析项目的简要概述:从CSV加载数据,执行数据预处理(填充缺失值,处理异常值),进行数据探索(可视化和统计分析),选择线性回归模型进行训练,评估模型性能并优化,最后结果解释与可视化。此案例展示了数据科学的典型流程。
12 2
|
3天前
|
JavaScript
vue + element UI【实战】音乐播放器/语音条(内含音频的加载、控制,事件监听,信息获取,手机网页阴影的去除等技巧)
vue + element UI【实战】音乐播放器/语音条(内含音频的加载、控制,事件监听,信息获取,手机网页阴影的去除等技巧)
7 1
|
3天前
|
运维 知识图谱 Python
专为运维工程师设计!阿里藏经阁出品的Python实战手册被我搞来了
Python 可能是极少数既简单又强大的编程语言中的一种。更重要的是,用它来编程是非常快乐的事。 今天给小伙伴们分享的是阿里“藏经阁”出品的专门给运维工程师设计的Python实战手册
|
4天前
Element UI【实战范例】下拉选择 el-select 的 change 事件传入选中值+自定义参数
Element UI【实战范例】下拉选择 el-select 的 change 事件传入选中值+自定义参数
11 1
|
8天前
|
数据采集 JSON 数据可视化
【Python实战】Python对中国500强排行榜数据进行可视化分析
【Python实战】Python对中国500强排行榜数据进行可视化分析
|
12天前
|
SQL 关系型数据库 数据库连接
Python连接线上数据库的实战指南
Python连接线上数据库的实战指南
17 1
|
1天前
|
机器学习/深度学习 数据可视化 搜索推荐
Python在社交媒体分析中扮演关键角色,借助Pandas、NumPy、Matplotlib等工具处理、可视化数据及进行机器学习。
【7月更文挑战第5天】Python在社交媒体分析中扮演关键角色,借助Pandas、NumPy、Matplotlib等工具处理、可视化数据及进行机器学习。流程包括数据获取、预处理、探索、模型选择、评估与优化,以及结果可视化。示例展示了用户行为、话题趋势和用户画像分析。Python的丰富生态使得社交媒体洞察变得高效。通过学习和实践,可以提升社交媒体分析能力。
8 0
|
2天前
Element UI 【表格合计】el-table 实战范例 -- 添加单位,自定义计算逻辑
Element UI 【表格合计】el-table 实战范例 -- 添加单位,自定义计算逻辑
6 0
|
3天前
|
JavaScript BI UED
vue + element UI【实战】打字闯关(含按键监听、按键音效、字符匹配、动态样式、结果判定、数据统计、音效获取和剪辑等实用技巧)
vue + element UI【实战】打字闯关(含按键监听、按键音效、字符匹配、动态样式、结果判定、数据统计、音效获取和剪辑等实用技巧)
5 0

相关实验场景

更多