Alexa非対応テレビをAlexa対応にする
※ この記事の前提としてTV(ディスプレイ)を赤外線で操作できる必要があります
FireTVを買ったので「Alexa、テレビつけて」で起動してもらいたかったのだが、「デバイスが応答ありません」と言われ起動できなかった。
接続しているのがTVではなく液晶ディスプレイなので、対応する赤外線プロファイルが存在せずFireTVから操作ができないらしい。
幸いなことに赤外線リモコン付きのディスプレイなので、IRKitを介せばAlexa経由で操作ができるはず。
ということで下記記事を参考にさせていただき、Alexa非対応テレビをAlexa対応にしてみた。
記事にもあるようにIRKitのIFTTTを使えば手軽に連携できるが「Alexa、テレビをトリガー」だと使いにくいので「Alexa、テレビつけて」で起動できるようにする。
Alexaスキル作成
- Alexa Developer Consoleで新規にスマートホームスキルを作成
- 参考記事に沿って自分専用スキルとしてアカウントリンクを実装
- Lambdaとの連携後に「有効なスキル」に表示されるようになる
Lambda実装
参考記事のコードをベースにTVと暖房を追加した。
また、IFTTTの代わりに家のRaspberryPiにIRKit操作用のAPIを用意してリクエストを送るようにした。
Lambdaから家のRaspberryPiへのアクセスにはinletsを使って対応した。
Host名+keyだけのURLだと推測しやすいので、適当に生成したAPI_TOKENをURLに含めるようにした。
生成したAPI_TOKENはLambdaの環境変数に設定しておく。
exports.handler = (request, context) => {
switch(request.directive.header.namespace) {
case 'Alexa.Discovery' :
handleDiscovery(request, context)
break
case 'Alexa.PowerController' :
handlePowerControl(request, context)
break
}
}
function requestPi(key) {
let https = require("https")
return new Promise((resolve, reject) => {
https.get(`https://{raspberrypi_api_host}/${process.env.API_TOKEN}/${key}`, res => resolve())
.on('error', error => reject(error))
})
}
function handlePowerControl(request, context) {
let powerResult = ""
let requestResult
const endpointId = request.directive.endpoint.endpointId
switch(request.directive.header.name) {
case 'TurnOn' :
requestResult = requestPi(`${endpointId}_on`)
powerResult = "ON"
break
case 'TurnOff' :
requestResult = requestPi(`${endpointId}_off`)
powerResult = "OFF"
break
}
requestResult.then(() => {
var responseHeader = request.directive.header
responseHeader.namespace = "Alexa"
responseHeader.name = "Response"
responseHeader.messageId = responseHeader.messageId + "-R"
var response = {
context: {
"properties": [{
"namespace": "Alexa.PowerController",
"name": "powerState",
"value": powerResult,
"timeOfSample": (new Date()).toISOString(),
"uncertaintyInMilliseconds": 50
}]
},
event: {
header: responseHeader,
endpoint: {
scope: {
type: "BearerToken",
token: request.directive.endpoint.scope.token
},
endpointId: endpointId
},
payload: {}
}
}
context.succeed(response)
})
}
function handleDiscovery(request, context) {
switch(request.directive.header.name) {
case 'Discover' :
let header = request.directive.header;
header.name = "Discover.Response";
let payload = {
"endpoints": [
{
"endpointId": 'irkit_tv',
"manufacturerName": "IRKIT",
"friendlyName": "テレビ",
"description": "テレビのスイッチ",
"displayCategories": ["TV"],
"capabilities": [
{
"type": "AlexaInterface",
"interface": "Alexa",
"version": "3"
},
{
"interface": "Alexa.PowerController",
"version": "3",
"type": "AlexaInterface",
"properties": {
"retrievable": true
}
}
]
},
{
"endpointId": 'irkit_heater',
"manufacturerName": "IRKIT",
"friendlyName": "暖房",
"description": "暖房のスイッチ",
"displayCategories": ["THERMOSTAT"],
"capabilities": [
{
"type": "AlexaInterface",
"interface": "Alexa",
"version": "3"
},
{
"interface": "Alexa.PowerController",
"version": "3",
"type": "AlexaInterface",
"properties": {
"retrievable": true
}
}
]
}
]
}
context.succeed({
event: {
header: header,
payload: payload
}
})
break
case 'Alexa.PowerController' :
handlePowerControl(request, context)
break
}
}
IRKit操作api実装
Lambdaから飛んでくるリクエストに応じてIRKitの赤外線送信を出し分けるAPIを実装。
Lambdaのテスト実行で正しくリモコン操作ができることを確認。
const express = require('express')
const exec = require('child_process').exec
const app = express()
const TOKEN = process.env.API_TOKEN
app.get(`/${TOKEN}/irkit_tv_on`, (req, res) => {
// display on
exec('curl -i -s -X POST "http://{irkit_host}/messages" -H "X-Requested-With: curl" -d \'{"format":"raw","freq":xx,"data":[xxxx]}\'')
res.send('ok')
})
app.get(`/${TOKEN}/irkit_tv_off`, (req, res) => {
// display off
exec('curl -i -s -X POST "http://{irkit_host}/messages" -H "X-Requested-With: curl" -d \'{"format":"raw","freq":xx,"data":[xxxx]}\'')
res.send('ok')
})
app.get(`/${TOKEN}/irkit_heater_on`, (req, res) => {
// heater on
exec('curl -i -s -X POST "http://{irkit_host}/messages" -H "X-Requested-With: curl" -d \'{"format":"raw","freq":xx,"data":[xxxx]}\'')
res.send('ok')
})
app.get(`/${TOKEN}/irkit_heater_off`, (req, res) => {
// heater off
exec('curl -i -s -X POST "http://{irkit_host}/messages" -H "X-Requested-With: curl" -d \'{"format":"raw","freq":xx,"data":[xxxx]}\'')
res.send('ok')
})
app.listen(3000, () => console.log('app listening on port 3000!'))
これで「Alexa、テレビつけて」でテレビがつくようになり、ついでに「Alexa、暖房つけて」で暖房も操作できるようになった。
「Alexa、テレビつけて」
「Alexa、XXXを再生して」
「Alexa、テレビ消して」
という基本的な操作は音声のみで完結できるようになって快適。
今後は、同様にしてデバイスを追加していけば機能拡張できる。
以上でAlexa非対応テレビ(ディスプレイ)をAlexa対応にできた。