## AppDaemon入门
 
## 写在前面

本教程根据appdaemon 3.0版本重写。如果你安装的是旧版本，本人建议你升级。


## 引言

任何你能想得到的HomeAssistant自动化都可以用AppDaemon实现。
那么问题来了：“HomeAssistant本身就可以为撒我要用AppDaemon呢？”
 
就我个人而言：“本人对python略知一二，但是对那些用来配置Home Assistant的文件格式比如像yaml、jinjia、json等缺知之甚少。不是说这些格式很难懂，个人觉得如果要写略微复杂的自动化脚本的话，它们就不能算很易用了。”
 
因此如果你对yaml、jinjia语言很熟悉的话就没必要转到appdaemon，除非你写配置文件遇到了瓶颈。
 
现在你明白我为什么要用AppDaemon了，那么我们就开始讲些基础知识吧。

首先要聊一聊appdaemon 3的架构。
在appdaemon.yaml里我们可以设置app的文件夹路径。如果你不设置那默认位置在conf/apps（appdaemon.yaml文件在conf文件夹下）。该文件夹路径非常重要，因为我们只能在此文件夹下创建我们的app。
每个app都保护两个部分：
- 一个py文件，包含了我们的代码。（如something.py）
- 一个yaml文件，包含我们app的配置信息。（如something.yaml）
一个app可以有许多实例配置。（重复使用py文件只需在yaml文件中添加另一个配置实例即可）

所以现在当我讲到什么py文件或者yaml文件你就懂我在说什么了。
别被吓到，我会教你怎么折腾。;)
 
## 我们的第一个App
 
让我们先思考下到底什么叫App。以前都叫程序，然后才有了App应用。App和程序没什么大区别，都可以用来生成文字、编辑图片等等。
在AppDaemon里的App也一样，用几行代码去做一个特定的实现。
对HA来说，App就是自动化。但是不止于此。说不定你希望记录一个温度传感器的每一度变化并存入文件，或者你又想对所有种类的传感器进行计算并把结果写进一个新传感器內。
使用App的话一切皆有可能。你想把传感器数值导出至excel表格吗？没问题。
你想记录加热器一共开了多久从而算出用掉多少能耗吗？一样没问题。就像我说的但凡你想的到就能做得到。
 
现在就开始吧。
我猜你一定已经开始使用Home Assistant了吧，否则也不会读这篇教程了。;)而且你也应该已经在Home Assistant里已写过自动化了吧（如果没有的话也该开始写了:wink:）
在HA(Home Assistant)里你的自动化脚本大概长这样：

```  
automation:
  - alias: " Turn on light when input boolean is switched on and someone is home"
  trigger:
    platform: state
    entity_id: input_boolean.some_choice
    state: 'on'
  condition:
    condition: state
    entity_id: device_tracker.some_mobile
    state: 'home'
action:
    service: light.turn_on
    entity_id: light.some_light
```

这是一个很简单的自动化实现。当你打开一个叫some choice的布尔输入后自动开启一个叫some light的设备，不过只有在某特定时段，some mobile这个设备在家的时候才会起作用。
 
我觉得这已经基本了不能再基本了吧？
现在我们把这段转换成AppDaemon的形式。
 
```
import appdaemon.plugins.hass.hassapi as hass
 
class your_class_name(hass.Hass):
 
  def initialize(self): 
    self.listen_state(self.what_we_want_to_do,"input_boolean.some_choice", new="on")
	
  def what_we_want_to_do (self, entity, attribute, old, new, kwargs):
    a_variabele_with_a_usable_name = self.get_state("device_tracker.some_mobile")
    if  a_variabele_with_a_usable_name == "home":
      self.turn_on("light.some_light")
 ```
  
一个完整的App就是这样。看不懂也别害怕，我会循序渐进的解释每一句语句的意思，如果你还能坚持读下去的话。
我知道你们觉得编程很难，其实一点也不难。就像是在学外语一个道理。如果你到了另一个国家又不会说当地语言而你又想喝一杯的话你一定会去学怎么说。下一趴无非就是学会怎么点一杯酒并且怎么畅饮而已。等到吃饱喝足了我们还能学学怎么问路，不知不觉地说不定你都会用当地语言给人指路了。在你质疑自己“学外语我完全不在行”之前，我要说在学校我语言天赋属于最最差的了，而如今我住在国外说着3种语言。根本不是看书看会的，都是用着用着用会的。
一开始你可能会犯很多错误，别去管它，这事情正常的很。每时每刻都有人在犯错。诀窍就是怎么辩识出自己的错误并学会怎么避免再犯。大道理就不讲了，回过头说我们之前的App。
 
## 通用部分：
 
首先我们从所有app都必须的部分开始：

```
import appdaemon.plugins.hass.hassapi as hass
 
class your_class_name(hass.Hass):
 
  def initialize(self): 
 ```
 
任何的python脚本的最顶头都要import一些**库.:two:**，就好比你要写篇文章，总得找些和文章主题相关或者对文章主题有帮助的资料和书籍。这些书籍的部分内容是等会要用的。
当然你现在只用采集对你有用的，如果在写作过程中又发现其他书籍也有用，再放进去也没问题。python中也一样，写着写着你发现要用datetime这个库了，那就把import datetime写在其他import下面就行了。
 
第二行里我们定义了一个**类:two:**，这里把这个类命名为“your_class_name”（“你的类名”）。名字随便起，重要的是你自己看得懂意思，最好别人也看得懂意思。
名字后面的括号里是**hass.Hass:one:**，但凡在AppDaemon里定义类都用它。这里不赘述类在python里什么概念了，自己百度python+类，教程很多。
  
第三行我们创建了一个函数。你肯定要问：什么函？什么数？简单说就是app里一个小动作。比如你要反复合计2个数据。那就写个函数叫合计，这个函数返回合计的数据。调用起来就是addup(5,7)
编程中每当有需要反复操作的动作时我们就会使用函数。有时候我们函数是从其他地方调用的。我们import的所有库包含了我们可以使用的函数。
你或许想知道函数怎么用来组织代码或者其他程序如何调用特定名称的函数。这些暂时不管了。反正就像你写文章放了一个其他文档的链接，读者也能看得到，不用你反复写一模一样的东西。常用函数就像文后索引，找书中的内容用的。而章节就是那些我们用来组织代码的函数，只不过引用了很多次罢了。
光这个app里我们会创造2个函数，并调用多次。第一个用来初始化我们的app，第二个包含我们要做的动作。
 
所有函数第一行都长这样：**def:two:** any_name(some, variables, you, want, to, pass, to, the, function):（定义 随便什么名字(需要, 传递, 的, 参数们)）。initialize（初始化函数）是每个app都需要的一个特殊函数。任何在这个函数里被创建的行为，在你启动app那一时刻就完成了。
 
所以这一趴还算简单。这3行复制黏贴就行了，最多你改下类的名称。
 
## 触发器：
 
这一趴开始就比较皮了。
``` 
self.listen_state(self.what_we_want_to_do,"input_boolean.some_choice", new="on")

self.状态监听(要做的事情, 被监听的设备, 新状态=开)
```
在你复制这条代码前先等下！在编辑yaml时要检查缩进，python里也一样。如果a附属于b，就像一个函数附属于某个类，或者一句申明附属于某个函数，你就得缩进。同样的缩进量为同一个组别。空格随便多少，但是同组别的申明空格数必须一样。
 
所以initialize下一行就得有缩进。
撒？你还想知道这是撒意思？好吧，因为你就是那么有求知欲。
 
我们从**self:two:**部分开始。这不是很好解释，不过基本上就是说这部分是附属于class（类）的组别。总之如果你想在AppDaemon内创立什么东西你就要用到self。**listen_state:one:** （状态监听）部分是你去调用AppDaemon里的另一个函数（用def定义）。由于这个类属于AppDaemon所以你可以用self去调用所有你在AppDaemon里创建的函数。
 
你没听错。listen_state是一个函数，和之前的initialize还有以后我们将会用到的更多函数一样。因此括号内我们发现有一些变量。 
第一个变量是listen_state当有事情出发时需要执行的函数名称。
在appdaemon的api文档里被叫做**callback:one:**（回调函数）。
除了设置我们要做什么外，我们还要设置什么时候去做。
你可以监听HA中任何变化。开关被拨动咯，传感器改变值咯，设备到家咯等。
在我们的例子中我们要监听的是input_boolean.some_choice（布尔输入.你的选择）
如果你不加其他参数的话，你可以监听这个实体的任何变化。这种情况下，函数"self.what_we_want_to_do"（“self.要做的事情”）就会被触发执行，不管你是把input Boolean设置成开还是设置成关。
这样我们决定还是设置只有在new state 新状态是开的时候函数才被调用。
 
这就是yaml中关于trigger触发器的所有对等部分。你一样可以在同一个app里设置很多关触发去指向同一个函数，或者创建其他app或者用其他的函数。个人是尽量把不同需求的触发器分配到不同的app里。
 
## 动作：
 
在触发器我们已经为执行动作选择了一个名字。用的是
self.要做的事情

所以现在要用这名字创建一个函数。
``` 
def what_we_want_to_do (self, entity, attribute, old, new, kwargs):

定义 要做的事(self, 实体, 属性, 老状态, 新状态, 其他参数)
```

我们已经def（定义）和紧跟的名字。
不过后面括号里的是什么？首先又是self部分，这全是让Python知道我们要在AppDaemon里创建东西了。这样你的app现在就附属于AppDaemon了。
然后我们就看到了entity（实体）, attribute（属性）, old（老状态）和new（新状态）。这些就是我们正在监听的设备的所有内容。在这里entity就是我们监听的布尔输入。attribute就是这个布尔值输入的属性（例如friendly name）。Old就是布尔值之前的状态，new就是变化后的新状态。
当然我们因为设置了new = "on"才会触发，所以这些变量值都可以被预测到是什么。
括号内的部分就是复制黏贴所有listen_state的参数。接下来的某个趴里我会向你们展示不同状况下还有其他的方法。
 
顺便说一句别忘了“:”！！这表示命令块的起始，“:”以后得命令都必须多缩进相同的量以表示附属于此函数。
 
慢慢的我们就快完成了。我们给动作命了名，现在做剩下的。
``` 
self.turn_on("light.some_light")
```
这就齐活了。动作也定义了。什么含义呢？又是self！紧跟着一个函数。这个例子中用了**turn_on:one:**（打开）函数。然后我们只要告诉这个函数去打开什么。我们的好朋友Andrew（译者注：Appdaemon作者）创建了很多类似的好用的函数像**turn_off:one:**（关闭），**toggle:one:**（切换）等。我之后还会用到一些，所有的函数都可以在AppDaemon的API手册里找到。
 
我觉得我们准备到运行我们的app了。
 
 
## 状态条件：
 
要死我差点忘了yaml里这部分了。好吧，你根本不需要状态这东西，不过我猜我们还是得用到。
我们回到yaml里看看我们都创建了哪些状态条件。
我们希望只有在some_mobile在家时等才打开。这就需要两样东西。
1) 是要知道我们mobile的状态
2) 要确认这状态是不是和我们期望的一样
 
要获取任何事体的状态非常简单，Andrew为这创建了个函数**get_state:one:**（状态获取）。如果你前面都认真听讲的话你应该知道这行该写点撒了。
```
a_variabele_with_a_usable_name = self.get_state("device_tracker.some_mobile")
```
真的需要我再告诉你等号后面那部分是撒意思吗？？？
不用了吧。不过就是一个函数加上函数需要的变量而已。 
其他还有撒？一个函数返回值赋予的变量名称。任何名称都可以。和函数名一样好记就行。如果你开始读编程有关书籍时你会发现很多范例会用fo, foo, bar这种名字来命名。不要这样搞。因为以后你回看自己代码时需要知道每个变量什么意思来增强可读性。
 
这是第一部分。现在讲第二部分。
我们将会用到所有编程语言都用到的常用声明。写起来稍有区别不过基本都长这样: **"if ... then ...":two:**  
在Python里长这样：
``` 
if  a_variabele_with_a_usable_name == "home":
``` 
就这么多。完全不需要then，用:代替了。我们把设备状态赋值到参数里然后和我们期望的状态做比较。如果声明为真，则if下缩进的行都被执行。
 
所有的内容都合起来：
```
import appdaemon.plugins.hass.hassapi as hass
 
class your_class_name(hass.Hass):
 
  def initialize(self): 
    self.listen_state(self.what_we_want_to_do,"input_boolean.some_choice", new="on")
 
  def what_we_want_to_do (self, entity, attribute, old, new, kwargs):
    a_variabele_with_a_usable_name = self.get_state("device_tracker.some_mobile")
    if  a_variabele_with_a_usable_name == "home":
      self.turn_on("light.some_light")
```
 
我们的app准备好运行了，还有一件事。
我们需要告诉AppDaemon去调用我们的app。为此得复制app到apps文件夹下，路径在配置文件appdaemon.yaml file里有设置。然后给app取名叫，比如叫some_app.py。
Then we make a yaml file in the apps directory (you can use any name for it, but i suggest to use the same name as the py file) to tell AppDaemon to use the app like this:
然后在apps文件夹下创建一个yaml文件（名字可以随便起，不过我建议和py文件名起相同的名字）来告诉AppDaemon如何调用我们的app:
```
our_automation_name:       #随便起名
  module: some_app         #py文件名
  class: your_class_name   #类名
```
 
就这样。我们创建了个app然后让appdaemon调用。继续！在ha里创建一个布尔输入然后用他开灯，不过仅有你的mobile在家时才有效。
 
下一个部分我们再讲更深入的内容。
你也应该搭建个debug平台来debug代码。特别对那些用惯了IDE的人来说有个适应过程。

 
**:one: 这些都是AppDaemon特有词汇，请到API手册查询**   
**:two: 这些词汇用百度配合关键词Python查询**

**译者注：1.括号内翻译旨在帮助理解，写代码时务必照抄英语源代码。2.能谷歌就不百度。**
