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周りの説明は以下を参考にしました。
-
https://spring.io/guides/gs/messaging-stomp-websocket/
Spring公式のGetting Started -
Spring Bootキャンプ ハンズオン資料
はじめてのSpring Boot作者さんの資料。
この資料も本も必読ですね。。
##サーバサイド
WebSocketの設定クラスです。
websocketのエンドポイントを設定、メッセージブローカーを有効にして宛先を設定します。
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
で設定した宛先に送信します。
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はこんな感じ。
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メソッドでハンドリングしてくれます。
<!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しないとすぐ切断しちゃうかも。
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起動時や不慮のプロセス停止に対応。
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に発話させることができました。
意外と仕事で使うこともあるかもしれませんね。