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年底。

目录
相关文章
|
14天前
|
数据采集 JSON 算法
Python爬虫——基于JWT的模拟登录爬取实战
Python爬虫——基于JWT的模拟登录爬取实战
37 1
Python爬虫——基于JWT的模拟登录爬取实战
|
11天前
|
机器学习/深度学习 数据可视化 Python
Python实用记录(三):通过netron可视化模型
使用Netron工具在Python中可视化神经网络模型,包括安装Netron、创建文件和运行文件的步骤。
16 2
Python实用记录(三):通过netron可视化模型
|
7天前
|
机器学习/深度学习 数据挖掘 Serverless
手把手教你全面评估机器学习模型性能:从选择正确评价指标到使用Python与Scikit-learn进行实战演练的详细指南
【10月更文挑战第10天】评估机器学习模型性能是开发流程的关键,涉及准确性、可解释性、运行速度等多方面考量。不同任务(如分类、回归)采用不同评价指标,如准确率、F1分数、MSE等。示例代码展示了使用Scikit-learn库评估逻辑回归模型的过程,包括数据准备、模型训练、性能评估及交叉验证。
20 1
|
8天前
|
调度 开发者 Python
探索Python中的异步编程:从基础到实战
【10月更文挑战第9天】在Python的世界中,异步编程是一个让开发者既能提升应用性能又能保持代码可读性的强大工具。本文将带你一探究竟,从理解异步编程的基本概念开始,到深入剖析其背后的原理,再到通过实际代码示例掌握其应用技巧。无论你是异步编程的新手还是希望深化理解的老手,这篇文章都将为你打开一扇新的大门,让你的项目因使用异步而更加高效和强大。
|
10天前
|
数据采集 Web App开发 数据可视化
Python爬虫教程:Selenium可视化爬虫的快速入门
Python爬虫教程:Selenium可视化爬虫的快速入门
|
13天前
|
存储 人工智能 Java
Python编程入门:从基础到实战
【10月更文挑战第4天】本文旨在为初学者提供一个全面而深入的Python编程学习路径。我们将从Python的基本语法和概念开始,然后逐步深入到更复杂的主题,如数据结构、面向对象编程和异常处理等。最后,我们将通过一些实际的项目案例,帮助读者将理论知识应用到实践中去。无论你是编程新手,还是有一定经验的开发者,都可以在这篇文章中找到适合自己的学习内容。让我们一起开启Python编程的学习之旅吧!
|
15天前
|
人工智能 数据挖掘 Serverless
探索Python编程:从基础到实战
【10月更文挑战第2天】本文将带你走进Python的世界,了解它的基本语法、数据结构、函数等核心概念,并通过实例演示如何应用这些知识解决实际问题。无论你是编程新手还是有一定经验的开发者,都能在这篇文章中找到有价值的内容。让我们一起开启Python编程之旅吧!
|
13天前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
21 3
|
15天前
|
存储 分布式计算 资源调度
大数据-04-Hadoop集群 集群群起 NameNode/DataNode启动 3台公网云 ResourceManager Yarn HDFS 集群启动 UI可视化查看 YarnUI(一)
大数据-04-Hadoop集群 集群群起 NameNode/DataNode启动 3台公网云 ResourceManager Yarn HDFS 集群启动 UI可视化查看 YarnUI(一)
60 5
|
15天前
|
资源调度 数据可视化 大数据
大数据-04-Hadoop集群 集群群起 NameNode/DataNode启动 3台公网云 ResourceManager Yarn HDFS 集群启动 UI可视化查看 YarnUI(二)
大数据-04-Hadoop集群 集群群起 NameNode/DataNode启动 3台公网云 ResourceManager Yarn HDFS 集群启动 UI可视化查看 YarnUI(二)
28 4