LoginSignup
4
2

More than 5 years have passed since last update.

Google Homeをリモートでしゃべらせる(Websocket使用)

Last updated at Posted at 2018-03-09

Google Home、面白いですね。

@miso_developさんのGoogle Homeでやったことまとめに心動かされて、いくつか写経させていただきました。

IFTTTを使用するとLINE送信もできる、とのことで導入したところ、小学生の娘が喜んでLINE送信してくれるようになり、そうなるとGoogle Homeに返答をしゃべらせたくなってきます。
調べてみると、Google Homeに発話させるのは
またまた@miso_developさんのWebからGoogle Homeを喋らせたり家電操作したりしてみるがあり、以下の手順でできるようです。

1.Node.jsのライブラリ「google-home-notifier」を使用するとGoogle Homeに発話させることができる。
2.1は自宅LAN内で実行する必要があり、リモートでのトリガーにRealtimeDBのFirebaseを使用する。

自分は自宅向けにSpring Bootでシステム構築してあったので、2のトリガーとなる部分をwebsocketにしてみました。Spring使えるとセキュリティ実装に悩まないことが大きいです。
Spring Securityは圧倒的に便利。

参考:@opengl-8080さんのSpring Security 使い方メモ 認証・認可

機材

Raspberry PI3
ウサギを飼っているので室内の気温ロガー&エアコン制御で導入済みでした。

Google Home mini
コストコで¥5,000くらいだったような。

実装

コードはこちら
https://github.com/ko-aoki/web2Googlehome

Spring Boot 1.5.10で実装しています。

websocket周りの説明は以下を参考にしました。

サーバサイド

WebSocketの設定クラスです。
websocketのエンドポイントを設定、メッセージブローカーを有効にして宛先を設定します。

WebSocketConfig.java
package com.example.web2googlehome;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/endpoint");
    }

}

通常のWebアプリケーションのコントローラクラスです。
パラメータ"send"つきでPOSTされたら
SenderDtoをWebSocketConfig.javaで設定した宛先に送信します。

SenderController.java
package com.example.web2googlehome.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/top")
public class SenderController {

    SimpMessagingTemplate simpMessagingTemplate;

    @Autowired
    public SenderController(SimpMessagingTemplate simpMessagingTemplate) {
        this.simpMessagingTemplate = simpMessagingTemplate;
    }

    @GetMapping
    public String top(Model model) throws Exception {
        model.addAttribute("senderDto", new SenderDto());
        return "top";
    }

    @PostMapping(params = "send")
    public String send(SenderDto dto, Model model) throws Exception {
        model.addAttribute("senderDto", dto);
        this.simpMessagingTemplate.convertAndSend("/topic/notification", dto);
        return "top";
    }
}


SenderDtoはこんな感じ。

SenderDto.java

package com.example.web2googlehome.controller;

public class SenderDto {

    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

発話のトリガーとなるPOSTを実行するHTMLはthymeleafで。
(もちろん、普通にJavaScriptでメッセージをsendしても。)
「送信」押下で、メッセージをSenderController.javaのsendメソッドでハンドリングしてくれます。

top.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8"/>
</head>
<body>
<div>
    <form action="/top"
          th:object="${senderDto}"
          th:action="@{/top}" method="post">
        <div>
            <label for="message">メッセージ</label>
            <input id="message" type="text" th:field="*{message}"/>
        </div>
        <div class="form-group">
            <button type="submit" class="btn-primary" name="send">送信</button>
        </div>
    </form>
</div>
</body>
</html>

クライアントサイド(Raspberry PI)

"ws://localhost:8080/topic/notification"をsubscribeして、
メッセージ通知されたらgoogle-home-notifierをキックして発話します。
stompFailureCallbackは切断時の再接続ロジックです。
環境によってはheartbeatしないとすぐ切断しちゃうかも。

web2googlehome-client.js
const Stomp = require('stompjs');
const Googlehome = require('google-home-notifier');
const language = 'ja';
var ip = 'xx.xx.xx.xx'; //ここにGoogle HomeのIPを記載

Googlehome.device('', language);
Googlehome.ip(ip, language);

var client;  // stompクライアント

var stompFailureCallback = function (error) {
    console.log('STOMP: ' + error);
    client = Stomp.overWS('ws://localhost:8080/endpoint');
    setTimeout(stompConnect, 5 * 60 * 1000);
    console.log('STOMP: Reconecting in 5 minutes');
};

var stompSuccessCallBack = function (frame) {
    console.log('connected to Stomp');
    client.subscribe('/topic/notification', function (data) {

        var obj = JSON.parse(data.body);
        console.log("received message " + obj.message);

        Googlehome.notify(obj.message , function(res) {
            console.log(res);
        });

    });
}

var stompConnect = function() {
    client = Stomp.overWS('ws://localhost:8080/endpoint');
    console.log('connecting to Stomp');
    client.connect('', '', stompSuccessCallBack, stompFailureCallback);
}

stompConnect();

自分がデプロイしている環境だと、50secでコネクション切断してしまうので
heartbeatをこんな感じにしてます。
あとはサービス化して、OS起動時や不慮のプロセス停止に対応。

web2googlehome-client.js(heartbeatあり)
const Stomp = require('stompjs');
const Googlehome = require('google-home-notifier');
const language = 'ja';
var ip = 'xx.xx.xx.xx'; //ここにIPを記載

Googlehome.device('', language);
Googlehome.ip(ip, language);

var client;  // stompクライアント
var intervalId; // heartbeatのintervalID

var stompFailureCallback = function (error) {
    console.log('STOMP: ' + error);
    clearInterval(intervalId);
    setTimeout(stompConnect, 5 * 60 * 1000);
    console.log('STOMP: Reconecting in 5 minutes');
};

var stompSuccessCallBack = function (frame) {
    console.log('connected to Stomp');
    client.subscribe('/topic/notification', function (data) {

        var obj = JSON.parse(data.body);
        console.log("received message " + obj.message);

        Googlehome.notify(obj.message , function(res) {
            console.log(res);
        });

    });
    // heartbeat
    intervalId = setInterval(function () {
        client.send('/app','');
    }, 10 * 1000)
}

var stompConnect = function() {
    client = Stomp.overWS('ws://localhost:8080/endpoint');
    client.heartbeat.outgoing = 10000;
    console.log(new Date() + ' connecting to Stomp');
    client.connect('', '', stompSuccessCallBack, stompFailureCallback);
}

stompConnect();

まとめ

業務アプリっぽい構成で、Google Homeに発話させることができました。
意外と仕事で使うこともあるかもしれませんね。

4
2
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
4
2