Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save willwhui/c45f09a54a52dc3d9cd839b10aef3338 to your computer and use it in GitHub Desktop.
Save willwhui/c45f09a54a52dc3d9cd839b10aef3338 to your computer and use it in GitHub Desktop.
在hass上检测通过Google Home生成的Google Calendar Event
@willwhui
Copy link
Author

willwhui commented Nov 12, 2017

目的:
"OK Google, set an event ..."
等到时间到了之后,google home会提醒

基本思路:
检查google home在google calendar上新建的相关事件
等到事件触发之后,利用tts来发声
利用hass的timer做到不停地循环发声
设定和特定的设备的标记状态,用来打破这个循环

@willwhui
Copy link
Author

willwhui commented Nov 12, 2017

在hass中检测google calendar

官方文档:https://home-assistant.io/components/calendar.google/

注:
google calendar 的api文档:https://developers.google.com/google-apps/calendar/concepts/
可以获取event(和reminder?)

按照hass官方文档配置

完成配置后,控制台消息输出出现错误:
[oauth2client.client] Failed to retrieve access token: b'{\n "error": "authorization_pending",\n "error_description": "Bad Request"\n}\n'

但是注意:在hass前端出现一个卡片:“Google Carlendar”,说需要在google的一个device界面输入卡片上给出的一个密码

you must visit: https://www.google.com/device and enter code: xxxx-xxxx

按照这个卡片上的指引进行:原来是一个连接设备(树莓派)-账户(用户账户)的步骤指引。
完成操作,然后日历就连接成功了。

@willwhui
Copy link
Author

willwhui commented Nov 28, 2017

到底引入了什么日历?

前端看到的日历和我在google上的日历数量不一样。
配置文件目录中的google_calendars.yaml文件中的日历配置信息是这样的:

- cal_id: xxx1@gmail.com <----------- 这个应该对应“默认”,但没有出现在前端卡片中
  entities:
  - device_id: ''  <---- device id为空,正常配置的话,是应该设定,系统会自动作为sensor id用的。
    name: "\u9ED8\u8BA4"<---- “默认”两个字的某种编码结果(unicode?)
    track: true

- cal_id: xxx2@group.calendar.google.com <----- 这个应该是我的一个自定义日历“工作提醒”,没有出现在前端卡片上
  entities:
  - device_id: ''  <---- device id为空,正常配置的话,是应该设定,系统会自动作为sensor id用的。
    name: "\u5DE5\u4F5C\u63D0\u9192"
    track: true

- cal_id: china__en@holiday.calendar.google.com <---- 系统提供的日历,出现在前端卡片上了
  entities:
  - device_id: holidays_in_china <---- 这里被用作sensor id
    name: Holidays in China
    track: true

- cal_id: zh-cn.china#holiday@group.v.calendar.google.com <---- 系统提供的日历,出现在前端卡片上了
  entities:
  - device_id: ''  <---- device id为空,正常配置的话,是应该设定,系统会自动作为sensor id用的。
    name: "\u4E2D\u56FD\u8282\u5047\u65E5"
    track: true

- cal_id: p#weather@group.v.calendar.google.com <---- 系统提供的日历?出现在前端卡片上了。但我在google日历管理界面上并不能看到它
  entities:
  - device_id: weather_  <---- 这里被用作sensor id
    name: "Weather: \u5175\u5EAB\u770C\u8C4A\u5CA1\u5E02"
    track: true

莫非是因为device_id没有写?
从state选项卡看,只有一个unnameed sensor:calendar.unnamed_device
莫非是最后一个unnamed device覆盖了之前的?
试试看。
果然,为每个空的device_id设定一个英文名字,就好了。

并且,上面的“name”使用的汉字编码结果,也可以直接改为汉字,这样维护其来会方便些。

如果增加了新的日历,重启hass,会自动更新这个文件。
这个文件中手动修改的地方,hass不会去改它。
如果删除了已有日历,重启hass,hass不会更新它。

@willwhui
Copy link
Author

willwhui commented Nov 28, 2017

先track一个event

通过测试发现,通过google home设定的event是出现在默认的日历里面的。

设定好要监控的日历event:

- cal_id: xxx1@gmail.com # calendar "default"
  entities:
  - device_id: 'take a walk'
    # name: "\u9ED8\u8BA4"
    name: "to take a walk"
    search: "take a walk"
    track: true

设定好automation:

- alias: 'TTS on event : take a walk'
  trigger:
    platform: state
    entity_id: calendar.take_a_walk
    from: 'off'
    to: 'on'
  action:
    service: tts.google_say
    entity_id: media_player.living_room_home
    data:
      message: "Time to take a walk, human!"

重启hass。
时间到了之后,什么都没发生。
哪里出了错?

但是到了预定触发时间点之后的1分钟,触发了!

# the event should start at 18:07:00

backend output log:

Nov 28 18:08:53 ... Bus:Handling <Event state_changed[L]: old_state=<state calendar.take_a_walk=off; start_time=None, location=None, all_day=False, message=, end_time=None, offset_reached=False, friendly_name=to take a walk, description=None @ 2017-11-28T17:53:34.752647+08:00>, entity_id=calendar.take_a_walk, new_state=<state calendar.take_a_walk=on; start_time=2017-11-28 18:07:00, location=, all_day=False, message=take a walk, end_time=2017-11-28 18:37:00, offset_reached=False, friendly_name=to take a walk, description= @ 2017-11-28T18:08:53.297229+08:00>>
Nov 28 18:08:53 ... Bus:Handling <Event state_changed[L]: old_state=<state group.calendar=off; auto=True, hidden=True, friendly_name=calendar, assumed_state=False, entity_id=('calendar.take_a_walk',), order=5 @ 2017-11-28T17:53:34.758268+08:00>, entity_id=group.calendar, new_state=<state group.calendar=on; auto=True, hidden=True, friendly_name=calendar, assumed_state=False, entity_id=('calendar.take_a_walk',), order=5 @ 2017-11-28T18:08:53.304413+08:00>>
Nov 28 18:08:57 ... Bus:Handling <Event state_changed[L]: old_state=<state automation.tts_on_event__take_a_walk=on; friendly_name=TTS on event : take a walk, last_triggered=None @ 2017-11-28T17:53:34.808990+08:00>, entity_id=automation.tts_on_event__take_a_walk, new_state=<state automation.tts_on_event__take_a_walk=on; friendly_name=TTS on event : take a walk, last_triggered=2017-11-28T18:08:57.812445+08:00 @ 2017-11-28T17:53:34.808990+08:00>>

尝试说出中文

google tts号称支持的语言:
https://cloud.google.com/speech/docs/languages
但里面的“cmn-Hans-CN”这样的写法并不被google支持,会报错。

上文链接中说用到了这里的规定:
https://tools.ietf.org/html/bcp47

For example, the macrolanguage Chinese ('zh') encompasses a number of
languages. For compatibility reasons, each of these languages has
both a primary and extended language subtag in the registry. A few
selected examples of these include Gan Chinese ('gan'), Cantonese
Chinese ('yue'), and Mandarin Chinese ('cmn'). Each is encompassed
by the macrolanguage 'zh' (Chinese). Therefore, they each have the
prefix "zh" in their registry records. Thus, Gan Chinese is
represented with tags beginning "zh-gan" or "gan", Cantonese with
tags beginning either "yue" or "zh-yue", and Mandarin Chinese with
"zh-cmn" or "cmn". The language subtag 'zh' can still be used
without an extended language subtag to label a resource as some
unspecified variety of Chinese, while the primary language subtag
('gan', 'yue', 'cmn') is preferred to using the extended language
form ("zh-gan", "zh-yue", "zh-cmn").

那么language参数的正确写法,中文很可能是"language:zh"
所以可以把action的data改成这样:

  action:
    service: tts.google_say
    entity_id: media_player.living_room_home
    data:
      message: "嗨!该站起来走走了!"
      language: "zh"

结果证明是可以的。

通过google搜索还可以找到这里的使用案例:pndurette/gTTS#31

@willwhui
Copy link
Author

willwhui commented Nov 29, 2017

利用timer组件达到TTS重复发声的目的

timer官方文档: https://home-assistant.io/components/timer/
设计思路:

  • automation1: "TTS on event (speak and start timer) "
    trigger: 当日历事件
    action:
    触发automation之后发声,
    并触发一个timer: timer_for_repeat_tts_on_google_calender_event
    并设定一个标记为on
  • automation2: "TTS on timer (speak and start timer again)"
    trigger: timer_for_repeat_tts_on_google_calender_event
    condition: 标记是否已经off
    action:
    发声
    并重启timer_for_repeat_tts_on_google_calender_event

那么,如何设定一个可以on/off的标记呢?

设计思路:
在automation1中设定一个标记,比如一个智能插座(或灯)。
这个标记将成为automation2的执行条件
手动关闭这个插座或灯,就可以结束automation2导致的循环

@willwhui
Copy link
Author

设置timer

timer的配置文件和常见的书写方式略有不同:
设置多个timer,必须得这样:

timer_for_repeat_tts_on_google_calender_event:
  duration: '00:01:00'
timer_for_repeat_tts_on_google_calender_event_2:
  duration: '00:01:00'

不能用 "-"那种模式

@willwhui
Copy link
Author

willwhui commented Nov 29, 2017

最终完成重复TTS配置如下

timer:

timer_for_repeat_tts_take_a_walk:
  duration: '00:00:10'

automation:

################################################################
# begin : automation for "stop working, take a walk"
- alias: "TTS on event (speak and start timer): stop working, take a walk"
  trigger:
    platform: state
    entity_id: calendar.start_work
    from: 'on'
    to: 'off' # stop working
  action:
    - service: tts.google_say
      entity_id: media_player.living_room_home
      data:
        message: "Time to take a walk, human!"
    - service: switch.turn_on # set the signal on
      entity_id: switch.orange_orvibo_wifi_socket
    - service: timer.start # start the timer for repeat
      entity_id: timer.timer_for_repeat_tts_take_a_walk

- alias: "TTS on timer (speak and start timer again): stop working, take a walk"
  trigger:
    platform: state
    entity_id: timer.timer_for_repeat_tts_take_a_walk
    to: 'idle' # time up
  condition:
    condition: state
    entity_id: switch.orange_orvibo_wifi_socket
    state: 'on' # repeat if signal is on
  action:
    - service: tts.google_say
      entity_id: media_player.living_room_home
      data:
        # message: "Time to take a walk, human!"
        message: "喂!够了够了,必须站起来走走了!"
        language: "zh"
    - service: timer.start # start this timer again
      entity_id: timer.timer_for_repeat_tts_take_a_walk
# end : automation for "stop working, take a walk"
################################################################

@willwhui
Copy link
Author

willwhui commented Dec 1, 2017

不足的地方:

  • event 下发不及时
    说检查状态的间隔时间是15分钟
  • event 无法在通过google home设定时间周期,总是默认1小时长度
    最终发现,在这个案例中,可以通过设置开始时间为 "20 minutes ago"来控制总时长
  • event 状态有时候不更新
    原因未知。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment