跳转到内容

LVGL:技巧与窍门

这里有一些在 ESPHome 中使用 LVGL 可以实现的各种有趣功能的配方。

NOTE

以下许多示例调用 Home Assistant 中的服务动作;然而,Home Assistant 默认不允许此类动作调用。对于每个将调用动作的 ESPHome 设备,您必须在 Home Assistant 中显式启用此设置。这可以在设备最初被采纳时完成,或通过使用 ESPHome 集成”设备”列表中的”配置”选项完成。

NOTE

以下示例假设您已正确设置 LVGL 及其显示屏和输入设备,并且您具备在 ESPHome 中设置各种组件的知识。某些示例使用绝对定位,适用于尺寸为 240x320px 的屏幕;如果您的显示屏尺寸不同,您需要调整它们以获得预期结果。

集成 LVGL switch 小部件与开关或灯的最简单方法是使用自动化

light:
- platform: ...
id: local_light
name: '本地灯'
on_state:
- lvgl.widget.update:
id: light_switch
state:
checked: !lambda return id(local_light).current_values.is_on();
lvgl:
...
pages:
- id: main_page
widgets:
- switch:
align: CENTER
id: light_switch
on_click:
light.toggle: local_light

如果您想从可选中(切换)的button控制 Home Assistant 中显示为实体的远程灯,首先需要将灯状态导入 ESPHome,然后通过动作调用来控制它:

binary_sensor:
- platform: homeassistant
id: remote_light
entity_id: light.remote_light
publish_initial_state: true
on_state:
then:
lvgl.widget.update:
id: light_btn
state:
checked: !lambda return x;
lvgl:
...
pages:
- id: room_page
widgets:
- button:
id: light_btn
align: CENTER
width: 100
height: 70
checkable: true
widgets:
- label:
align: CENTER
text: '远程灯'
on_click:
- homeassistant.action:
action: light.toggle
data:
entity_id: light.remote_light

您可以使用 sliderarc 来控制可调光灯的亮度。

我们可以使用传感器获取灯的当前亮度,它作为实体的属性存储在 Home Assistant 中,是一个介于 0(最小)和 255(最大)之间的整数值。相应地设置滑块的 min_valuemax_value 很方便。

sensor:
- platform: homeassistant
id: light_brightness
entity_id: light.your_dimmer
attribute: brightness
on_value:
- lvgl.slider.update:
id: dimmer_slider
value: !lambda return x;
lvgl:
...
pages:
- id: room_page
widgets:
- slider:
id: dimmer_slider
x: 20
y: 50
width: 30
height: 220
pad_all: 8
min_value: 0
max_value: 255
on_release:
- homeassistant.action:
action: light.turn_on
data:
entity_id: light.your_dimmer
brightness: !lambda return int(x);

请注意,Home Assistant 期望 light.turn_on 动作调用的 brightness 参数是一个整数,由于 ESPHome 使用浮点数,x 需要转换。

这也适用于像 fan.set_percentagevalve.set_valve_position 这样的动作调用;唯一的区别是 max_value 需要是 100

同样地,您可以使用 sliderarc 来控制媒体播放器的音量级别,它使用浮点值。

通过传感器我们获取媒体播放器的当前音量级别,它作为实体的属性存储在 Home Assistant 中,是一个介于 0(最小)和 1(最大)之间的浮点值。由于 LVGL 只处理整数,将滑块的可能值设置为介于 0100 之间很方便。因此需要来回转换,意味着当我们从 Home Assistant 读取值时需要乘以 100,当我们通过动作调用设置音量时需要除以 100

sensor:
- platform: homeassistant
id: media_player_volume
entity_id: media_player.your_room
attribute: volume_level
on_value:
- lvgl.slider.update:
id: slider_media_player
value: !lambda return (x * 100);
lvgl:
...
pages:
- id: mediaplayer_page
widgets:
- slider:
id: slider_media_player
x: 60
y: 50
width: 30
height: 220
pad_all: 8
min_value: 0
max_value: 100
adv_hittest: true
on_value:
- homeassistant.action:
action: media_player.volume_set
data:
entity_id: media_player.your_room
volume_level: !lambda return (x / 100);

adv_hittest 选项确保意外触摸屏幕不会导致突然的音量变化(更多详情请参阅 slider 文档)。

NOTE

请记住,on_value 在拖动滑块时会连续触发。这通常会对性能产生负面影响。例如,您不应使用此触发器通过 Modbus 设置热泵的目标温度,或设置电动窗帘的位置,因为这很可能导致故障。为了缓解这个问题,考虑使用像 on_release 这样的通用小部件触发器,在交互完成后获取一次 x 变量。

类似于 Home Assistant 在能源仪表板中显示的仪表,可以使用 meterlabel 小部件实现:

这里的技巧是有一个父级 obj,包含其他小部件作为子级。我们在中间放置一个 meter,它由指示器 line 和两个 arc 小部件组成。我们在其上面使用另一个较小的 obj 来隐藏指示器的中心部分,并放置一些 label 小部件来显示数字信息:

sensor:
- platform: ...
id: values_between_-10_and_10
on_value:
- lvgl.indicator.update:
id: val_needle
value: !lambda return x;
- lvgl.label.update:
id: val_text
text:
format: "%.0f"
args: [ 'x' ]
lvgl:
...
pages:
- id: gauge_page
widgets:
- obj:
height: 240
width: 240
align: CENTER
bg_color: 0xFFFFFF
border_width: 0
pad_all: 4
widgets:
- meter:
height: 100%
width: 100%
border_width: 0
bg_opa: TRANSP
align: CENTER
scales:
- range_from: -10
range_to: 10
angle_range: 180 # 将总角度设置为 180 = 从左中开始到右中结束
ticks:
count: 0
indicators:
- line:
id: val_needle
width: 8
r_mod: 12 # 通过此值设置线长度与刻度默认半径的差异
value: -2
- arc: # 刻度背景的前半部分
color: 0xFF3000
r_mod: 10 # 与刻度默认半径的半径差异
width: 31
start_value: -10
end_value: 0
- arc: # 刻度背景的后半部分
color: 0x00FF00
r_mod: 10
width: 31
start_value: 0
end_value: 10
- obj: # 用于覆盖仪表指示线中间部分
height: 146
width: 146
radius: 73
align: CENTER
border_width: 0
bg_color: 0xFFFFFF
pad_all: 0
- label: # 仪表数字指示器
id: val_text
text_font: montserrat_48
align: CENTER
y: -5
text: "0"
- label: # 下限指示器
text_font: montserrat_18
align: CENTER
y: 8
x: -90
text: "-10"
- label: # 上限指示器
text_font: montserrat_18
align: CENTER
y: 8
x: 90
text: "+10"

TIP

用于隐藏仪表指示线中间部分的 objradius 等于 widthheight 的一半。这会产生一个圆形——实际上是一个带有超大圆角的正方形。

带有精确仪表的温度计也由 meter 小部件和使用 label 的数字显示组成:

每当传感器有新值时,我们更新指针指示器以及 label 中的文本。由于 LVGL 在 meter 刻度上只处理整数值,但传感器的值是 float,我们使用与上面示例相同的方法;我们将传感器的值乘以 10 并将此值提供给 meter。这本质上是两个重叠的刻度:一个根据乘后的值设置指针,另一个在 label 中显示传感器的原始值。

sensor:
- platform: ...
id: outdoor_temperature
on_value:
- lvgl.indicator.update:
id: temperature_needle
value: !lambda return x * 10;
- lvgl.label.update:
id: temperature_text
text:
format: "%.1f°C"
args: [ 'x' ]
lvgl:
...
pages:
- id: meter_page
widgets:
- meter:
align: CENTER
height: 180
width: 180
scales:
- range_from: -100 # 指针值的刻度
range_to: 400
angle_range: 240
rotation: 150
indicators:
- line:
id: temperature_needle
width: 2
color: 0xFF0000
r_mod: -4
- tick_style:
start_value: -10
end_value: 40
color_start: 0x0000bd
color_end: 0xbd0000
width: 1
- range_from: -10 # 值标签的刻度
range_to: 40
angle_range: 240
rotation: 150
ticks:
width: 1
count: 51
length: 10
color: 0x000000
major:
stride: 5
width: 2
length: 10
color: 0x404040
label_gap: 10
widgets:
- label:
id: temperature_text
text: "-.-°C"
align: CENTER
y: 45
- label:
text: "室外"
align: CENTER
y: 65

下面是相同的传感器配置,但使用带有渐变背景的半圆仪表,由大量刻度线绘制:

如果您更改小部件的尺寸,要获得均匀的渐变,请确保相应地增加或减少刻度数量。

lvgl:
...
pages:
- id: meter_page
widgets:
- obj:
height: 240
width: 240
align: CENTER
y: -18
bg_color: 0xFFFFFF
border_width: 0
pad_all: 14
widgets:
- meter:
height: 100%
width: 100%
border_width: 0
align: CENTER
bg_opa: TRANSP
scales:
- range_from: -15
range_to: 35
angle_range: 180
ticks:
count: 70
width: 1
length: 31
indicators:
- tick_style:
start_value: -15
end_value: 35
color_start: 0x3399ff
color_end: 0xffcc66
- range_from: -150
range_to: 350
angle_range: 180
ticks:
count: 0
indicators:
- line:
id: temperature_needle
width: 8
r_mod: 2
value: -150
- obj: # 用于覆盖仪表指示线中间部分
height: 123
width: 123
radius: 73
align: CENTER
border_width: 0
pad_all: 0
bg_color: 0xFFFFFF
- label:
id: temperature_text
text: "--.-°C"
align: CENTER
y: -26
- label:
text: "室外"
align: CENTER
y: -6

TIP

您可以通过使用位图 image 指示器作为指针来省略用于隐藏仪表指示线中间部分的 obj,其中只有悬挂在刻度线上方的部分可见,其余部分是透明的。

spinbox 是控制恒温器的理想小部件:

首先我们从 Home Assistant 导入恒温组件的当前目标温度,并在它变化时更新 spinbox 的值。我们使用两个标有减号和加号的按钮来控制 spinbox,每当我们更改其值时,只需调用 Home Assistant 动作来设置恒温的新目标温度。

sensor:
- platform: homeassistant
id: room_thermostat
entity_id: climate.room_thermostat
attribute: temperature
on_value:
- lvgl.spinbox.update:
id: spinbox_id
value: !lambda return x;
lvgl:
...
pages:
- id: thermostat_control
widgets:
- obj:
align: BOTTOM_MID
y: -50
layout:
type: FLEX
flex_flow: ROW
flex_align_cross: CENTER
width: SIZE_CONTENT
height: SIZE_CONTENT
widgets:
- button:
id: spin_down
on_click:
- lvgl.spinbox.decrement: spinbox_id
widgets:
- label:
text: "-"
- spinbox:
id: spinbox_id
align: CENTER
text_align: CENTER
width: 50
range_from: 15
range_to: 35
selected_digit: 0
rollover: false
digits: 3
decimal_places: 1
on_value:
then:
- homeassistant.action:
action: climate.set_temperature
data:
temperature: !lambda return x;
entity_id: climate.room_thermostat
- button:
id: spin_up
on_click:
- lvgl.spinbox.increment: spinbox_id
widgets:
- label:
text: "+"

要为控制 Home Assistant 窗帘制作漂亮的用户界面,您可以使用 3 个按钮,它们也显示状态。

就像前面的示例一样,我们需要先获取窗帘的状态。我们将使用数字传感器获取窗帘的当前位置,使用文本传感器获取其当前移动状态。我们特别关注移动(openingclosing)状态,因为在此期间我们希望将中间的标签更改为显示 STOP。否则,此按钮标签将显示打开的实际百分比。此外,我们将根据窗帘是否完全打开或关闭来更改 UPDOWN 按钮上标签的不透明度。

sensor:
- platform: homeassistant
id: cover_myroom_pos
entity_id: cover.myroom
attribute: current_position
on_value:
- if:
condition:
lambda: |-
return x == 100;
then:
- lvgl.widget.update:
id: cov_up_myroom
text_opa: 60%
else:
- lvgl.widget.update:
id: cov_up_myroom
text_opa: 100%
- if:
condition:
lambda: |-
return x == 0;
then:
- lvgl.widget.update:
id: cov_down_myroom
text_opa: 60%
else:
- lvgl.widget.update:
id: cov_down_myroom
text_opa: 100%
text_sensor:
- platform: homeassistant
id: cover_myroom_state
entity_id: cover.myroom
on_value:
- if:
condition:
lambda: |-
return ((0 == x.compare(std::string{"opening"})) or (0 == x.compare(std::string{"closing"})));
then:
- lvgl.label.update:
id: cov_stop_myroom
text: "停止"
else:
- lvgl.label.update:
id: cov_stop_myroom
text:
format: "%.0f%%"
args: [ 'id(cover_myroom_pos).get_state()' ]
lvgl:
...
pages:
- id: room_page
widgets:
- label:
x: 10
y: 6
width: 70
text: "我的房间"
text_align: CENTER
- button:
x: 10
y: 30
width: 70
height: 68
widgets:
- label:
id: cov_up_myroom
align: CENTER
text: "\uF077"
on_press:
then:
- homeassistant.action:
action: cover.open
data:
entity_id: cover.myroom
- button:
x: 10
y: 103
width: 70
height: 68
widgets:
- label:
id: cov_stop_myroom
align: CENTER
text: 停止
on_press:
then:
- homeassistant.action:
action: cover.stop
data:
entity_id: cover.myroom
- button:
x: 10
y: 178
width: 70
height: 68
widgets:
- label:
id: cov_down_myroom
align: CENTER
text: "\uF078"
on_press:
then:
- homeassistant.action:
action: cover.close
data:
entity_id: cover.myroom

由于 LVGL 使用继承来在各个小部件之间应用样式,可以在顶层应用它们,仅在必要时进行修改。

在这个示例中,我们在主题中准备一组渐变样式,并在样式定义中进行一些修改,可以批量应用到所需的小部件。主题会自动应用,可以手动用样式定义覆盖(继续阅读以了解如何操作)。

lvgl:
...
theme:
label:
text_font: my_font # 将所有标签设置为使用您自定义定义的字体
button:
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
text_color: 0xFFFFFF
pressed: # 设置一些按钮在按下状态时颜色不同
bg_color: 0x006699
bg_grad_color: 0x00334d
checked: # 设置一些按钮在选中状态时颜色不同
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
text_color: 0xfff300
buttonmatrix:
bg_opa: TRANSP
border_color: 0x0077b3
border_width: 0
text_color: 0xFFFFFF
pad_all: 0
items: # 将所有按钮矩阵按钮设置为使用您自定义定义的样式和字体
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
text_color: 0xFFFFFF
text_font: my_font
pressed:
bg_color: 0x006699
bg_grad_color: 0x00334d
checked:
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
text_color: 0x005580
switch:
bg_color: 0xC0C0C0
bg_grad_color: 0xb0b0b0
bg_grad_dir: VER
bg_opa: COVER
checked:
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
bg_grad_dir: VER
bg_opa: COVER
knob:
bg_color: 0xFFFFFF
bg_grad_color: 0xC0C0C0
bg_grad_dir: VER
bg_opa: COVER
slider:
border_width: 1
border_opa: 15%
bg_color: 0xcccaca
bg_opa: 15%
indicator:
bg_color: 0x1d5f96
bg_grad_color: 0x03324A
bg_grad_dir: VER
bg_opa: COVER
knob:
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_color: 0x0077b3
border_width: 1
text_color: 0xFFFFFF
style_definitions:
- id: header_footer
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_opa: TRANSP
radius: 0
pad_all: 0
pad_row: 0
pad_column: 0
border_color: 0x0077b3
text_color: 0xFFFFFF
width: 100%
height: 30

请注意,样式定义也可以包含通用属性,如定位和尺寸。

如果使用多个页面,屏幕底部的导航栏可能很有用:

为了避免在每个页面上重复相同的小部件,有 top_layer,即所有页面之上的始终置顶透明页面。您放在此页面上的所有内容都将位于所有其他页面之上。

对于导航栏,我们可以使用 buttonmatrix。注意 header_footer 样式定义如何应用到小部件及其子对象,以及如何在主小部件上手动配置更多样式:

lvgl:
...
top_layer:
widgets:
- buttonmatrix:
align: bottom_mid
styles: header_footer
pad_all: 0
outline_width: 0
id: top_layer
items:
styles: header_footer
rows:
- buttons:
- id: page_prev
text: "\uF053"
on_press:
then:
lvgl.page.previous:
- id: page_home
text: "\uF015"
on_press:
then:
lvgl.page.show: main_page
- id: page_next
text: "\uF054"
on_press:
then:
lvgl.page.next:

要使此示例正确显示,请使用上面的主题和样式选项以及 LVGL 自己的库字体

顶层对于在所有页面上显示可见的状态图标很有用:

在下面的示例中,我们只在建立与 Home Assistant 的连接时显示图标:

api:
on_client_connected:
- if:
condition:
lambda: 'return (0 == client_info.find("Home Assistant "));'
then:
- lvgl.widget.show: lbl_hastatus
on_client_disconnected:
- if:
condition:
lambda: 'return (0 == client_info.find("Home Assistant "));'
then:
- lvgl.widget.hide: lbl_hastatus
lvgl:
...
top_layer:
widgets:
- label:
text: "\uF1EB"
id: lbl_hastatus
hidden: true
align: top_right
x: -2
y: 7
text_align: right
text_color: 0xFFFFFF

注意:

  • 小部件在启动时以隐藏状态开始,仅在 API 连接触发时显示。

每个页面可以有自己的标题栏:

要在状态图标后面放置标题栏,我们需要将其添加到每个页面,同时包含具有唯一标题的标签:

lvgl:
...
pages:
- id: main_page
widgets:
- obj:
align: TOP_MID
styles: header_footer
widgets:
- label:
text: "ESPHome LVGL 显示屏"
align: CENTER
text_align: CENTER
text_color: 0xFFFFFF
...
- id: second_page
widgets:
- obj:
align: TOP_MID
styles: header_footer
widgets:
- label:
text: "第二页"
align: CENTER
text_align: CENTER
text_color: 0xFFFFFF
...

要使此示例正常工作,请使用上面的主题和样式选项。

布局旨在自动定位小部件,无需指定坐标来定位每个小部件。这是简化包含许多小部件配置的好方法,因为它甚至允许您省略对齐选项。

此示例说明了一个控制三个窗帘的控制面板,由标签和离散按钮组成。虽然按钮矩阵也可能适用于此,但您可能仍然更喜欢功能齐全的独立按钮,因为它们提供更广泛的定制可能性,如窗帘状态和控制示例所示。这里我们使用 Flex 布局:

lvgl:
...
pages:
- id: room_page
widgets:
- obj: # 一个位置合适的容器对象,用于所有这些控件
align: CENTER
width: 240
height: 256
x: 4
y: 4
pad_all: 3
bg_opa: TRANSP
border_opa: TRANSP
layout: # 为子小部件启用 FLEX 布局
type: FLEX
flex_flow: COLUMN_WRAP # 小部件顺序从左上开始
flex_align_cross: CENTER # 它们应该居中
pad_row: 6
pad_column: 8
widgets:
- label:
text: ""
- button:
id: but_cov_up_east
width: 70 # 选择按钮尺寸,以便
height: 68 # 它们在流动时很好地填充列
widgets:
- label:
id: cov_up_east
align: CENTER
text: "\U000F005D" # mdi:arrow-up
- button:
id: but_cov_stop_east
width: 70
height: 68
widgets:
- label:
id: cov_stop_east
align: CENTER
text: "\U000F04DB" # mdi:stop
- button:
id: but_cov_down_east
width: 70
height: 68
widgets:
- label:
id: cov_down_east
align: CENTER
text: "\U000F0045" # mdi:arrow-down
- label:
text: ""
- button:
id: but_cov_up_south
width: 70
height: 68
widgets:
- label:
id: cov_up_south
align: CENTER
text: "\U000F005D"
- button:
id: but_cov_stop_south
width: 70
height: 68
widgets:
- label:
id: cov_stop_south
align: CENTER
text: "\U000F04DB"
- button:
id: but_cov_down_south
width: 70
height: 68
widgets:
- label:
id: cov_down_south
align: CENTER
text: "\U000F0045"
- label:
text: "西"
- button:
id: but_cov_up_west
width: 70
height: 68
widgets:
- label:
id: cov_up_west
align: CENTER
text: "\U000F005D"
- button:
id: but_cov_stop_west
width: 70
height: 68
widgets:
- label:
id: cov_stop_west
align: CENTER
text: "\U000F04DB"
- button:
id: but_cov_down_west
width: 70
height: 68
widgets:
- label:
id: cov_down_west
align: CENTER
text: "\U000F0045"

这节省了大量的手动计算小部件位置的工作,否则需要使用 xy 手动放置它们!您只需要确定小部件的通用宽度和高度,以便按您喜欢的方式在页面上分布。(下面的 MDI 图标文本 展示了如何使用自定义图标。)

还有更多!使用 Grid 布局,您不需要为小部件指定宽度和高度。您只需将空间划分为行和列;小部件可以自动调整大小以适应由这些行和列定义的单元格。上面的相同任务,在完全自动化的网格中,看起来像这样:

lvgl:
...
pages:
- id: room_page
widgets:
- obj: # 一个位置合适的容器对象,用于所有这些控件
align: CENTER
width: 240
height: 256
pad_all: 6
bg_opa: TRANSP
border_opa: TRANSP
layout: # 为子小部件启用 GRID 布局
type: GRID # 按比例分割行和列
grid_columns: [FR(1), FR(1), FR(1)] # 相等
grid_rows: [FR(10), FR(30), FR(30), FR(30)] # 类似百分比
pad_row: 6
pad_column: 8
widgets:
- label:
text: ""
grid_cell_column_pos: 0 # 将小部件放置在
grid_cell_row_pos: 0 # 相应的单元格中
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
- button:
id: but_cov_up_east
grid_cell_column_pos: 0
grid_cell_row_pos: 1
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
widgets:
- label:
id: cov_up_east
align: CENTER
text: "\U000F005D"
- button:
id: but_cov_stop_east
grid_cell_column_pos: 0
grid_cell_row_pos: 2
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
widgets:
- label:
id: cov_stop_east
align: CENTER
text: "\U000F04DB"
- button:
id: but_cov_down_east
grid_cell_column_pos: 0
grid_cell_row_pos: 3
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
widgets:
- label:
id: cov_down_east
align: CENTER
text: "\U000F0045"
- label:
text: ""
grid_cell_column_pos: 1
grid_cell_row_pos: 0
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
- button:
id: but_cov_up_south
grid_cell_column_pos: 1
grid_cell_row_pos: 1
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
widgets:
- label:
id: cov_up_south
align: CENTER
text: "\U000F005D"
- button:
id: but_cov_stop_south
grid_cell_column_pos: 1
grid_cell_row_pos: 2
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
widgets:
- label:
id: cov_stop_south
align: CENTER
text: "\U000F04DB"
- button:
id: but_cov_down_south
grid_cell_column_pos: 1
grid_cell_row_pos: 3
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
widgets:
- label:
id: cov_down_south
align: CENTER
text: "\U000F0045"
- label:
text: "西"
grid_cell_column_pos: 2
grid_cell_row_pos: 0
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
- button:
id: but_cov_up_west
grid_cell_column_pos: 2
grid_cell_row_pos: 1
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
widgets:
- label:
id: cov_up_west
align: CENTER
text: "\U000F005D"
- button:
id: but_cov_stop_west
grid_cell_column_pos: 2
grid_cell_row_pos: 2
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
widgets:
- label:
id: cov_stop_west
align: CENTER
text: "\U000F04DB"
- button:
id: but_cov_down_west
grid_cell_column_pos: 2
grid_cell_row_pos: 3
grid_cell_x_align: STRETCH
grid_cell_y_align: STRETCH
widgets:
- label:
id: cov_down_west
align: CENTER
text: "\U000F0045"

这里的巨大优势是,每当您需要添加例如新窗帘的额外按钮列时,您只需将其追加到 grid_columns 变量,并像上面一样添加相应的小部件。使用 STRETCH,它们的尺寸和位置将自动计算以填充单元格,而父级的 pad_allpad_rowpad_column 可以帮助它们之间的间距。请参阅本页面下方的 天气预报面板 以获取另一个依赖 Grid 的示例。

要显示一个带有旋转动画的启动图像,它会在几秒钟后或触摸屏幕后自动消失,您可以使用 顶层。技巧是将一个基础 obj 放置为全屏,子 image 小部件在中间,作为小部件列表的最后一项,这样它们绘制在所有其他小部件之上。要使其在启动后自动消失,您使用 ESPHome 的 on_boot 触发器:

esphome:
...
on_boot:
- delay: 5s
- lvgl.widget.hide: boot_screen
image:
- file: https://media.esphome.io/logo/logo.png
id: boot_logo
resize: 200x200
type: RGB565
transparency: alpha_channel
lvgl:
...
top_layer:
widgets:
... # 确保它是此列表中的最后一个:
- obj:
id: boot_screen
x: 0
y: 0
width: 100%
height: 100%
bg_color: 0xffffff
bg_opa: COVER
radius: 0
pad_all: 0
border_width: 0
widgets:
- image:
align: CENTER
src: boot_logo
y: -40
- spinner:
align: CENTER
y: 95
height: 50
width: 50
spin_time: 1s
arc_length: 60deg
arc_width: 8
indicator:
arc_color: 0x18bcf2
arc_width: 8
on_press:
- lvgl.widget.hide: boot_screen

ESPHome 的 字体渲染器 允许您为文本使用任何 OpenType/TrueType 字体文件。这非常灵活,因为您可以准备不同大小的各种字体集,每个都有不同数量的字形;这很重要,因为它可能有助于节省闪存空间。

一个例子是当您想在文本中内联使用一些 MDI 图标(类似于 LVGL 内置字体和符号共存的方式)。您可以使用您选择的字体;从 MDI 中选择您想要的符号/图标,并将它们混合在一个单一大小的集合中。

在下面的示例中,我们使用 RobotoCondensed-Regular 的默认字形集,并从 MDI 追加一些额外的符号。然后我们通过转义它们的码点来与文本一起显示这些内联:

font:
- file: "fonts/RobotoCondensed-Regular.ttf"
id: roboto_icons_42
size: 42
bpp: 4
extras:
- file: "fonts/materialdesignicons-webfont.ttf"
glyphs: [
"\U000F02D1", # mdi-heart
"\U000F05D4", # mdi-airplane-landing
]
lvgl:
...
pages:
- id: main_page
widgets:
- label:
text: "Just\U000f05d4here. Already\U000F02D1this."
align: CENTER
text_align: CENTER
text_font: roboto_icons_42

TIP

按照以下步骤选择您的 MDI 图标:

  • 要查找您的图标,请使用 Pictogrammers 网站。点击所需图标并记下其码点(下载选项附近的十六进制数字)。
  • 要获取包含所有图标的 TrueType 字体,请前往
  • Pictogrammers GitHub 仓库

-从最近的版本文件夹中,下载 materialdesignicons-webfont.ttf 文件并将其放在您的 ESPHome 配置目录下名为 fonts 的文件夹中(以匹配上面的示例)。

  • 要使用所需的图标,在复制的码点前加上 \U000。Unicode 字符转义序列必须以大写 \U 开头,并恰好有 8 个十六进制数字。
  • 要将转义序列转换为真实的字形,请确保将字符串用双引号括起来。

如果您将自定义字体配置为 LVGL 使用的 default_font,并且此字体不包含 FontAwesome 符号,您可能会观察到某些小部件无法正确显示;具体来说,checkbox 在选中时不会显示勾选标记。

要解决此问题,只需以所需大小导入仅勾选标记符号,并通过主题和样式定义将其应用到配置中的所有复选框:

font:
- file: 'fonts/FontAwesome5-Solid+Brands+Regular.woff'
id: fontawesome_checkmark
size: 18
bpp: 4
glyphs: [
"\uF00C", # 勾选标记,用于复选框
]
lvgl:
...
theme:
checkbox:
indicator:
checked:
text_font: fontawesome_checkmark

您当然可以简单地应用内置的 montserrat_ 包之一,但这对二进制大小没有好处——它会无用地包含整个字形集在闪存中。

图标的常见用例是状态显示。例如,可选中(切换)按钮将根据灯或开关的状态显示不同的图标。要在按钮上放置图标,您使用 label 小部件作为 button 的子级。由于主题和样式定义,着色已经可以不同,您可以为 checked 状态设置不同的颜色。此外,通过使用 text_sensor 从 Home Assistant 导入状态,我们不仅可以跟踪 on 状态,还可以跟踪 unavailableunknown 状态来为这些情况应用禁用样式

如果我们采用之前的远程灯按钮示例,我们可以像这样修改它:

font:
- file: "custom/materialdesignicons-webfont.ttf"
id: mdi_42
size: 42
bpp: 4
glyphs: [
"\U000F0335", # mdi-lightbulb
"\U000F0336", # mdi-lightbulb-outline
]
text_sensor:
- platform: homeassistant
id: ts_remote_light
entity_id: light.remote_light
on_value:
then:
- lvgl.widget.update:
id: btn_lightbulb
state:
checked: !lambda return (0 == x.compare(std::string{"on"}));
disabled: !lambda return ((0 == x.compare(std::string{"unavailable"})) or (0 == x.compare(std::string{"unknown"})));
- lvgl.label.update:
id: lbl_lightbulb
text: !lambda |-
static char buf[10];
std::string icon;
if (0 == x.compare(std::string{"on"})) {
icon = "\U000F0335";
} else {
icon = "\U000F0336";
}
snprintf(buf, sizeof(buf), "%s", icon.c_str());
return buf;
lvgl:
...
pages:
- id: room_page
widgets:
- button:
x: 110
y: 40
width: 90
height: 50
checkable: true
id: btn_lightbulb
widgets:
- label:
id: lbl_lightbulb
align: CENTER
text_font: mdi_42
text: "\U000F0336" # mdi-lightbulb-outline
on_short_click:
- homeassistant.action:
action: light.toggle
data:
entity_id: light.remote_light

另一个使用 MDI 图标的示例是以 10 个步骤显示电池百分比。我们需要一个包含对应不同电池百分比级别字形的字体,我们需要一个传感器将电池状态从 Home Assistant 导入为数值。我们使用 lambda 根据传感器值返回相应字形的码点:

font:
- file: "fonts/materialdesignicons-webfont.ttf"
id: battery_icons_20
size: 20
bpp: 4
glyphs: [
"\U000F007A", # mdi-battery-10
"\U000F007B", # mdi-battery-20
"\U000F007C", # mdi-battery-30
"\U000F007D", # mdi-battery-40
"\U000F007E", # mdi-battery-50
"\U000F007F", # mdi-battery-60
"\U000F0080", # mdi-battery-70
"\U000F0081", # mdi-battery-80
"\U000F0082", # mdi-battery-90
"\U000F0079", # mdi-battery (满电)
"\U000F008E", # mdi-battery-outline
"\U000F0091", # mdi-battery-unknown
]
sensor:
- platform: homeassistant
id: sns_battery_percentage
entity_id: sensor.device_battery
on_value:
- lvgl.label.update:
id: lbl_battery_status
text: !lambda |-
static char buf[10];
std::string icon;
if (x == 100.0) {
icon = "\U000F0079"; // mdi-battery (满电)
} else if (x > 90) {
icon = "\U000F0082"; // mdi-battery-90
} else if (x > 80) {
icon = "\U000F0081"; // mdi-battery-80
} else if (x > 70) {
icon = "\U000F0080"; // mdi-battery-70
} else if (x > 60) {
icon = "\U000F007F"; // mdi-battery-60
} else if (x > 50) {
icon = "\U000F007E"; // mdi-battery-50
} else if (x > 40) {
icon = "\U000F007D"; // mdi-battery-40
} else if (x > 30) {
icon = "\U000F007C"; // mdi-battery-30
} else if (x > 20) {
icon = "\U000F007B"; // mdi-battery-20
} else if (x > 10) {
icon = "\U000F007A"; // mdi-battery-10
} else if (x > 0) {
icon = "\U000F008E"; // mdi-battery-outline
} else {
icon = "\U000F0091"; // mdi-battery-unknown
}
snprintf(buf, sizeof(buf), "%s", icon.c_str());
return buf;
lvgl:
...
pages:
- id: battery_page
widgets:
- label:
id: lbl_battery_status
align: TOP_RIGHT
y: 40
x: -10
text_font: battery_icons_20
text: "\U000F0091" # 以 mdi-battery-unknown 开始

要制作一个说明电池充电的动画,您可以使用 animimg 和一组从 MDI 渲染的图像,显示电池级别:

image:
- file: mdi:battery-10
id: batt_10
resize: 20x20
- file: mdi:battery-20
id: batt_20
resize: 20x20
- file: mdi:battery-30
id: batt_30
resize: 20x20
- file: mdi:battery-40
id: batt_40
resize: 20x20
- file: mdi:battery-50
id: batt_50
resize: 20x20
- file: mdi:battery-60
id: batt_60
resize: 20x20
- file: mdi:battery-70
id: batt_70
resize: 20x20
- file: mdi:battery-80
id: batt_80
resize: 20x20
- file: mdi:battery-90
id: batt_90
resize: 20x20
- file: mdi:battery
id: batt_full
resize: 20x20
- file: mdi:battery-outline
id: batt_empty
resize: 20x20
lvgl:
...
pages:
- id: battery_page
widgets:
- animimg:
align: TOP_RIGHT
y: 41
x: -10
id: ani_battery_charging
src: [
batt_empty,
batt_10,
batt_20,
batt_30,
batt_40,
batt_50,
batt_60,
batt_70,
batt_80,
batt_90,
batt_full
]
duration: 2200ms

TIP

您可以将上面的两个电池示例放置在彼此上方,并根据充电器是否连接切换它们的 hidden 标志:

binary_sensor:
- platform: ...
id: charger_connected
on_press:
then:
- lvgl.widget.show: ani_battery_charging
- lvgl.widget.hide: lbl_battery_status
on_release:
then:
- lvgl.widget.show: lbl_battery_status
- lvgl.widget.hide: ani_battery_charging

使用 xyalign 小部件属性进行精确定位。

使用 meterlabel 小部件,我们可以创建一个也显示日期的模拟时钟。

meter 有三个刻度:一个用于分钟刻度线和指针,范围在 060 之间;一个用于小时刻度线和标签作为主要刻度,范围在 112 之间;一个更高分辨率的刻度用于时针,范围在 0720 之间,以便能够自然地将指针定位在小时之间。第二个刻度没有指示器,而第三个刻度没有刻度线或标签。

脚本在每分钟开始时运行,更新每个指针的线位置以及各自的文本。

lvgl:
...
pages:
- id: clock_page
widgets:
- obj: # 时钟容器
height: SIZE_CONTENT
width: 240
align: CENTER
pad_all: 0
border_width: 0
bg_color: 0xFFFFFF
widgets:
- meter: # 表盘
height: 220
width: 220
align: CENTER
bg_opa: TRANSP
border_width: 0
text_color: 0x000000
scales:
- range_from: 0 # 分钟刻度
range_to: 60
angle_range: 360
rotation: 270
ticks:
width: 1
count: 61
length: 10
color: 0x000000
indicators:
- line:
id: minute_hand
width: 3
color: 0xa6a6a6
r_mod: -4
value: 0
- range_from: 1 # 小时刻度用于标签
range_to: 12
angle_range: 330
rotation: 300
ticks:
width: 1
count: 12
length: 1
major:
stride: 1
width: 4
length: 10
color: 0xC0C0C0
label_gap: 12
- range_from: 0 # 高分辨率小时刻度用于指针
range_to: 720
angle_range: 360
rotation: 270
ticks:
count: 0
indicators:
- line:
id: hour_hand
width: 5
color: 0xa6a6a6
r_mod: -30
value: 0
- label:
styles: date_style
id: day_label
y: -30
- label:
id: date_label
styles: date_style
y: 30
time:
- platform: homeassistant
id: time_comp
on_time_sync:
- script.execute: time_update
on_time:
- minutes: '*'
seconds: 0
then:
- script.execute: time_update
script:
- id: time_update
then:
- lvgl.indicator.update:
id: minute_hand
value: !lambda |-
return id(time_comp).now().minute;
- lvgl.indicator.update:
id: hour_hand
value: !lambda |-
auto now = id(time_comp).now();
return std::fmod(now.hour, 12) * 60 + now.minute;
- lvgl.label.update:
id: date_label
text: !lambda |-
static const char * const mon_names[] = {"一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"};
static char date_buf[8];
auto now = id(time_comp).now();
snprintf(date_buf, sizeof(date_buf), "%s %2d", mon_names[now.month-1], now.day_of_month);
return date_buf;
- lvgl.label.update:
id: day_label
text: !lambda |-
static const char * const day_names[] = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
return day_names[id(time_comp).now().day_of_week - 1];

buttonmatrix 小部件可以与 Key collector 组件 配合使用,将按钮按下收集为按键序列。它将按钮的 text(或配置的 key_code)发送到按键收集器。

如果您输入正确的序列,led 小部件将相应地改变颜色:

lvgl:
...
pages:
- id: keypad_page
widgets:
- led:
id: lvgl_led
x: 30
y: 47
color: 0xFF0000
brightness: 70%
- obj:
width: 140
height: 25
align_to:
id: lvgl_led
align: OUT_RIGHT_MID
x: 17
border_width: 1
border_color: 0
border_opa: 50%
pad_all: 0
bg_opa: 80%
bg_color: 0xFFFFFF
shadow_color: 0
shadow_opa: 50%
shadow_width: 10
shadow_spread: 3
radius: 5
widgets:
- label:
id: lvgl_label
align: CENTER
text: "输入代码并 \uF00C"
text_align: CENTER
- buttonmatrix:
id: lvgl_keypad
x: 20
y: 85
width: 200
height: 190
items:
pressed:
bg_color: 0xFFFF00
rows:
- buttons:
- text: 1
control:
no_repeat: true
- text: 2
control:
no_repeat: true
- text: 3
control:
no_repeat: true
- buttons:
- text: 4
control:
no_repeat: true
- text: 5
control:
no_repeat: true
- text: 6
control:
no_repeat: true
- buttons:
- text: 7
control:
no_repeat: true
- text: 8
control:
no_repeat: true
- text: 9
control:
no_repeat: true
- buttons:
- text: "\uF55A"
key_code: "*"
control:
no_repeat: true
- text: 0
control:
no_repeat: true
- text: "\uF00C"
key_code: "#"
control:
no_repeat: true
key_collector:
- source_id: lvgl_keypad
min_length: 4
max_length: 4
end_keys: "#"
end_key_required: true
back_keys: "*"
allowed_keys: "0123456789*#"
timeout: 5s
on_progress:
- if:
condition:
lambda: return (0 != x.compare(std::string{""}));
then:
- lvgl.label.update:
id: lvgl_label
text: !lambda 'return x.c_str();'
else:
- lvgl.label.update:
id: lvgl_label
text: "输入代码并 \uF00C"
on_result:
- if:
condition:
lambda: return (0 == x.compare(std::string{"1234"}));
then:
- lvgl.led.update:
id: lvgl_led
color: 0x00FF00
else:
- lvgl.led.update:
id: lvgl_led
color: 0xFF0000

注意:

  • 一个基础对象 obj 用作标签的父级;这允许正确居中标签以及独立于标签尺寸用阴影强调它。
  • align_to 用于将标签垂直对齐到 led
  • 更改按钮在 pressed 状态下的背景颜色。
  • 使用 key_code 配置将不同的字符发送到 key_collector 而不是显示的符号。

另一个依赖 Grid 布局的示例可以是显示 Home Assistant OpenWeatherMap 集成 预报的天气面板。

这里显示的所有信息可以像本实用示例中的几个示例描述的那样检索到本地 platform: homeassistant 传感器,但是,这次我们采取不同的方法。不是由 ESPHome 拉取数据,我们将从 Home Assistant 推送数据到原生 Lvgl 组件。

我们使用的天气状况图标来自 MDI。我们只导入与 Home Assistant 中 Weather 集成支持的天气状况对应的图标。对于所有其他标签,您可以使用您选择的任何字体

binary_sensor:
- platform: status
name: 状态传感器
font:
- file: "fonts/materialdesignicons-webfont.ttf"
id: icons_100
size: 100
bpp: 4
glyphs: [
"\U000F0594", # clear-night
"\U000F0590", # cloudy
"\U000F0F2F", # exceptional
"\U000F0591", # fog
"\U000F0592", # hail
"\U000F0593", # lightning
"\U000F067E", # lightning-rainy
"\U000F0595", # partlycloudy
"\U000F0596", # pouring
"\U000F0597", # rainy
"\U000F0598", # snowy
"\U000F067F", # snowy-rainy
"\U000F0599", # sunny
"\U000F059D", # windy
"\U000F059E", # windy-variant
"\U000F14E4", # sunny-off
]
lvgl:
...
pages:
- id: weather_forecast
widgets:
- obj:
align: CENTER
width: 228
height: 250
pad_all: 10
layout:
pad_column: 0
type: GRID
grid_rows: [FR(48), FR(13), FR(13), FR(13), FR(13)]
grid_columns: [FR(10), FR(40), FR(40), FR(10)]
widgets:
- label:
text: "\U000F14E4"
id: lbl_weather_forecast_condition_icon
text_font: icons_100
text_align: CENTER
grid_cell_row_pos: 0
grid_cell_column_pos: 0
grid_cell_column_span: 2
grid_cell_x_align: CENTER
grid_cell_y_align: START
- label:
text: "未知"
id: lbl_weather_forecast_condition_name
text_align: CENTER
grid_cell_row_pos: 0
grid_cell_column_pos: 2
grid_cell_column_span: 2
grid_cell_x_align: STRETCH
grid_cell_y_align: CENTER
- label:
text: "体感温度:"
grid_cell_row_pos: 1
grid_cell_column_pos: 1
- label:
text: "--.- °C"
id: lbl_weather_forecast_tempap
text_align: RIGHT
grid_cell_row_pos: 1
grid_cell_column_pos: 2
grid_cell_x_align: STRETCH
- label:
text: "最高:"
grid_cell_row_pos: 2
grid_cell_column_pos: 1
- label:
text: "--.- °C"
id: lbl_weather_forecast_temphi
text_align: RIGHT
grid_cell_row_pos: 2
grid_cell_column_pos: 2
grid_cell_x_align: STRETCH
- label:
text: "最低:"
grid_cell_row_pos: 3
grid_cell_column_pos: 1
- label:
text: "--.- °C"
id: lbl_weather_forecast_templo
text_align: RIGHT
grid_cell_row_pos: 3
grid_cell_column_pos: 2
grid_cell_x_align: STRETCH
- label:
text: "当前:"
grid_cell_row_pos: 4
grid_cell_column_pos: 1
- label:
text: "--.- °C"
id: lbl_weather_outdnoor_now
text_align: RIGHT
grid_cell_row_pos: 4
grid_cell_column_pos: 2
grid_cell_x_align: STRETCH
text:
- platform: lvgl
name: fr_cond_icon
widget: lbl_weather_forecast_condition_icon
mode: text
- platform: lvgl
name: fr_cond_name
widget: lbl_weather_forecast_condition_name
mode: text
- platform: lvgl
name: fr_tempap
widget: lbl_weather_forecast_tempap
mode: text
- platform: lvgl
name: fr_temphi
widget: lbl_weather_forecast_temphi
mode: text
- platform: lvgl
name: fr_templo
widget: lbl_weather_forecast_templo
mode: text
- platform: lvgl
name: wd_out_now
widget: lbl_weather_outdnoor_now
mode: text

如果您仔细查看 grid_columns 变量,您会注意到左右两侧有两个较薄的列(FR(10))。原因是为标签从边缘添加一些空间。这就是为什么我们必须在第一行的小部件上使用 grid_cell_column_span,以占用多列的空间。

这些标签将在 Home Assistant 中显示为可编辑文本组件,这使得使用 text.set_value 动作更新它们非常容易。为此,我们向 Home Assistant 添加以下自动化

- id: weather_cond_forecast
alias: '天气预报状况'
trigger:
- platform: state
entity_id: sensor.openweathermap_forecast_condition
- platform: state
entity_id: binary_sensor.your_esphome_node_status_sensor
to: 'on'
action:
- action: text.set_value
target:
entity_id:
- text.your_esphome_node_fr_cond_icon
data:
value: >
{% set d = {
"clear-night": "\U000F0594",
"cloudy": "\U000F0590",
"exceptional": "\U000F0F2F",
"fog": "\U000F0591",
"hail": "\U000F0592",
"lightning": "\U000F0593",
"lightning-rainy": "\U000F067E",
"partlycloudy": "\U000F0595",
"pouring": "\U000F0596",
"rainy": "\U000F0597",
"snowy": "\U000F0598",
"snowy-rainy": "\U000F067F",
"sunny": "\U000F0599",
"windy": "\U000F059D",
"windy-variant": "\U000F059E",
"unknown": "\U000F14E4",
"unavailable": "\U000F14E4",
} %}
{{ d.get( states('sensor.openweathermap_forecast_condition') ) }}
- action: text.set_value
target:
entity_id:
- text.your_esphome_node_fr_cond_name
data:
value: >
{% set d = {
"clear-night": "晴朗夜晚",
"cloudy": "多云",
"exceptional": "异常",
"fog": "雾",
"hail": "冰雹",
"lightning": "闪电",
"lightning-rainy": "雷雨",
"partlycloudy": "局部多云",
"pouring": "大雨",
"rainy": "下雨",
"snowy": "下雪",
"snowy-rainy": "雨夹雪",
"sunny": "晴天",
"windy": "有风",
"windy-variant": "多云有风",
"unknown": "未知",
"unavailable": "不可用",
} %}
{{ d.get( states('sensor.openweathermap_forecast_condition') ) }}
- id: weather_temp_feels_like_forecast
alias: '天气体感温度'
trigger:
- platform: state
entity_id: sensor.openweathermap_feels_like_temperature
- platform: state
entity_id: binary_sensor.your_esphome_node_status_sensor
to: 'on'
action:
- action: text.set_value
target:
entity_id:
- text.your_esphome_node_fr_tempap
data:
value: "{{states('sensor.openweathermap_feels_like_temperature') | round(1)}} °C"
- id: weather_temp_forecast_temphi
alias: '天气预报最高温度'
trigger:
- platform: state
entity_id: sensor.openweathermap_forecast_temperature
- platform: state
entity_id: binary_sensor.your_esphome_node_status_sensor
to: 'on'
action:
- action: text.set_value
target:
entity_id:
- text.your_esphome_node_fr_temphi
data:
value: "{{states('sensor.openweathermap_forecast_temperature') | round(1)}} °C"
- id: weather_temp_forecast_templo
alias: '天气预报最低温度'
trigger:
- platform: state
entity_id: sensor.openweathermap_forecast_temperature_low
- platform: state
entity_id: binary_sensor.your_esphome_node_status_sensor
to: 'on'
action:
- action: text.set_value
target:
entity_id:
- text.your_esphome_node_fr_templo
data:
value: "{{states('sensor.openweathermap_forecast_temperature_low') | round(1)}} °C"
- id: weather_temp_outdoor_now
alias: '当前室外温度'
trigger:
- platform: state
entity_id: sensor.outdoor_temperature
- platform: state
entity_id: binary_sensor.your_esphome_node_status_sensor
to: 'on'
action:
- action: text.set_value
target:
entity_id:
- text.your_esphome_node_wd_out_now
data:
value: "{{states('sensor.outdoor_temperature') | round(1)}} °C"

每当相应实体发生变化以及 ESPHome 启动时,自动化将被触发以更新标签——这也是您需要 状态传感器 的原因。请注意,您需要根据您如何配置其名称来调整对应于您的 ESPHome 节点的实体 ID。

LVGL 有屏幕不活动的概念——换句话说,自上次用户与屏幕交互以来的时间被跟踪。这可用于在不活动一段时间后调暗显示屏背光或将其关闭(如屏幕保护程序)。每次使用输入设备(触摸屏、旋转编码器)都算作活动并重置不活动计数器。请注意,使用 on_release 触发器来完成此任务很重要。使用模板数字,您可以让用户调整超时时间。

lvgl:
...
on_idle:
timeout: !lambda "return (id(display_timeout).state * 1000);"
then:
- logger.log: "LVGL 空闲"
- light.turn_off: display_backlight
- lvgl.pause:
touchscreen:
- platform: ...
on_release:
- if:
condition: lvgl.is_paused
then:
- logger.log: "LVGL 恢复"
- lvgl.resume:
- lvgl.widget.redraw:
- light.turn_on: display_backlight
light:
- platform: ...
id: display_backlight
number:
- platform: template
name: LVGL 屏幕超时
optimistic: true
id: display_timeout
unit_of_measurement: "s"
initial_value: 45
restore_value: true
min_value: 10
max_value: 180
step: 5
mode: box

您可以使用此功能来保护和延长 LCD 屏幕的使用寿命,从而更加环保并减少有害废物的产生。

壁挂式 LCD 屏幕的一个常见问题是它们 99.999% 的时间显示相同的图像。即使有人在夜间或黑暗时段关闭背光,LCD 屏幕仍然显示相同的图像,但没有人看到。这种情况很可能在几年的操作后导致烧屏。

一种缓解方法是定期通过显示不同的内容来锻炼像素。LVGL 暂停状态下的 show_snow 选项就是为此目的开发的;它在整个屏幕上显示随机颜色的像素,通过锻炼每个单独的像素来最大程度地减少屏幕烧屏。

在下面的示例中,像素训练每晚进行四次,每次半小时;可以通过触摸屏幕停止。

time:
- platform: ...
on_time:
- hours: 2,3,4,5
minutes: 5
seconds: 0
then:
- switch.turn_on: switch_antiburn
- hours: 2,3,4,5
minutes: 35
seconds: 0
then:
- switch.turn_off: switch_antiburn
switch:
- platform: template
name: 防烧屏
id: switch_antiburn
icon: mdi:television-shimmer
optimistic: true
entity_category: "config"
turn_on_action:
- logger.log: "开始防烧屏"
- if:
condition: lvgl.is_paused
then:
- lvgl.resume:
- lvgl.widget.redraw:
- lvgl.pause:
show_snow: true
turn_off_action:
- logger.log: "停止防烧屏"
- if:
condition: lvgl.is_paused
then:
- lvgl.resume:
- lvgl.widget.redraw:
touchscreen:
- platform: ...
on_release:
then:
- if:
condition: lvgl.is_paused
then:
- lvgl.resume:
- lvgl.widget.redraw:

您可以将其与前面的示例结合使用以关闭背光,这样用户实际上不会注意到这一点。