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

目录
相关文章
|
27天前
|
存储 数据采集 人工智能
Python编程入门:从零基础到实战应用
本文是一篇面向初学者的Python编程教程,旨在帮助读者从零开始学习Python编程语言。文章首先介绍了Python的基本概念和特点,然后通过一个简单的例子展示了如何编写Python代码。接下来,文章详细介绍了Python的数据类型、变量、运算符、控制结构、函数等基本语法知识。最后,文章通过一个实战项目——制作一个简单的计算器程序,帮助读者巩固所学知识并提高编程技能。
|
7天前
|
数据采集 人工智能 自然语言处理
Midscene.js:AI 驱动的 UI 自动化测试框架,支持自然语言交互,生成可视化报告
Midscene.js 是一款基于 AI 技术的 UI 自动化测试框架,通过自然语言交互简化测试流程,支持动作执行、数据查询和页面断言,提供可视化报告,适用于多种应用场景。
90 1
Midscene.js:AI 驱动的 UI 自动化测试框架,支持自然语言交互,生成可视化报告
|
27天前
|
小程序 开发者 Python
探索Python编程:从基础到实战
本文将引导你走进Python编程的世界,从基础语法开始,逐步深入到实战项目。我们将一起探讨如何在编程中发挥创意,解决问题,并分享一些实用的技巧和心得。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你提供有价值的参考。让我们一起开启Python编程的探索之旅吧!
46 10
|
26天前
|
数据可视化 编译器 Python
Manim:数学可视化的强大工具 | python小知识
Manim(Manim Community Edition)是由3Blue1Brown的Grant Sanderson开发的数学动画引擎,专为数学和科学可视化设计。它结合了Python的灵活性与LaTeX的精确性,支持多领域的内容展示,能生成清晰、精确的数学动画,广泛应用于教育视频制作。安装简单,入门容易,适合教育工作者和编程爱好者使用。
151 7
|
2月前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
119 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
2月前
|
存储 数据可视化 数据挖掘
使用Python进行数据分析和可视化
本文将引导你理解如何使用Python进行数据分析和可视化。我们将从基础的数据结构开始,逐步深入到数据处理和分析的方法,最后通过实际的代码示例来展示如何创建直观的数据可视化。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。让我们一起探索数据的世界,发现隐藏在数字背后的故事!
|
2月前
|
算法 Unix 数据库
Python编程入门:从基础到实战
本篇文章将带你进入Python编程的奇妙世界。我们将从最基础的概念开始,逐步深入,最后通过一个实际的项目案例,让你真正体验到Python编程的乐趣和实用性。无论你是编程新手,还是有一定基础的开发者,这篇文章都将为你提供有价值的信息和知识。让我们一起探索Python的世界吧!
|
2月前
|
并行计算 调度 开发者
探索Python中的异步编程:从基础到实战
在Python的世界里,异步编程是一种让程序运行更加高效、响应更快的技术。本文不仅会介绍异步编程的基本概念和原理,还将通过具体代码示例展示如何在Python中实现异步操作。无论你是初学者还是有经验的开发者,都能从中获益,了解如何运用这一技术优化你的项目。
|
2月前
|
数据处理 Python
探索Python中的异步编程:从基础到实战
在Python的世界中,“速度”不仅是赛车手的追求。本文将带你领略Python异步编程的魅力,从原理到实践,我们不单单是看代码,更通过实例感受它的威力。你将学会如何用更少的服务器资源做更多的事,就像是在厨房里同时烹饪多道菜而不让任何一道烧焦。准备好了吗?让我们开始这场技术烹饪之旅。
|
2月前
|
机器学习/深度学习 数据可视化 数据挖掘
使用Python进行数据分析和可视化
【10月更文挑战第42天】本文将介绍如何使用Python进行数据分析和可视化。我们将从数据导入、清洗、探索性分析、建模预测,以及结果的可视化展示等方面展开讲解。通过这篇文章,你将了解到Python在数据处理和分析中的强大功能,以及如何利用这些工具来提升你的工作效率。