はじめに
皆さんは Nature Remo というデバイスをご存知でしょうか?
これは赤外線方式のリモコンを備えた家電をスマートフォンのアプリやスマートスピーカーから操作することができるようになる製品です。
この Nature Remo は Web API を公開しています。
つまり Nature Remo を使用すると curl コマンドや任意のプログラミング言語で家電を操作できるということです。
この Web API を活用してブラウザから家電を操作できるようにすると PC の前から離れずに生活することができて便利そうなので、実際にそんな仕組みを作ってみました。
私はあまり UI を作るのが得意ではないので UI は自作せず microCMS の管理画面を UI として使うことにしました。
microCMS は本来外部の Web API と直接やりとりするような仕組みを備えていませんが、コンテンツの更新をトリガーとした Webhook 機能を持っています。
Web API を叩くコードを実装して Webhook サーバを用意すると、間接的に任意の Web API を microCMS から操作できることになります。
今回はこのサーバを Go 言語で実装し、 AWS AppRunnner 上で動作させることにしました。
簡易的なものですが、構成図としては以下のようになります。
実際に動作させた様子が以下の動画になります。
ブラウザを操作して部屋の照明をON/OFFしています。
microCMS の管理画面から家電を操作できるようになりました pic.twitter.com/yAwv01vFn9
— λ沢 (@lambdasawa) March 15, 2022
ここからは実際にどんな作業をすればこの仕組みを構築できるか解説していきます。
構築
AWS AppRunner にサーバ(仮実装)をデプロイ
AWS AppRunner は AWS 上でコンテナを実行、 HTTP リクエストを受け付けるための最も簡単な手段だと思います。
今回はこれを使用します。
まずは Webhook を受け付けてそのままログ取得するようなコードをデプロイしてみましょう。Dockerfile
を以下のように記述します。
FROM golang
WORKDIR /app
EXPOSE 8888
ADD go.mod go.sum ./
RUN go mod download
ADD . ./
RUN go build -o main
ENTRYPOINT [ "/app/main" ]
main.go
を以下のように記述します。
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
log.Println("Start")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
reqBody, _ := ioutil.ReadAll(r.Body)
println(string(reqBody))
fmt.Fprintf(w, "OK")
})
log.Fatal(http.ListenAndServe(":8888", nil))
}
AWS Copilot CLI を使うことによって以下のようなコマンドでこのコンテナを AppRunner にデプロイすることができます。--app
, --name
はお好みの名前に変更してください。
$ copilot init --app go-docker --name go-docker --type "Request-Driven Web Service" --deploy --dockerfile ./Dockerfile
デプロイが成功するとターミナルに以下のようなログが表示されます。
Recommended follow-up action:
- You can access your service at https://xxxx.ap-northeast-1.awsapprunner.com over the internet.
この URL を後ほど microCMS の Webhook URL として登録することになります。
microCMS で API 作成
家電操作の UI として microCMS を利用する際の最適な API スキーマは家電によって異なると思います。
私の家のリビングには照明とエアコンが1つずつあり、それぞれ Nature Remo がセットアップ済みです。
照明は単純に ON/OFF ができれば十分で、エアコンは ON/OFF、モード(冷房or暖房)、風向き、風の強さ、温度を制御したいと思ったため、以下のように API スキーマを定義しました。
家電ごとに行える操作は異なりますが、 Nature が公開している OpenAPI スキーマ を読むとどんな操作が可能なのか分かります。
特に以下の API が今回注目するべきものです。POST /1/appliances/{appliance}/light
POST /1/appliances/{appliance}/aircon_setting
POST /1/appliances/{appliance}/tv
microCMS で Webhook 設定
以下のように Webhook の設定を行うことによって microCMS と AppRunner の繋ぎこみを行います。
今回のユースケースでは通知タイミングは「コンテンツ編集画面による操作」だけチェックされていれば十分です。
シークレットの設定も行い、後ほどシークレットの検証コードも書くことをおすすめします。
もしシークレットを検証せず誰でも AppRunner の Webhook を叩けるようになってしまった場合、悪意を持った人が真夏に30度の暖房を設定しようとするかもしれません。
Nature Remo のアクセストークンを取得
home.nature.global からアクセストークンを取得可能です。
アクセストークンを取得したら AppRunner にそれを環境変数として設定する必要があります。
これは AWS のコンソール画面から簡単に設定することができます。
Nature Remo の OpenAPI から SDK を生成
ここまでで各種設定作業は一通り完了しました。
Nature Remo の API を叩くためには何かしらのクライアントライブラリがあると便利です。
今回は OpenAPI からコード生成をすることにしました。
以下のようなコマンドでコード生成をすることによって API 仕様とコードを見比べながらクライアントコードを実装する手間が省けます。
$ curl -sSLO https://swagger.nature.global/swagger.yml
$ openapi-generator generate -i swagger.yml -g go -o openapi/nature/ --additional-properties=enumClassPrefix=true
サーバのロジックを実装
あとは microCMS から送られた Webhook を受け取って、 Nature Remo の API を叩くコードを書くだけです。
今回定義したスキーマの API から送られる Webhook イベントをパースできる Go の struct
は以下のようになります。
このような struct
は手書きせず Quicktype などを使って実際の JSON から生成すると簡単に作れます。
type WebhookEvent struct {
API string `json:"api,omitempty"`
Contents Contents `json:"contents,omitempty"`
}
type Contents struct {
New New `json:"new,omitempty"`
}
type New struct {
PublishValue PublishValue `json:"publishValue,omitempty"`
}
type PublishValue struct {
LightOn bool `json:"lightOn,omitempty"`
AirconOn bool `json:"airconOn,omitempty"`
AirMode []string `json:"airMode,omitempty"`
AirDir []string `json:"airDir,omitempty"`
AirVol []string `json:"airVol,omitempty"`
Temp float64 `json:"temp,omitempty"`
}
AirMode
などは管理画面上では日本語で表示すると分かりやすいですが、 Nature Remo の API を叩く時は warm
または cold
という値でやりとり必要があります。
この変換を行うヘルパーメソッドもついでに定義すると便利です。
func (value PublishValue) AirModeValue() string {
switch v := value.AirMode[0]; v {
case "暖房":
return "warm"
case "冷房":
return "cool"
default:
return v
}
}
func (value PublishValue) AirDirValue() string {
switch v := value.AirDir[0]; v {
case "自動":
return "auto"
case "スイング":
return "swing"
default:
return v
}
}
func (value PublishValue) AirVolValue() string {
switch v := value.AirVol[0]; v {
case "自動":
return "auto"
default:
return v
}
}
この struct
を使って Webhook イベントを以下のようにパースできます。
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// ...
event := &WebhookEvent{}
_ := json.Unmarshal(r.Body, &event)
// ...
})
この event
オブジェクトを元に実際に家電の操作を行う API を叩きます。
照明を操作する場合は以下のようになります。
appliances, _, _ := api.Call1AppliancesGet(ctx).Execute()
var light string
for _, a := range appliances { // 家電の一覧でループ
if *a.Nickname == "リビングの照明" { // 名前で家電を特定する
light = *a.Id
}
}
if value.LightOn == true {
// 照明を ON にする
api.Call1AppliancesApplianceLightPost(ctx, light).Button("on").Execute()
} else {
// 照明を OFF にする
api.Call1AppliancesApplianceLightPost(ctx, light).Button("off").Execute()
}
エアコンを操作する場合はもう少し複雑になります。
appliances, _, _ := api.Call1AppliancesGet(ctx).Execute()
var aricon string
for _, a := range appliances { // 家電の一覧でループ
if *a.Nickname == "リビングのエアコン" { // 名前で家電を特定する
aricon = *a.Id
}
}
if value.AirconOn == true {
api.
Call1AppliancesApplianceAirconSettingsPost(ctx, aircon).
OperationMode(value.AirModeValue()). // 暖房 or 冷房
AirDirection(value.AirDirValue()). // 風向き
AirVolume(value.AirVolValue()). // 風量
Temperature(fmt.Sprint(value.Temp)). // 温度
Execute()
} else {
api.
Call1AppliancesApplianceAirconSettingsPost(ctx, aircon).
Button("power-off").
Execute()
}
終わりに
以上の作業でブラウザから管理画面を操作できるようになりました。お疲れ様でした!
今回のような microCMS の利用方法はマイナーですが、実用的でもあります。
ぜひ皆さんも microCMS の面白い利用方法を思いついたらツイートしたり、ブログに書いたりしていただけると嬉しいです。