LoginSignup
8
3

More than 1 year has passed since last update.

SwitchbotをLINE BOTから操作してみた(Lambda,API Gateway,Go)

Last updated at Posted at 2022-06-30

はじめに

自宅を完全スマートホーム化してみたい!!ということで第一弾はSwitchBotの加湿器を買ってみました。

じゃじゃん!
IMG_4786.jpg

今回はこの加湿器をLINE上からON/OFFできるようにしてみます。

構成

kousei.PNG

準備

LINEアカウント

https://developers.line.biz/ja/docs/messaging-api/getting-started/
を参考にチャネルを作成します。

スクリーンショット 2022-06-29 15.25.57.PNG

「チャネル基本設定」のチャネルシークレットと「Messaging API設定」のチャネルアクセストークンをメモしておきます。

Switchbot

アプリをインストールし、アカウントを作成します。

「デバイスの追加」から加湿器を追加し、好きなように名前をつけます。

IMG_4784.JPG

プロフィールの設定のアプリバージョンを10回くらい連続で押すと、「開発者向けオプション」が表示されます。
トークンを取得してメモしておきます。

IMG_4773.jpg

LINEからLambdaを起動してみる

まずは作成したLINEBOTからメッセージを送信するとLambdaが起動するところを目指します。

Lambda(Go)の作成

関数名とランタイム(Go)を指定し、関数の作成(他の項目はとりあえずデフォルトでOK)

スクリーンショット 2022-06-29 16.06.16.PNG

ランタイム設定のハンドラをmainに変更

スクリーンショット 2022-06-30 18.22.37.PNG

環境変数を追加(先ほどメモした内容を設定します)

  • CHANNEL_SECRET:LINEのチャネルシークレット
  • CHANNEL_TOKEN:LINEのチャネルアクセストークン
  • SWITCHBOT_TOKEN:Switchbotのトークン

コードのデプロイ

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-package.html
を参考にデプロイします。

コードは以下です(Lambdaが起動できているか確認するだけのコード)

package main

import (
	"context"
	"log"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

func HandleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (string, error) {
	log.Printf("やっほ〜")
	return "やっほ〜", nil
}

func main() {
	lambda.Start(HandleRequest)
}

コードができたら以下のコマンドでzipファイルを作成します(Macの場合)

go get github.com/aws/aws-lambda-go/lambda
GOOS=linux go build main.go
zip main.zip main

main.zipをコードソースにアップロード
スクリーンショット 2022-06-30 18.24.39.PNG

API Gatewayの作成

Lambdaの「設定」の「トリガー」からトリガーを追加します。

今回は以下で設定します。

  • APIタイプ:REST API
  • セキュリティ:オープン

スクリーンショット 2022-06-29 16.24.55.PNG

追加したらAPIエンドポイントの値をコピー

IMG_4832.jpg

作成したLINEのチャネルに移動し、Messaging API設定のWebhook URLにAPIエンドポイントをいれ、Webhookの利用にチェックをつけます。

IMG_4833.JPG

作成したチャネルのQRコードから友達追加し、適当なメッセージを送信します。

IMG_4770.jpg

Lambdaのモニタリングの「CloudWatchのログを表示」に移動して、「やっほ〜」とログ出力されていればOKです。

スクリーンショット 2022-06-29 16.41.55.jpg

switchbotのapiを叩き、その結果をLINE BOTから送信する

ディレクトリ構成

.
├──adapter
|   └──controller
|       ​​​​└──handler.go
|   └──gateway
|       ​​​​└──switchbot
|          ​​​​└──switchbot.go
|   └──presenter
|       ​​​​└──linebot.go
├──application
|   └──input
|       ​​​​└──input.go
|   └──usecase
|       ​​​​└──interface.go
|       ​​​​└──ope_appliances.go
├──go.mod
└──go.sum
└──main.go

SwitchBot APIのGoクライアントを作っている方がいたので、今回はこちらを使用させていただきました。go-switchbot

handler.go

package controller

import (
	"context"
	"fmt"

	"workspace/smartHome/application/input"
	"workspace/smartHome/application/usecase"

	"github.com/aws/aws-lambda-go/events"
)

type Controller struct {
	u usecase.Usecase
}

func NewController(usecase *usecase.Usecase) *Controller {
	return &Controller{
		u: *usecase,
	}
}

func (c *Controller) Handler(ctx context.Context, request events.APIGatewayProxyRequest) error {

	req, err := input.Unmarshal([]byte(request.Body))
	if err != nil {
		fmt.Printf("Failed to unmarshal event:%v\n", err.Error())
	}
	for _, event := range req.Events {
		err := c.u.OpeAppliances(ctx, event)
		if err != nil {
			fmt.Printf("Failed to reply by linebot:%v\n", err.Error())
		}
	}

	return nil
}

switchbot.go

package switchbot

import (
	"context"
	"fmt"

	switchbot "github.com/nasa9084/go-switchbot"
)

type SwitchBot struct {
	c SwitchbotInterface
}

func NewSwitchBotGateWay(switchbot SwitchbotInterface) *SwitchBot {
	return &SwitchBot{
		c: switchbot,
	}
}

type SwitchbotInterface interface {
	Device() *switchbot.DeviceService
}

func (s *SwitchBot) CheckStatus(ctx context.Context, deviceName string) (string, string, error) {

	deviceId := ""
	powerState := ""
	deviceList, _, err := s.c.Device().List(ctx)
	if err != nil {
		return "", "", fmt.Errorf("Fail to get device list :%w", err)
	}

	for _, d := range deviceList {
		if d.Name == deviceName {
			status, err := s.c.Device().Status(ctx, d.ID)
			if err != nil {
				return "", "", fmt.Errorf("Fail to check the status of power %v:%w", d.Type, err)
			}
			deviceId = d.ID
			powerState = status.Power.ToLower()
			fmt.Printf("Check the status of power: %s is %s\n", d.Type, status.Power.ToLower())
		}
	}

	if deviceId == "" {
		return "", "", fmt.Errorf("No device")
	}
	return deviceId, powerState, nil
}

func (s *SwitchBot) TurnOnOff(ctx context.Context, deviceName string) (string, error) {

	deviceId, powerState, err := s.CheckStatus(ctx, deviceName)
	if err != nil {
		return "", fmt.Errorf("Fail to check the status of power:%w", err)
	}

	if powerState == "on" {
		err = s.c.Device().Command(ctx, deviceId, switchbot.TurnOff())
		powerState = "off"
	} else if powerState == "off" {
		err = s.c.Device().Command(ctx, deviceId, switchbot.TurnOn())
		powerState = "on"
	}
	if err != nil {
		return "", fmt.Errorf("Fail to tunn on/off the power :%w", err)
	}

	fmt.Println("Succeeded in turning on/off the power")
	return powerState, nil
}

linebot.go

package presenter

import (
	"fmt"

	"github.com/line/line-bot-sdk-go/linebot"
)

type Presenter struct {
	l LinebotInterface
}

func NewPresenter(linebot LinebotInterface) *Presenter {
	return &Presenter{
		l: linebot,
	}
}

type LinebotInterface interface {
	ReplyMessage(replyToken string, messages ...linebot.SendingMessage) *linebot.ReplyMessageCall
}

func (p *Presenter) Reply(replyToken string, text string) error {

	if _, err := p.l.ReplyMessage(replyToken, linebot.NewTextMessage(text)).Do(); err != nil {
		return err
	}
	fmt.Println("Reply to line bot")
	return nil
}

input.go

package input

import "encoding/json"

type LineRequest struct {
	Events      []Event `json:"events"`
	Destination string  `json:"destination"`
}

type Event struct {
	Type       string  `json:"type"`
	ReplyToken string  `json:"replyToken"`
	Source     Source  `json:"source"`
	Timestamp  int64   `json:"timestamp"`
	Message    Message `json:"message"`
}

type Message struct {
	Type string `json:"type"`
	ID   string `json:"id"`
	Text string `json:"text"`
}

type Source struct {
	UserID string `json:"userId"`
	Type   string `json:"type"`
}

func Unmarshal(data []byte) (LineRequest, error) {
	var r LineRequest
	err := json.Unmarshal(data, &r)
	return r, err
}

interface.go

package usecase

import (
	"context"
)

type PresenterInterface interface {
	Reply(replyToken string, text string) error
}

type GateWayInterface interface {
	TurnOnOff(ctx context.Context, deviceName string) (string, error)
}

ope_appliances.go

package usecase

import (
	"context"
	"workspace/smartHome/application/input"
)

type Usecase struct {
	g GateWayInterface
	p PresenterInterface
}

func NewUsecase(switchbot GateWayInterface, linebot PresenterInterface) *Usecase {
	return &Usecase{
		g: switchbot,
		p: linebot,
	}
}

func (u *Usecase) OpeAppliances(ctx context.Context, event input.Event) error {

	deviceName := event.Message.Text
	powerState, err := u.g.TurnOnOff(ctx, deviceName)
	response := ""
	if err != nil {
		response = "error:" + err.Error()
	} else {
		response = event.Message.Text + "が" + powerState + "になりました!やったね!"
	}

	err = u.p.Reply(event.ReplyToken, response)
	if err != nil {
		return err
	}

	return nil
}

main.go

package main

import (
	"log"
	"os"
	"workspace/smartHome/adapter/controller"
	"workspace/smartHome/adapter/gateway/switchbot"
	"workspace/smartHome/adapter/presenter"
	"workspace/smartHome/application/usecase"

	sw "github.com/nasa9084/go-switchbot"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/line/line-bot-sdk-go/linebot"
)

func main() {

	sbcli := sw.New(
		os.Getenv("SWITCHBOT_TOKEN"),
	)
	lbcli, err := linebot.New(
		os.Getenv("CHANNEL_SECRET"),
		os.Getenv("CHANNEL_TOKEN"),
	)
	if err != nil {
		log.Fatal(err)
	}
	presenter := presenter.NewPresenter(lbcli)
	switchbot := switchbot.NewSwitchBotGateWay(sbcli)
	usecase := usecase.NewUsecase(switchbot, presenter)
	handler := controller.NewController(usecase)
	lambda.Start(handler.Handler)
}

(clean architectureを絶賛勉強中なので何かご指摘あれば教えてください…!)

こちらのコードをデプロイします。

LINEBOTからデバイスに登録した名前を打ち込むと、加湿器をONすることができました!!

IMG_4774.jpg IMG_4785.JPG

登録していない名前を送った場合はエラーが返ってきます。

IMG_4775.jpg

LINEのリッチメニュー

毎回デバイス名を打ち込むのは面倒なのでLINEのリッチメニューを使用します。

https://www.linebiz.com/jp/manual/OfficialAccountManager/rich-menus/
を参考に、デバイス名とアイコン用の画像を保存して反映します。

スクリーンショット 2022-06-29 17.20.37.PNG

アイコンをタップするだけで操作できるようになりました!

IMG_4777.jpg

最後に

スマートホーム化第一弾としてSwitchbotを使ってみました。
加湿器以外にも面白そうなデバイスが沢山あるので、色々買ってみてスマートホーム化を目指したいと思います!!
他にもおすすめのものがあれば是非教えてください〜!

参考資料

Messaging APIを始めよう
.zip ファイルアーカイブを使用して Go Lambda 関数をデプロイする
API GatewayとLambda(Python)でLINE BOT(Messaging API)開発 [前編]
SwitchBot APIのGoクライアント、go-switchbotを書いた
リッチメニューを作成する

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3