Mirukuteii 发表于 2018-4-8 02:45:54

【YAML+Jinja】教程与经验,庆祝论坛人数过万,解除回帖可见

本帖最后由 Mirukuteii 于 2018-8-7 09:45 编辑

【YAML+Jinja】基础教程与编写经验,萌新探索之三【8.7更新】

---
# HomeAssistant环境下的YAML与Jinja模板引擎基础教程及编写经验探索
`YAML` `Jinja` `Home-assistant` `基础教程` `萌新探索系列`
![](https://img.shields.io/badge/YAML-1.2-blue.svg) ![](https://img.shields.io/badge/Jinja-2.10-orange.svg) ![](https://img.shields.io/badge/PyYAML-3.12-red.svg) ![](https://img.shields.io/badge/Pas ... 5.5-brightgreen.svg) ![](https://img.shields.io/badge/%E6 ... 4--29-lightgrey.svg)


### **感谢**
- **深深感谢论坛各位大神和坛友们的捧场,你们的支持是我不断前进的动力!**
- **本文将持续跟进与深入,楼主萌新一枚,水平有限,不到之处还请各位大神和坛友批评指正!**

### **更新**
- 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的学习过程中发生疑问,或者有新的好的经验体会填坑分享,可以在我的帖子下面回复或者直接私信我,欢迎一起探讨、试验、论证和总结。
      
###**约定**
- HA=“Home Assistant”


# 一、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`。目的在于为键(对象)构建和配置值(属性),从而对系统进行组件构建、初始化等配置和管理。
**代码实例:**
```yaml
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,...]`
**注意:**`-`或者`,`要半角英文,后面必须加`空格`!
**解读:**以若干成员(元素)组成一个列表的数据结构,比如``。
**代码实例:**
```yaml
monitored_conditions:   #网速检测项目
- ping                #ping响应速度
- download            #下载速度
- upload            #上传速度
```
**代码解读:** 这是一个字典(或字典成员),键为monitored_conditions,值为一个列表,其成员分别为ping、download、upload。我们通过这种方式配置所需要的网速测试项目分别为ping、download、upload。

###**③ 缩进**
      并非一种数据结构,而是为辅助构建复合嵌套的数据结构而存在。
      
**表示:**`两个空格` 或者 利用`{}`、`[]`的包含与被包含关系来确定
**注意:**关键要对齐,不能用`tab`代替空格,小心你的代码编辑器自动用`tab`缩进!
**解读:**表示和管理数据间层次关系(嵌套)的方法。
**代码实例:**
                  
```yaml
http:
api_password:hassbian#HA前端登录密码
server_host:   0.0.0.0   #监听所有IPV4端口
```
也可以写成:
                  
```yaml
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个空格缩进的情形:当字典嵌套字典又嵌套在列表中时,比如
```yaml
- 字典键1:
    字典键2: "字典值"
```
**代码解读:** 因为实际嵌套了2层次,所以字典键2这一行必须缩进4个空格,而不是2个。若只缩进两个,侧字典键1与字典键2位并列的字典成员关系。

**理解了以上3个基本语法,剩下的不过是复合嵌套罢了。**

###**④ 复合嵌套**
其实缩进本身就是管理复合嵌套关系的一种语法,但是这里我将再举个复杂点的例子。我在上个帖子中发表了一段关于网络测速PKG的YAML代码,原理是利用系统自带组件`sensor.speedtest`进行网络测速。我就顺便以此为例简单介绍复合嵌套。
**代码实例:**
                  
```yaml
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:在此基础上,以缩进(或者`{}`、`[]`的包含关系)管理配置的层次关系
**比如:**对一个物联网交互组件的简单配置,结构通常如下
```yaml
组件域名:
平台:XXX
配置项1: XXX
配置项2: XXX
```
**扩展解读:**在物联网交互组件中,组件提供某些功能的核心逻辑。平台可以连接到特定的软件或硬件平台。一个物联网交互组件下面往往包含多个平台。比如灯组件,有hue平台、lifx平台等。平台的配置项与平台并列。

>###HA官方说明-缩进结构
*请注意,缩进是使用YAML指定关系的重要部分。缩进的东西嵌套在“更高一层”的东西里面。如果您不使用固定宽度字体的编辑器,那么获得正确的缩进可能会非常棘手。 制表符不允许用于缩进。 我们约定使用2个空格作为每个缩进的级别。*

####体会3:在此基础上,以列表扩展取值范围
**比如:**
```yaml
组件1:
实体1:
    属性1: XXX
    属性2:
      - XXX
      - XXX
实体2:
    属性3:
      - XXX
      - XXX
```

**发现有的新坛友都会问如何在YAML中添加多个相同的设备,本文这里就是答案,自发布之日起就存在在这里了,楼主我虽然是萌新,但这个教程也不是随便写的,很多例子都有经验在里面,请注意看看下面的例子!**

####体会4:注意列表不仅可以用在值上,还可以用来对组件内部的不同平台或实体进行分组
**比如:**
```yaml
组件1:
- 实体1的属性1: XXX
    实体1的属性2: XXX
    实体1的属性3:
      - XXX
      - XXX
- 实体2的属性1: XXX
    实体2的属性4: XXX
    实体2的属性5:
      - XXX
      - XXX
```

所以,当某个组件下面出现多胞胎的子组件或者子属性组合时,应当用列表分开。



####实际中的案例:
配置2个小米网关,用列表方法将mac与key属性组合打包并与其它属性分开。
```yaml
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自动解析为Python`datatime`格式

###⑤ 字符串
字符串:`'123456'`
解读:HA配置文件中最常见也最重要的纯量类型,在YAML中通常的字符不加引号也会自动认为是字符串,引号可以用`'`也可以用`"`,注意相互嵌套关系。使用`"`表示字符串时,特殊字符可以通过倒斜线(\x* 以及 \u***)来进行转义。

## 特殊符号和操作
###①强制转换数据类型
**用法:** `!! XXX` XXX表示强制转换的类型
**例如:** `notdata: !! str 2018-04-09`相当于`notdate: '2018-04-09'`
###②多行字符串折叠换行
**用法:** `>`
**解读:** 表示`>`下面的多行字符串行之间不保留换行符`\n`
**实例:**
```yaml
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`
**实例:**
```yaml
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`被保留
**实例:**
```yaml
value: |+
hahaha
#
#
```
**解读:** value = `hahaha\n\n`

###⑤字符串不保留结尾换行符
**用法:** `-` 一般用在`|`之后
**解读:** 表字符串行之后的换行符`\n`被保留
**实例:**
```yaml
value: |-
hahaha



```
**解读:** value = `hahaha`


## HA中的特殊用法
###**!XXXXX**
暂时理解为HA内部定义的标签,用于替换内容
**例如:**`!include`系列、`!secret`、`!env_var`
###**①!include系列**
- `!include`:调用单个外部文件加入指定位置。
**下例解读:**调用外部文件lights.yaml中的内容作为lights组件的值。
```yaml
lights: !include lights.yaml
```
- `!include_dir_list`:调用外部目录下的每个文件,并将其内容作为单个列表的成员,最后将整个列表加入指定位置。
**下例解读:**调用外部目录lights中的每个文件,并将其内容作为列表的一个成员,最后将整个列表作为组件lights的配置属性(值)。注意:文件名不会被涉及,每个文件的内容是列表成员。
```yaml
lights: !include_dir_list lights
```
- `!include_dir_named`:调用外部目录下的每个文件,并将其文件名和文件内容分别作为单个字典成员的键和值,最后将整个字典加入指定位置。
**下例解读:**调用外部目录lights中的每个文件,并将其文件名和文件内容分别作为单个字典成员的键和值,最后将整个字典作为组件lights的配置属性(值)。注意:文件名会被作为字典成员的键,每个文件的内容则作为字典成员的值。
```yaml
lights: !include_dir_named lights
```
- `!include_dir_merge_list`:调用外部目录下的所有文件作为列表加入指定位置。
**下例解读:**调用外部目录lights中的每个文件,将其内容直接合并,最后以一个大列表作为组件lights的配置属性(值)。
```yaml
lights: !include_dir_merge_list lights
```
- `!include_dir_merge_named`:调用外部目录下的所有文件作为字典加入指定位置。
**下例解读:**调用外部目录lights中的每个文件,将其内容直接合并,最后以一个大字典作为组件lights的配置属性(值)。
```yaml
lights: !include_dir_merge_named lights
```

**注意:不管是哪种涉及调用整个外部目录下文件的方法,该目录下的所有文件都将被递归调用,包括任何子目录下的文件。**

###**②!secret** 调用secret.yaml文件中的私密变量
**用法举例:**
```yaml
api_password: !secret APIPWD
```
**解读:**使用secret.yaml文件中的字典项APIPWD: hassbian作为api_password的值,别忘了在secret.yaml中包含如下代码:
```yaml
APIPWD: hassbian
```
**注意:**如果你在某个yaml文件中使用!secret,那么HA将首先查找该yaml文件同目录下的secret.yaml并进行调用,若无法调用成功,则会查找上一级目录下的secret.yaml并进行调用,直至查找到HA配置文件的根目录(configuration.yaml所在的目录)。你可以通过这种方法来设置该私密变量的有效范围。
###**③!env_var** 调用系统环境变量
**用法举例:**
```yaml
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架构和组件](https://bbs.hassbian.com/thread-3408-1-1.html)

##前置知识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 }}` 返回 `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,这看上去有点反人类不是么。原因很简单:在`{{ ... }}`中通过滤镜操作变量更加方便简洁,我们往往无法对变量进行直接运算。
**这么写是不行的**:
```yaml
value_template: '{{ value / 1000 |round(1) }}'
```
**要写成这样才行**:
```yaml
value_template: '{{ (value|int) / 1000 |round(1) }}'
```

##变量检验
除了用滤镜修正变量,我们还用检查器检验变量。
检验通常在if语句块中进行,方法为在变量后加上一个 is 以及检查器的名字。
Jinja内置检查器有很多,下面举几个常用的检查器:

- defined:检验变量是否定义
- lower:检验变量是否为小写
- mapping:检验变量是否为字典
- sequence:检验变量是否为列表
- number:检验变量是否为数字
- even:检验变量是否为偶数
- divisibleby('除数'):检验变量是否能被除数整除

例子:
```yaml
{% 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 %}`将转义内容包括进来
```yaml
{% 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 %}"`
- 多行模板代码外面不需要用引号。
#持续更新中


咸味土豆 发表于 2018-4-8 08:17:42

这种帖子对我这种不懂编程的小白来说,技术含量很高啊,楼主太谦虚了,谢谢分享。

发表于 2018-4-8 08:32:06

强烈感谢楼主的巨大贡献!

iLee 发表于 2018-4-8 08:54:06

很到位:victory:

潇洒哥er 发表于 2018-4-8 10:23:47

写得非常好 666 ,我懒得去找资料学习了。看楼主的介绍就可以了解了,期待楼主下回发个分析查看日志找问题的帖子

vcprograme 发表于 2018-4-8 11:59:13

总结的很好,适合我们小白

wqqs 发表于 2018-4-8 13:53:58

这种帖子太难得了,必须顶

yuandong8110 发表于 2018-4-8 20:08:47

萌新老师好!
希望多多出教程!!!!

令狐鸣 发表于 2018-4-8 21:03:20

这个基础太赞了:lol

freexx 发表于 2018-4-8 23:16:24

make一下
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 【YAML+Jinja】教程与经验,庆祝论坛人数过万,解除回帖可见