やりたいこと
自宅のスマートホーム化計画を進めていますが、Alexaからスマート家電を操作する際の自由度が思いのほか低いなと感じました. 例えば、スマートライトであるHueの単純なON/OFFはアレクサから操作できるが、起床ルーチン (6:00にセットすると5:45からライトがだんだん明るくなる、など) はAlexaから設定できない、など. 私が具体的にやりたかったのは、例えば、
- Amazon Echoに「アレクサ、明日の朝7:00に起こして」とお願いする.
- Philips Hueを6:45から7:00にかけて徐々に明るくする.
- Nature Remo経由で、冷房/暖房を6:30にONにする.
- Amazon Echoのアラームを7:00に設定する.
というもの. 起床時間に対して前もって各家電をONにする、というのが主な目的です. このうち、4. 以外は実現の目処が立ったので作業ログを残します (Alexaカスタムスキル経由でのアラーム設定は現状許可されていないようです). ちなみに、Alexaの定型ルーチンでも似たようなことができるはずですが、以下のような制約が出ると考え、採用しませんでした
- 声 (アレクサ、〜時に起こして) で時刻を設定することができない (Alexa App経由で時刻を変更する必要がある).
- Hueの起床ルーチンを設定できない (指定の時刻になったらいきなり明るくなる).
- 起床時間のXX分前から冷房/暖房をつける、という設定ができない. Alexa Appで起床時刻のXX分前から定型アクションを開始し、冷房/暖房をつけた後、定型アクションをXX分待機させ、その後Hueライトをつける、とすれば似たような挙動にはなるが、起床時刻のXX分前の時刻を計算して設定する必要があり、面倒.
作るものの全体像
これの宅内サーバの部分を作ります. (その1はこちら)
HueライトのAPI初期設定
参考
LAN内のHue BridgeのIPアドレスを取得する.
ブラウザで https://discovery.meethue.com にアクセスすると、IPアドレス情報を含むJSONが得られる.
APIをいじってみるためにCLIP API Debuggerにアクセスする.
ブラウザで https://[上で取得したIP]/debug/clip.html
にアクセスする.
ユーザを作成する.
-
URL:
に/api
、Message Body:
に{"devicetype":"[your-device-name]"}
、を入力してPOSTする. すると、"description": "link button not pressed"
でエラーが返ってくる. - 素直に、Hue Bridgeのボタンをポチッと押して、もう一度同じようにPUTしてみましょう.
- すると
"username": "XXXXXXXXXXXXXXXXX"
と、API利用時に必要なusernameが返ってきます.
API commandの例
URL: | Message Body: | Method | できること |
---|---|---|---|
/api/[your-username]/lights | なし | GET | Hue light一覧を取得できる |
/api/[your-username]/groups | なし | GET | グループ一覧を取得できる |
/api/[your-username]/lights/{light#}/state | {"on":true} | PUT | 指定したライトをオンにする |
/api/[your-username]/groups/{group#}/action | {"on":false} | PUT | 指定したグループをオフにする |
/api/[your-username]/groups/{group#}/action | {"on":true, "transitiontime": 100, "bri": 254} | PUT | 指定したグループを1秒かけて明るさ254(最大)にする |
最後の/groups/{group#}/action
を使って、徐々にライトを明るくします. transitiontimeの単位は100 msecなので、15分かけて明るくしたい場合は9000と設定します.
Node.jsでAPIを叩く方法.
turnOnHueTargetGroup () {
// Hue API.
var hueApi = `http://${hueBridgeIp}/api/${username}/groups/${groupId}/action`
axios
.put(hueApi, {
"on": true,
"transitiontime": Math.round(msecTransTime/100),
"bri": this.brightness
})
.then(function (response) {
console.info(
`[INFO] Hue light API has been invoked at ${new Date()}.`
)
})
.catch(e => {console.log(e)})
}
Nature RemoのAPI初期設定
参考
アクセストークンの生成
https://home.nature.global/
からトークンを生成する.
登録された家電一覧を取得
curl -X GET "https://api.nature.global/1/appliances" -H "accept: application/json" -k --header "Authorization: Bearer ${your-token}" | jq .
以下のようなレスポンスが返ってくるはず.
[
{
"id": "xxxxxxxxxxxxxxx"
"device": {
"name": "リビングのRemo",
"id": "xxxxxxxxxxxxxxx",
"created_at": "2019-02-06T02:09:08Z",
"updated_at": "2019-02-15T22:00:01Z",
"mac_address": "xxxxxxxxxxxxxxx",
"serial_number": "xxxxxxxxxxxxxxx",
"firmware_version": "Remo-mini/1.0.87-g8b06f0e",
"temperature_offset": 0,
"humidity_offset": 0
},
"model": null,
"type": "IR",
"nickname": "エアコン停止用",
"image": "ico_ac_1",
"settings": null,
"aircon": null,
"signals": [
{
"id": "xxxxxxxxxxxxxxx",
"name": "オフ",
"image": "ico_off"
}
]
},
{ ... }
]
この中のsignals -> idがAPI経由で家電を操作するときに必要になります.
API経由で家電を操作
curl
curl -H 'Authorization: Bearer ${your-token}' -H "accept: application/json" -X POST "https://api.nature.global/1/signals/${your-signal-id}/send" -H "accept: application/json" -H "Content-Type: application/x-www-form-urlencoded"
Node.js
turnOnDevice () {
var headers = {
'Authorization': `Bearer ${remoApiToken}`,
'accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
};
axios({
method : 'POST',
url : `https://api.nature.global/1/signals/${deviceOnSignalId}/send`,
headers : headers,
})
.then(function (response) {
console.info(
`[INFO] Nature Remo API for the device has been invoked at ${new Date()}.`
)
})
.catch(e => {console.log(e)})
}
Slackから時刻設定情報を取得する
参考
Slack API tokenの取得
https://api.slack.com/apps
から作成する.
時刻設定情報が投稿されているChannelのIDを取得する.
https://api.slack.com/methods/channels.list/test
から取得する.
Node.js
getSlackPosts () {
axios
.get(`${this.slackApiUrl}?token=${slackApiToken}&channel=${slackChannelId}&count=${slackPostCount}`)
.then(function (response) {
var messages = response.data.messages;
for (var message of messages) {
// 取得したポストの中で、最新の有効な設定 (時刻情報 or OFF) を探す.
if (checkSettingValue(message.text).valid) {
var latestPost = message;
break;
}
else {
continue;
}
}
})
}
宅内サーバで家電のONタイマーを設定する.
コード概要 (Hueライトタイマー部分のみ抜粋)
大まかには、以下のような流れで処理が動きます.
- Slackから時刻設定情報を取得 (例えば6:30、OFFなど).
- transitiontime (設定時刻に対して何分前に家電をONにするか) を考慮して、HueのAPIを叩く時間を計算する. 例えばtransitiontimeが15分の場合、6:15が実際にAPIを叩く時間になる.
- node-scheduleのタイマーの設定を6:15にして登録する.
var axios = require('axios');
var schedule = require('node-schedule');
var ApiScheduler = require('./api-scheduler');
const setting = require('./setting.json');
class WakeUpRoutine extends ApiScheduler {
constructor(setting) {
// 各種設定の読み込み.
...
}
start () {
// 各家電のタイマーを適当な設定で初期化.
this.jobInvokeHue =
schedule.scheduleJob('0 0 0 1 1 0', function() {
this.turnOnHueTargetGroup()
}.bind(this));
// 一旦タイマーをキャンセル.
this.jobInvokeHue.cancel();
// Slackの投稿をチェックするタイマーを設定. (下記 * * * * * は毎分確認)
var jobCheckSlack = schedule.scheduleJob('* * * * *', function() {
axios
.get(`${this.slackApiUrl}?token=${this.slackApiToken}&channel=${this.slackChannelId}&count=${this.slackPostCount}`)
.then(function (response) {
var messages = response.data.messages;
for (var message of messages) {
if (this.checkSettingValue(message.text).valid) {
var latestPost = message;
break;
}
else {
continue;
}
}
...
// 有効な設定情報が得られた場合にタイマーを更新する.
// HueライトのONタイマーを設定
this.setInvokeTimer(
'Hue',
this.jobInvokeHue,
targetTimeHue,
this.msecTransTimeHue,
);
}.bind(this))
.catch(e => {console.log(e)})
}.bind(this))
}
turnOnHueTargetGroup () {
// Hue API.
var hueApi = `http://${this.hueBridgeIp}/api/${this.username}/groups/${this.groupId}/action`
axios
.put(hueApi, {
"on": true,
"transitiontime": Math.round(this.msecTransTimeHue/100),
"bri": this.brightness
})
.then(function (response) {
console.info(
`[INFO] Hue light API has been invoked at ${new Date()}.`
)
})
.catch(e => {console.log(e)})
}
setInvokeTimer (name, timer, targetTime, transitionTime) {
// 設定時間に対してtransitionTimeだけ早くタイマーを設定する.
var [hour, minute] = this.calculateInvokeTime(targetTime, transitionTime)
// タイマーを更新.
timer.reschedule(`${minute} ${hour} * * *`)
console.log(
`[INFO] ${name} timer setting has been updated. New invoke time is ${hour}:${minute}.`
)
}
}
var scheduler = new WakeUpRoutine(setting)
scheduler.start()
コード全体
コンソール出力
Slackの時刻設定情報 (投稿) が更新されたときと、実際にタイマーで設定された時間になってAPIが叩かれたときに通知が出ます.
$ node server.js
[INFO] New target time has been set to 06:30
[INFO] Hue timer setting has been updated. New invoke time is 06:15.
[INFO] New target time has been set to 19:00
[INFO] Hue timer setting has been updated. New invoke time is 18:45.
[INFO] Hue light API has been invoked at Sat Feb 16 2019 18:45:00 GMT+0900 (JST).