HomeAssistant环境下的YAML与Jinja模板引擎基础教程及编写经验探索
YAML
Jinja
Home-assistant
基础教程
萌新探索系列
感谢
- 深深感谢论坛各位大神和坛友们的捧场,你们的支持是我不断前进的动力!
- 本文将持续跟进与深入,楼主萌新一枚,水平有限,不到之处还请各位大神和坛友批评指正!
更新
- 4月8日发表本贴。
- 4月11日更新【Jinja的局限性】,希望得到大神指导。
- 4月12日使用【MarkDown】格式重新编辑更新帖子,希望大家喜欢。
- 4月14日新增【代码编写经验梳理】和 Jinja【转义】。
- 4月16日引用HA官方对YAML的某些说明作为参考,修正概念表述中可能引起歧义的解读,细化YAML中【!include系列】【!secret】【!env_var】等用法。
- 4月21日新增【缩进的特殊情况】,并以PyYAML的角度,进一步解读了部分YAML概念(主要是纯量类型)。
- 4月22日更新Jinja【变量滤镜】,新增【变量检验】
- 4月27日新增Jinja【前置知识】之【HA的状态对象】
- 4月29日新增Jinja【HA模板扩展专题】之【状态扩展】和【时间扩展】,因楼主连续几日发烧,暂时休息,其它部分下次再更新。
- 8月7日重新着手更新,准备结合Hass的配置结构和常用组件,重新写一篇基础+进阶版的教程,筹划中。
前言
作为一名零基础上了论(zei)坛(chuan)的萌新,最近在论坛混了几天,跟着各位大神学习学习再学习,收获很多。然后萌新我也发表几个探索的帖子,感觉自己的努力得到了支持和认可,有了一点点的成就感(或许萌新如我就是这样LOW吧)。
本想继续发点组件集合之类的帖子,不过自己的时间精力其实也很有限,而且更关键的是水平也很有限,并且最近对YAML配置文件的修改日益频繁,代码更趋冗杂,错误愈至烦乱,调试越加抓狂...
遂感觉萌新应该沉下心,先把YAML+Jinja语法好好总结一下。同时呢,在论坛里似乎没有找到这样的专门的教程,于是决定在此写个YAML和Jinja模板语言的基础教程,写给自己看,也分享给如我一般的萌新看,大家共同学习,共同提高,在HASSBIAN论坛中不断进步!
另:如在YAML和Jinja的学习过程中发生疑问,或者有新的好的经验体会填坑分享,可以在我的帖子下面回复或者直接私信我,欢迎一起探讨、试验、论证和总结。
约定
一、YAML 基础教程
简介
YAML的意思就是:“我不是标记语言”。
那么标记语言是啥玩意?
这个萌新我知道,HTML(超文本标记语言)就是一种标记语言。
标记语言里有很多种标记,标记用来把文本或者其他内容以某种形式展现出来。
而YAML里面没有各种各样的标记,其本身形式是很利于被人直接阅读和理解的。
所以,我们通常用它来写配置文件,管理各种配置。
YAML的特性用一句话表达如下:
**YAML以数据为中心,使用空白,缩进,分行组织数据,从而使得表示更加简洁易读。**
YAML官网:http://yaml.org,最新版本:1.2
HA的YAML解析器为PyYAML,官网http://pyyaml.org,最新版本3.12
HA官方说明-关于YAML
Home Assistant使用YAML语法进行配置。
YAML可能需要一段时间才能习惯,但在表达复杂配置方面非常强大。
基本规则
- 大小写敏感
- 禁止使用tab缩进(推荐整个代码编写中都不要用到tab)
- 字符串可以不用引号标注
注释方法
用法: 使用#
,注释该行中注释符之后的所有内容
解读: 注释区域由YAML文档中任意位置的#
开始至该行结束,在HA的YAML配置文件内被注释掉的内容,将不可能被YAML和Jinja解析。
备注: 该方法是YAML文档唯一的注释方法,如果需要多行注释,建议在通用编辑软件中使用快捷键Ctrl
+/
字典、列表、缩进
我琢磨,YAML里面最需要注意的是这3种语法:字典、列表、缩进。
① 字典
一种重要的数据结构,本文所述字典(dictionary)指的是Python等语言中的名称,YAML官方中称为映射(mapping)、JSON中称为对象(object)、数据结构也叫做哈希(hashes)或者散列表等。
萌新是不是有点晕,没关系,名字千变万化,实质结构并不复杂。
单个成员表示: 键(key): 值(Value)
完整字典表示: 成员1
对齐换行
成员2
... 或 {成员1, 成员2, ...}
注意: :
或者,
要半角英文,后面必须加空格
!
解读: 以键和值成对出现并组成集合的方式构成的数据结构,比如name: mirukuteii
。目的在于为键(对象)构建和配置值(属性),从而对系统进行组件构建、初始化等配置和管理。
代码实例:
api_password: hassbian #HA前端登录密码
server_host: 0.0.0.0 #监听所有IPV4端口
代码解读: 这是一个字典,这个字典有两个成员。成员1的键为api_password,值为hassbian;成员2的键为server,值为0.0.0.0。我们通过这种方法来对HA前端登录密码和监听ip地址进行配置。
② 列表
另一种数据结构,本文所述列表(list)指的是Python等语言中的名称,YAML官方中称为序列(sequence)、JSON中称为数组(array)。
单个成员表示: - 列表成员
完整列表表示: 成员1
对齐换行
成员2
... 或 [成员1, 成员2,...]
注意: -
或者,
要半角英文,后面必须加空格
!
解读: 以若干成员(元素)组成一个列表的数据结构,比如[dog, cat]
。
代码实例:
monitored_conditions: #网速检测项目
- ping #ping响应速度
- download #下载速度
- upload #上传速度
代码解读: 这是一个字典(或字典成员),键为monitored_conditions,值为一个列表,其成员分别为ping、download、upload。我们通过这种方式配置所需要的网速测试项目分别为ping、download、upload。
③ 缩进
并非一种数据结构,而是为辅助构建复合嵌套的数据结构而存在。
表示: 两个空格
或者 利用{}
、[]
的包含与被包含关系来确定
注意: 关键要对齐,不能用tab
代替空格,小心你的代码编辑器自动用tab
缩进!
解读: 表示和管理数据间层次关系(嵌套)的方法。
代码实例:
http:
api_password: hassbian #HA前端登录密码
server_host: 0.0.0.0 #监听所有IPV4端口
也可以写成:
http: {api_password: hassbian, Server_host: 0.0.0.0}
代码解读: 这是1个字典,其键为http,其值为另1个字典(包含2个字典成员,键分别为api_password和Server_host)。空格缩进逐级表示出层次关系(缩进后的字典从属于不缩进的字典,相同缩进的字典项是组成完整字典的平等的成员)。我们通过这种方法,配置组件http下的两个子属性,api密码和ip地址监听列表,其值分别为hassbian与0.0.0.0。
特殊情况:
4个空格缩进的情形:当字典嵌套字典又嵌套在列表中时,比如
- 字典键1:
字典键2: "字典值"
代码解读: 因为实际嵌套了2层次,所以字典键2这一行必须缩进4个空格,而不是2个。若只缩进两个,侧字典键1与字典键2位并列的字典成员关系。
理解了以上3个基本语法,剩下的不过是复合嵌套罢了。
④ 复合嵌套
其实缩进本身就是管理复合嵌套关系的一种语法,但是这里我将再举个复杂点的例子。我在上个帖子中发表了一段关于网络测速PKG的YAML代码,原理是利用系统自带组件sensor.speedtest
进行网络测速。我就顺便以此为例简单介绍复合嵌套。
代码实例:
sensor:
- platform: speedtest
server_id: 5316 #南京电信测试节点
monitored_conditions:
- ping #sensor.speedtest.ping
- download
- upload
hour:
- 2
- 14
minute: 22
- platform: speedtest
server_id: 4665 #上海移动测试节点
monitored_conditions:
- ping #sensor.speedtest.ping_2
代码解读: 构建1个键为sensor的字典,其值由列表(含2个成员)构成。列表成员1为1个字典,有5个字典成员,其中的2个(键分别为monitored_conditions、hour)的值又分别由列表构成;列表成员2也是1个字典,有3个字典成员,其中最后的字典成员的值又由列表(成员只有1个)构成。配置组件类型sensor下的子组件speedtest(同时开2个),第1个每天2时22分和14时22分连接南京电信测试节点测试ping、下载、上传3个项目;第2个按照默认的时间(每小时)连接上海移动测试节点测试ping值。
⑤ 经验总结
在配置HA中总结的YAML经验,拿出来分享一下,不一定准确,希望群友及时批评指正。
体会1:HA的配置文件中,重点和基础是字典结构
就是: 属性: 值
解读: 作为一个配置文件,这是一种核心的配置手段。就像注册表与ini文件一样,必须建立项目与数据的关系。
HA官方说明-映射(字典)结构
YAML语法的基础是块集合和包含键值对的映射。 集合中的每个项目都以一个 - 开始,而映射具有格式key:value。 如果指定重复键,则使用键的最后一个值。 这有点类似于哈希表或者更具体的Python中的字典,它们也可以进行嵌套。
体会2:在此基础上,以缩进(或者{}
、[]
的包含关系)管理配置的层次关系
比如:对一个物联网交互组件的简单配置,结构通常如下
组件域名:
平台:XXX
配置项1: XXX
配置项2: XXX
扩展解读:在物联网交互组件中,组件提供某些功能的核心逻辑。平台可以连接到特定的软件或硬件平台。一个物联网交互组件下面往往包含多个平台。比如灯组件,有hue平台、lifx平台等。平台的配置项与平台并列。
HA官方说明-缩进结构
请注意,缩进是使用YAML指定关系的重要部分。缩进的东西嵌套在“更高一层”的东西里面。如果您不使用固定宽度字体的编辑器,那么获得正确的缩进可能会非常棘手。 制表符不允许用于缩进。 我们约定使用2个空格作为每个缩进的级别。
体会3:在此基础上,以列表扩展取值范围
比如:
组件1:
实体1:
属性1: XXX
属性2:
- XXX
- XXX
实体2:
属性3:
- XXX
- XXX
发现有的新坛友都会问如何在YAML中添加多个相同的设备,本文这里就是答案,自发布之日起就存在在这里了,楼主我虽然是萌新,但这个教程也不是随便写的,很多例子都有经验在里面,请注意看看下面的例子!
体会4:注意列表不仅可以用在值上,还可以用来对组件内部的不同平台或实体进行分组
比如:
组件1:
- 实体1的属性1: XXX
实体1的属性2: XXX
实体1的属性3:
- XXX
- XXX
- 实体2的属性1: XXX
实体2的属性4: XXX
实体2的属性5:
- XXX
- XXX
所以,当某个组件下面出现多胞胎的子组件或者子属性组合时,应当用列表分开。
实际中的案例:
配置2个小米网关,用列表方法将mac与key属性组合打包并与其它属性分开。
xiaomi_aqara:
gateways:
- mac: !secret xiaomi_gateway_mac1
key: !secret xiaomi_gateway_key1
- mac: !secret xiaomi_gateway_mac2
key: !secret xiaomi_gateway_key2
纯量
纯量的意义
YAML最后一种也是最基本的数据结构,YAML官方中称为纯量(scalars),也称为原子量。
解读: 纯粹抽象概念,即相对于以上各类字典、列表以及复合嵌套等复杂结构来说,不可再分割的最基本的节点(node)。YAML官方说法凡是能够用一系列的零到若干个可打印的unicode字符表示的数据,都可以作为纯量。
例子: age: 33
中的age
与33
都是纯量!
① 整数型纯量(intergers)
- 标准写法:
12345
- 十 进 制:
+ 12345
- 八 进 制:
0o14
- 十六进制:
0xc
解读:只有数字和下划线构成的值,将被YAML解析为整数,下划线将自动忽略!
② 浮点数(floating point)
- 标准写法:
1.23015e + 3
- 指数写法:
12.3015e + 02
- 固定小数点:
1230.15
- 负无穷大:
-.inf
- 无法以数字衡量:
.NAN
③ 布尔值、NULL
- NULL:
null
或者~
- 布尔值:
True
, False
解读:true
, false
; yes
, no
; on
, off
同样有效(PyYAML将解析为True或False)
④ 时间日期
- 标准写法:
2001-12-15T02:59:43.1Z
- ISO8601:
2001-12-14t02:59:43.10-05:00
- 空格写法:
2001-12-14 21:59:43.10 -5
- 日期写法:
2002-12-14
解读:凡是这样写的值,会被YAML自动解析为Pythondatatime
格式
⑤ 字符串
字符串:'123456'
解读:HA配置文件中最常见也最重要的纯量类型,在YAML中通常的字符不加引号也会自动认为是字符串,引号可以用'
也可以用"
,注意相互嵌套关系。使用"
表示字符串时,特殊字符可以通过倒斜线(\x* 以及 \u***)来进行转义。
特殊符号和操作
①强制转换数据类型
用法: !! XXX
XXX表示强制转换的类型
例如: notdata: !! str 2018-04-09
相当于notdate: '2018-04-09'
②多行字符串折叠换行
用法: >
解读: 表示>
下面的多行字符串行之间不保留换行符\n
实例:
action:
- service: notify.weixin_sysnotify
data_template:
title: "设备联网状态变更"
message: >
设备:{{ trigger.event.data.new_state.attributes.friendly_name }}
{%- if trigger.event.data.new_state.state == '离线' %}
已断开家庭网络
{%- elif trigger.event.data.new_state.state == '在线' %}
已接入家庭网络
{% endif -%}
收到的微信提醒信息为:
设备:XXXXX已断开(或接入)家庭网络
③多行字符串保留换行
用法: |
解读: 表示|
下面的多行字符串行之间保留换行符\n
实例:
action:
- service: notify.weixin_sysnotify
data_template:
title: "设备联网状态变更"
message: |
设备:{{ trigger.event.data.new_state.attributes.friendly_name }}
{%- if trigger.event.data.new_state.state == '离线' %}
已断开家庭网络
{%- elif trigger.event.data.new_state.state == '在线' %}
已接入家庭网络
{% endif -%}
收到的微信提醒信息为:
设备:XXXXX
已断开(接入)家庭网络
④字符串保留结尾换行符
用法: +
一般用在|
之后
解读: 表字符串行之后的换行符\n
被保留
实例:
value: |+
hahaha
#
#
解读: value = hahaha\n\n
⑤字符串不保留结尾换行符
用法: -
一般用在|
之后
解读: 表字符串行之后的换行符\n
被保留
实例:
value: |-
hahaha
解读: value = hahaha
HA中的特殊用法
!XXXXX
暂时理解为HA内部定义的标签,用于替换内容
例如:!include
系列、!secret
、!env_var
①!include系列
!include
:调用单个外部文件加入指定位置。
下例解读:调用外部文件lights.yaml中的内容作为lights组件的值。
lights: !include lights.yaml
!include_dir_list
:调用外部目录下的每个文件,并将其内容作为单个列表的成员,最后将整个列表加入指定位置。
下例解读:调用外部目录lights中的每个文件,并将其内容作为列表的一个成员,最后将整个列表作为组件lights的配置属性(值)。注意:文件名不会被涉及,每个文件的内容是列表成员。
lights: !include_dir_list lights
!include_dir_named
:调用外部目录下的每个文件,并将其文件名和文件内容分别作为单个字典成员的键和值,最后将整个字典加入指定位置。
下例解读:调用外部目录lights中的每个文件,并将其文件名和文件内容分别作为单个字典成员的键和值,最后将整个字典作为组件lights的配置属性(值)。注意:文件名会被作为字典成员的键,每个文件的内容则作为字典成员的值。
lights: !include_dir_named lights
!include_dir_merge_list
:调用外部目录下的所有文件作为列表加入指定位置。
下例解读:调用外部目录lights中的每个文件,将其内容直接合并,最后以一个大列表作为组件lights的配置属性(值)。
lights: !include_dir_merge_list lights
!include_dir_merge_named
:调用外部目录下的所有文件作为字典加入指定位置。
下例解读:调用外部目录lights中的每个文件,将其内容直接合并,最后以一个大字典作为组件lights的配置属性(值)。
lights: !include_dir_merge_named lights
注意:不管是哪种涉及调用整个外部目录下文件的方法,该目录下的所有文件都将被递归调用,包括任何子目录下的文件。
②!secret 调用secret.yaml文件中的私密变量
用法举例:
api_password: !secret APIPWD
解读:使用secret.yaml文件中的字典项APIPWD: hassbian作为api_password的值,别忘了在secret.yaml中包含如下代码:
APIPWD: hassbian
注意:如果你在某个yaml文件中使用!secret,那么HA将首先查找该yaml文件同目录下的secret.yaml并进行调用,若无法调用成功,则会查找上一级目录下的secret.yaml并进行调用,直至查找到HA配置文件的根目录(configuration.yaml所在的目录)。你可以通过这种方法来设置该私密变量的有效范围。
③!env_var 调用系统环境变量
用法举例:
api_password: !env_var HA_PWD hassbian
解读:使用系统环境变量HA_PWD作为api_password的值,若不存在该环境变量,则使用hassbian作为密码。设置环境变量有诸多方法,不在本文探讨范围。
/XX
在自动化触发组件【automation.trigger】中,属性hours、minutes、sencond的值可以为/XX,表示每隔XX个时间(小时、分钟、秒)触发该自动化
待补充项
YAML教程暂时就写到这里,不定期更新和深入
二、Jinja 基础教程
简介
如果看到这里你还不知道Jinja是什么,没关系,只要在论坛里混过那么几个日夜,拜读过大神那么几篇帖子,配置过那么几段代码,那么你对下面这种代码肯定不陌生:
data_template:
message: >
{% if is_state('device_tracker.paulus', 'home') %}
Ha, Paulus is home!
{% else %}
Paulus is at {{ states('device_tracker.paulus') }}.
{% endif %}
以上代码显然有别于前面教程里的YAML的体系,这就是Jinja,基于Python的一种模板(Template)引擎,它让本来死板的YAML拥有更大的灵活性和效率性。
官网:http://jinja.pocoo.org/ 最新版本:2.10
HA官方说明-关于模板
*模板是Home Assistant的一个重要特性。
模板允许用户对进出系统的信息进行控制。
比如:
- 格式化输出一段消息,例如notify组件、alexa组件等。
- 对信息源传入的原始数据进行处理,例如MQTT组件、REST传感器组件、命令行传感器组件等。
- 自动化模板。*
HA官方说明-关于Jinja2
Home Assistant中的模板采用Jinja2模板引擎。这意味着我们正在使用它们的语法,同时在渲染过程中我们也为模板创造了一些自定义的HA变量。
前置知识1 HA的基本架构(非必需)
在开始Jinja的学习前,建议大家先了解一下HA的基本架构:
【HA官方开发指南】【自译】第一篇 HA架构和组件
前置知识2 HA的状态对象(非必需)
然后,再了解下HA中的状态对象(State Objects)
首先,我按照官网说法简单介绍如下:
1.在HA中所有的设备都以实体(Entity,重要概念请记住!)表示。
2.实体将其当前状态写入状态机以供其他实体/模板/前端访问。
3.我们通过各种状态来“看”实体,这些状态就是实体当前的代表。
4.所以HA中用实体的状态对象来代表实体。
简单分,可以用以下的层次来说明:
设备
->实体
->状态对象
->状态字段
->属性扩展字段
状态对象(State Objects)通常包含以下字段:
state
:以字符串形式,表示当前状态
entity_id
:实体代号,由domian.object_id
组成
domain
:实体的域名
object_id
:实体的对象代号
name
:实体名,必须为英文,在HA后台中使用的实体名
last_updated
:状态写入状态机的时间。请注意,编写完全相同的状态(包括属性)不会导致更新此字段。
last_changed
:状态改变的时间,当只有属性改变时不会导致更新此字段
attributes
:以字典形式,表示当前状态的额外属性
attributes属性字段(本身就是个字典)中通常包含以下字段:
friendly_name
:实体名昵称,可以用中文,在HA前端看到的实体名
icon
:实体图标,在HA前端看到的实体图标
hidden
:实体是否在HA前端可见
entity_picture
:实体图片,用来在HA前端中取代图标
assumed_state
:实体状态是否为假定状态。(即:HA无法从实体获取状态反馈信息,只能通过最后的命令来假设当前实体的状态。)
unit_of_measurement
:实体状态的度量单位。
要是看了以上内容还比较懵逼,下面就看我举几个例子吧。
下面我以小米网关自带小彩灯为例。
这个设备在HA中表示为1个实体
实体表示为:
light.gateway_light_XXXXXXXXXXXX
(XXX代表网关MAC地址,网关不同,MAC地址当然也不一样,这种命名方法来自于xiaomi_aqara组件)
这个实体的各种状态,以状态对象来表示。
实体的状态对象表示为:
states.light.gateway_light_XXXXXXXXXXXX
这个实体的状态对象的状态字段可以这样写(在下面的小节中,你会发现这不是最好的写法):
实体的状态对象的状态字段对应值表示为:
states.light.gateway_light_XXXXXXXXXXXX.state
如果我们要读取这个实体的状态对象的属性字段中的friendly_name
字段对应值,可以这么写(在下面的小节中,你会发现这不是最好的写法)
这个实体的状态对象的属性字段的昵称字段对应值表示为:
states.light.gateway_light_XXXXXXXXXXXX.attributes.friendly_name
所以,我们要想得知这个小米网关小彩灯的状态(on
、off
)时,
只需要读取states.light.gateway_light_XXXXXXXXXXXX.state
即可
我们想要得知这个小米网关小彩灯在前端显示的名称(一个字符串)时,
只需要读取states.light.gateway_light_XXXXXXXXXXXX.attributes.friendly_name
更多细节和最佳使用姿势请参看后面的HA模板扩展专题一节
看到这里,我想总明白状态对象了吧。
在Jinja中,你会经常和状态对象打交道的。
前置知识3 HA的事件(非必需)
本项主要涉及到在自动化中编写Jinja代码所需要的前置知识。
有空再写吧
标记符
{% ... %}
块标记符,一般用来执行语句
{{ ... }}
变量标记符,一般用来输出变量
{# ... #}
注释标记符,一般用来调试代码,使某段代码无效
变量
除了普通的字符串变量,Jinja2还支持列表、字典和对象。
变量赋值
变量赋值通常用 set ,并可以为多个变量赋值:
{% set name = 'mirukuteii' %}
{% set solar_angle = states.sun.sun.attributes.elevation %}
局部变量:使用with语句来创建一个内部的作用域,这样创建的变量只在with代码块中才有效。
{% with temp = 42 %}
{{ temp }}
{% endwith %}
变量输出
{{ temp }}
同时,我们可以通过.
和[]
访问变量的属性:
{{ states.sun.sun.attributes.elevation }}
{{ states.sun.sun.attributes["elevation"]) }}
以上两种方式几乎没有差别,但当属性值内含有空格时,请使用后者。
算术运算符
算术运算符 |
意义 |
解读 |
+ |
加法 |
{{ 1 + 1 }} 返回 2 |
- |
减法 |
{{ 1 - 1 }} 返回 0 |
* |
乘法 |
{{ 2 * 3 }} 返回 6 |
/ |
除法 |
{{ 1 / 2 }} 返回 0.5 |
// |
整除 |
{{ 5 // 3 }} 返回 1 |
% |
整除 |
{{ 5 % 3 }} 返回 2 |
** |
乘方 |
{{ 2 ** 3 }} 返回 8 |
比较运算符
比较运算符 |
意义 |
解读 |
== |
等于 |
{{ 1 == 1 }} 返回 true |
!= |
不等于 |
{{ 1 != 1 }} 返回 false |
> |
大于 |
{{ 2 > 3 }} 返回 false |
>= |
大于等于 |
{{ 6 >= 6 }} 返回 true |
< |
小于 |
{{ 5 < 6 }} 返回 true |
<= |
小于等于 |
{{ 5 <= 6 }} 返回 true |
逻辑运算符
逻辑运算符 |
意义 |
解读 |
and |
与 |
{{true and false}} 返回 true |
or |
或 |
{{true or false}} 返回 false |
not |
非 |
{{not false}} 返回 true |
其它操作符
其他操作符 |
意义 |
解读 |
in |
在...之内(是否包含) |
{{1 in [1,2,3]}} 返回 true |
is |
是(是否同一对象) |
{{a is a}} 返回 true |
| |
滤镜 |
|
() |
|
|
~ |
拼接字符串 |
|
. 或/ 或[] |
获取属性 |
|
控制结构
if 表达式
{{
做某事 if
满足条件 else
做另一件事 }}
实例:
{{ '在线' if is_state('device_tracker.iphone', 'home') else '离线' }}
if 语句
if
后直接跟变量
,可以测试该变量是否未定义,为null
或 false
。
if语句
关键词:if
、elif
、else
、endif
。
用法举例:
{%- if states.sensor.moon -%}
{%- if states.sensor.moon.state == 'New moon' -%} 新月
{%- elif states.sensor.moon.state == 'Waxing crescent' -%} 娥眉月
{%- elif states.sensor.moon.state == 'First quarter' -%} 上弦月
{%- elif states.sensor.moon.state == 'Waxing gibbous' -%} 盈凸月
{%- elif states.sensor.moon.state == 'Full moon' -%} 满月
{%- elif states.sensor.moon.state == 'Waning gibbous' -%} 亏凸月
{%- elif states.sensor.moon.state == 'Last quarter' -%} 下弦月
{%- elif states.sensor.moon.state == 'Waning crescent' -%} 残月
{%- else -%} 未知月相
{%- endif -%}
{%- endif -%}
由于Jinja中没有选择语句,所以通常用elif方法代替选择。
for 语句
待编辑
变量滤镜
滤镜(过滤器)用来修正变量,使用一个竖线和变量相隔。
在HA的配置文件中灵活使用滤镜将使得你的代码简洁高效。
Jinja内置过滤器有很多,下面举几个常用的过滤器:
- int:将变量转换为整数
- float:将变量转换为浮点数
- abs:取变量的绝对值
- round('精度'):按照精度(保留小数点后X位数)进行四舍五入。
- join('分隔符'):以分隔符组合元素为字符串,也可不设定分隔符直接组合。
- length:返回该列表或字典变量的成员数。
- replace("a","b"):把字符串中的a替换为b。
- default('默认值'):若变量为定义,则变量值修正为默认值,缩写:d
- first:返回该列表变量的第一个成员
- format:应用Python字符串格式化
- 待编辑
例子:
value_template: '{{ value | multiply(0.001) | round(1) }}'
说明:对变量进行了两次过滤,先乘0.001(就是除以1000),再保留小数点后1位进行四舍五入。
扩展说明:
细心的朋友可能注意到了,为什么上述例子中不直接将变量除以1000而是要弯弯绕绕用乘法滤镜乘以0.001,这看上去有点反人类不是么。原因很简单:在{{ ... }}
中通过滤镜操作变量更加方便简洁,我们往往无法对变量进行直接运算。
这么写是不行的:
value_template: '{{ value / 1000 |round(1) }}'
要写成这样才行:
value_template: '{{ (value|int) / 1000 |round(1) }}'
变量检验
除了用滤镜修正变量,我们还用检查器检验变量。
检验通常在if语句块中进行,方法为在变量后加上一个 is 以及检查器的名字。
Jinja内置检查器有很多,下面举几个常用的检查器:
- defined:检验变量是否定义
- lower:检验变量是否为小写
- mapping:检验变量是否为字典
- sequence:检验变量是否为列表
- number:检验变量是否为数字
- even:检验变量是否为偶数
- divisibleby('除数'):检验变量是否能被除数整除
例子:
{% if name is defined %}
说明:测试变量name是否存在,存在则返回true。
空格控制
使用-
来控制空格和换行,一般必须紧邻%
。
举例说明
例1代码:
a
{% if True %}
b
{% endif %}
c
例2代码:
a
{% if True -%}
b
{%- endif %}
c
例3代码:
a
{%- if True -%}
b
{%- endif -%}
c
例1输出结果:
a
b
c
例2输出结果:
a
b
c
例3输出结果:
abc
转义控制
就是不解析Jinja代码,直接输出其中的内容。
用法:使用{% raw %}
和{% endraw %}
将转义内容包括进来
{% raw %}
{{states(sun.sun)}}
{% endraw %}
结果为:
{{states(sun.sun)}}
HA模板扩展专题
状态扩展
states
包含了你的HA中所有的状态对象(按实体id字母顺序排列)
states.domain
包含了该域名(实体类别)下所有的状态对象(按实体id字母顺序排列)
states.domain
包含了该域名(实体类别)下所有的状态对象(按实体id字母顺序排列)
states.sensor.temperature
返回实体sensor.temperature
的状态对象
states('device_tracker.paulus')
返回实体device_tracker.paulus
的状态对象,若该实体不存在,则返回unknown
is_state('device_tracker.paulus', 'home')
将测试给定的实体是否处于指定的状态。
state_attr('device_tracker.paulus', 'battery')
返回实体的属性值,若不存存在该属性,则什么也不返回。
is_state_attr('device_tracker.paulus', 'battery', 40)
将测试给定的实体的属性值是否处于指定的状态。
从以上部分内容不难发现,HA对于实体的state
和attributes
更多关注和扩展,使得我们的访问更加方便。
再以小米网关小彩灯为例:
前置知识中灯的状态写法为:
states.light.gateway_light_XXXXXXXXXXXX.state
更强壮的写法应为:
states('light.gateway_light_XXXXXXXXXXXX')
两个的区别就在于后者的容错性更高,实体不存在时不会报错。
其它扩展用法也是同理,不啰嗦了。
时间扩展
now()
返回当前本地时间
utcnow()
返回当前UTC时间
这里注意,以上两个扩展包含特定的值可供访问:
*.second
、*.minute
、*.hour
、*.day
、*.month
、*.year
、*.weekday()
、*.isoweekday()
as_timestamp()
将日期时间对象或字符串转换为UNIX时间戳
strptime(string, format)
根据指定的格式把一个时间字符串解析为时间元组,用法和python中相同。
- 滤镜:
|timestamp_local
将UNIX时间戳转换为本地时间
- 滤镜:
|timestamp_utc
将UNIX时间戳转换为本地时间
- 滤镜:
|timestamp_custom(format_string, local_boolean)
将UNIX时间戳转换为自定义格式,默认使用本地时间戳
数学扩展
待更新
正则扩展
待更新
其它扩展
待更新
Jinja的局限性
1.在YAML中的局限性
①Jinja是独立于YAML设计的,它作为一种模板引擎服务于各种环境。
②所有的Jinja代码必然以{
打头。({{
或 {%
)
③YAML中字典结构可以以{ ... }
(流式映射的方式)来构建。
④要使得jinja代码正常工作,只能把{
打头的一整段代码放置于字符串结构
中。
⑤所谓字符串结构,可以'
或"
引用,也可以是多行字符串
形式。
⑥不把{
打头的Jinja代码放在字符串结构中,YAML就会在解析时把它当做字典处理,从而发生错误,比如直接解析{%
时报错:%不可以作为字典的键
。
结论一:在YAML下,Jinja代码要正常运行,必须包含在字符串结构中。
2.在HA环境下的局限性
HA环境下,当我们使用模板(template),并构建一个模板实体的配置项与属性的过程中,不允许配置项为没有定义过的配置项(字典的键),从而无法使用Jinja代码来编写配置项。
例如:
device_tracker.iphone: {'friendly_name': 'iphone', '{{"entity_picture" if is_state("device_tracker.iphone", "home") else "icon"}}': '{{"local/pic/me.jpg" if is_state("device_tracker.iphone5", "home") else "mdi:cellphone-iphone"}}'}
解读:上述代码目的在于跟踪手机,并在手机在家时,设置实体的图标为机主的照片,否则为手机的图标。YAML和Jinja语法上并没有任何错误,但是无法通过HA配置。
报错信息:
{{"entity_picture" if is_state("device_tracker.iphone5", "home") else "icon"}}
不是一个正确的配置选项,无法通过配置。
所以,只有entity_picture
或者icon
这种特定的字符串才能作为一个正确的配置选项。本例的解决办法是使用Cunstom UI中的JavaScript脚本。在这里感谢@yoyosuka老哥的交流指导。
结论二:在HA下,Jinja代码段无法作为字典的键,即HA的组件或属性。
事实上,HA官网文档中所有的Jinja例子都是用来输出字典成员的值的。
3.经验总结:
在HA下的YAML配置文件中,一段Jinja代码,只能在字符串中起作用,并只能在字典的值中发生作用。无法影响到值外的键,也无法构建值内创建结构。
三、代码编写经验梳理
- 找一个顺手好用的代码编辑器,会让你事半功倍。
- 涉及语法的符号请用英文半角字符。
- 注意YAML文件的编码格式,中文请使用UTF-8
- 时刻注意组件、平台、实体等的名称是否与其对应的名称一致,比如大小写,下划线、空格等容易被忽视之处。
- 用states()和is_state()代替states.domain.entityid.state的写法.
friendly_name
、单行模板代码等,用"
引用
- 模板代码外面用
"
,里面用'
, 比如message: "{% if trigger.to_state.name == 'Dale\'s Bedroom' %}Someone's in your base, killing your noobs!{% else %}It's just another door.{% endif %}"
- 多行模板代码外面不需要用引号。
持续更新中