JavaScriptを使ったロボット作りの4回目です。

前回までブラウザを利用してロボットを操作していましたが、今回は音声入力を利用して寝ているロボットを起こし、ダンスをさせてみます。


via GIPHY

こんな感じです。

ロボットの頭脳部、RaspberryPi2に接続しているマイクに向かって「ダンス」と話しかけます。

今回はRaspberryPi2のOS上にChromiumブラウザを起動させ、Web Speech Recognition APIを利用して音声認識を行います。

動画の方をご覧になると分かるのですが、R2D2っぽい音声と合わせてご機嫌な音楽も再生させてみました。

さて、簡単にまとめてみます。

Annyang.js

Chromeブラウザを利用してブラウザ上の要素を変更することができます。(画像やの変形や動画の再生など)

以前Qiitaにまとめました。

ブラウザで音声操作をする。(Speech Recognition API) - Qiita

これを応用してロボットを操作させるという作戦です。Web Speech Recognition APIをそのまま記述すると結構大変なので、以下のライブラリを利用します。

TalAter/annyang - GitHub

CDNも公開されているので、サンプルに沿って記述すれば簡単に音声コマンドを利用することが出来ます。

RaspberryPiでChromeを使う

そんな便利な音声認識ですが、RaspberryPiで使うときには結構苦労していました。音声認識の可能なChromeのバーションが49からなのですが、RaspberryPiで安定して導入できるChromiumが48まででした。

OSをUbuntuMATEにして、Electronアプリのサーバーサイドから音声認識APIを使うという、ちょっと裏技的なやり方を試したりしていたのですが、RaspberryPiのOSがアップグレードされて晴れて最新版Chromiumを利用することができるようになりました。

PIXEL: THE BRAND NEW DESKTOP FOR THE RASPBERRY PI

この新しいOSのデフォルトブラウザがChromiumになったので、安定した音声認識APIを利用することができるようになりました。iPad miniにリモートデスクトップした画面はこんな感じです。

こんな感じでRaspberryPi本体に接続したマイクを利用して音声認識を実行することが出来ます。

ロボットを起こす

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>PonBot03</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Orbitron" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<article>
<h1>PonBot03</h1>
<article>
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<p id="caption"></p>
</article>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
<!--annyang.jsのCDNを読み込む-->
<script src="//cdnjs.cloudflare.com/ajax/libs/annyang/2.4.0/annyang.min.js"></script>
<script>
$(document).ready(function() {
/*socket.ioに接続*/
var socket = io.connect('http://192.168.x.xx');
socket.emit('click_servo_yes',150);
socket.emit('click_servo_no',90);
/*音声が認識されたら実行*/
if (annyang) {
/*実行コマンドを定義*/
var commands = {
/*「ハロー」と声がしたら*/
'ハロー': function() {
console.log('Wake Up');
/*0.3秒毎にアクションの指示をサーバーサイドに実行*/
setTimeout(function(){
socket.emit('click_sounds_r2d2',1);
socket.emit('click_servo_yes',60);
socket.emit('click_motor_left');
setTimeout(function(){
socket.emit('click_servo_yes',120);
socket.emit('click_motor_right');
setTimeout(function(){
socket.emit('click_servo_yes',60);
socket.emit('click_motor_left');
setTimeout(function(){
socket.emit('click_servo_yes',120);
socket.emit('click_motor_right');
setTimeout(function(){
socket.emit('click_servo_yes',90);
},1500);
},1200);
},900);
},600);
},300);
}
};
/*annyann.jsの設定。言語は日本語*/
annyang.setLanguage('ja-JP')
annyang.addCommands(commands);
annyang.start();
console.log('録音開始');
}
});
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
var express = require("express");
var app = express();
var path = require("path");
var server = require('http').Server(app);
var io = require('socket.io')(server);
var fs = require('fs');
var request = require('request');
var five = require('johnny-five');
var exec = require('child_process').exec;
server.listen(3000, function () {
console.log('listening on *:3000');
});
app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/index.html'));
});
board = new five.Board();
board.on("ready", function() {
var a = new five.Motor({
controller: "GROVE_I2C_MOTOR_DRIVER",
pin: "A",
});
var b = new five.Motor({
controller: "GROVE_I2C_MOTOR_DRIVER",
pin: "B",
});
io.sockets.on('connection', function (socket) {
socket.on('click_servo_yes', function (degree_y) {
console.log('サーボ1: ' + degree_y);
var options_y = {
uri: 'https://us.wio.seeed.io/v1/node/GroveServoD1/angle/' + degree_y,
form: { access_token: '発行されたアクセストークン' },
json: true
};
request.post(options_y, function(error, response, body){
if (!error && response.statusCode == 200) {
} else {
console.log('error: '+ response.statusCode);
}
});
});
socket.on('click_servo_no', function (degree_n) {
console.log('サーボ0: ' + degree_n);
var options_n = {
uri: 'https://us.wio.seeed.io/v1/node/GroveServoD0/angle/' + degree_n,
form: { access_token: '発行されたアクセストークン' },
json: true
};
request.post(options_n, function(error, response, body){
if (!error && response.statusCode == 200) {
console.log(body);
} else {
console.log('error: '+ response.statusCode);
}
});
});
socket.on('click_sounds_r2d2', function (num) {
exec('aplay /home/pi/ponbot03.2/public/r2d2_' + num + '.wav', function(error, stdout, stderr) {
if (error != null) {
console.log(error);
}
});
});
socket.on('click_motor_right', function () {
a.rev(381);
b.rev(381);
setTimeout(function(){
a.stop();
b.stop();
},300);
});
socket.on('click_motor_left', function () {
a.fwd(381);
b.fwd(381);
setTimeout(function(){
a.stop();
b.stop();
},300);
});
});
});

こんな感じで、「ハロー」と声を掛けると目を覚まします。

ロボットにダンスをさせる

ついでにご機嫌なサウンドに乗せてロボットにダンスをさせてみます。
上記サンプルのクライアントサイドのコマンドを以下のように追加しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
'ダンス': function() {
console.log('The Get Down');
setTimeout(function(){
socket.emit('click_sounds_r2d2',2);
socket.emit('click_music_play');
socket.emit('click_motor_left');
 socket.emit('click_servo_yes',80);
socket.emit('click_servo_no',100);
setTimeout(function(){
socket.emit('click_motor_right');
socket.emit('click_servo_yes',100);
socket.emit('click_servo_no',80);
setTimeout(function(){
socket.emit('click_motor_left');
socket.emit('click_servo_yes',80);
socket.emit('click_servo_no',100);
setTimeout(function(){
socket.emit('click_motor_right');
socket.emit('click_servo_yes',100);
socket.emit('click_servo_no',80);
setTimeout(function(){
socket.emit('click_motor_right_turn');
setTimeout(function(){
socket.emit('click_servo_yes',90);
socket.emit('click_servo_no',90);
socket.emit('click_motor_stop');
socket.emit('click_music_stop');
},6500);
},1500);
},1200);
},900);
},600);
},300);
}

タイマー処理のネストがえらいことになっていますが、今回はこれでよしとします。サーバーサイドは音楽再生のコマンドを追加します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
socket.on('click_music_play', function () {
exec('mpg321 /home/pi/Music/getdown.mp3', function(error, stdout, stderr) {
if (error != null) {
console.log(error);
}
});
});
socket.on('click_music_stop', function () {
exec('pkill -SIGSTOP mpg321', function(error, stdout, stderr) {
if (error != null) {
console.log(error);
}
});
});

まとめ

さて、えらいことになったネストはさておき、とりあえずJavaScriptだけを
使って、音声コマンドによるロボット操作は出来るようになりました。

少々残念なのがダンスのキレが無く、あんまり踊っているように見えないことです。(残念)

もう少し工夫してロボットの機能を増やせると良いな、と思います。ではまた。