音声認識デバイスを使って声に反応するラジコンを作ってみた

# 概要
特定の声に反応するモジュールであり、Raspberry Piに接続できるモジュールであるCodamaで、声のする方向に移動するラジコンを作ってみた。

# Codamaとは？
特徴はざっくり言うと2つ

- **特定のワードに反応する**
- **声の角度を計測する**

セットアップの方法は[Codamaのマニュアル](https://github.com/YUKAI/codama-doc-r2/wiki)の**codamaの設定方法**と**ウェイク・アップ・ワード作成ツール**の項目に記載されている通りに行えばセットアップは完了である。

# 完成品
動画より、方向を転換して移動していることが分かる。(トリガーワードを声に出しています)
![IMG_5280 (1)_min.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384390/c2af130f-0a41-87cf-9f25-9d47f007ca9c.gif)
上に乗っかっているのはQooboである。
なので、AIB〇が無くてもペットを飼っているような気分になる！かもしれない・・・

# 材料
ラズパイとCodama以外は身近にあったパーツを寄せ集めて作った。

-  Raspberry Pi3 BodelB+
- Codama
- 無限回転サーボ2つ
- 小型キャスター2つ
- プラスチック段ボール
- ジャンパワイヤ
- 結束バンド

# ラジコンの形
形は以下の写真の通りである。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384390/692b5d44-36ce-c7df-857a-f6c163cc94e0.jpeg"width=50%>>


2つのサーボ以外にキャスターを付けている理由は安定させるためである。
また、写真にはないが本来はRaspberry Pi等のマイコンボードは
下部に段ボールに穴をあけて結束バンドで固定する設計にしている。
もしマイコンボードが床に接してしまうなら、車輪とボードの間に板などをかまして高さを調節してほしい。

# ラジコンを動作させる
サーボの制御のプログラムは[このサイト](https://uepon.hatenadiary.com/entry/2019/01/30/165644)を参考に行った。
プログラムは以下のとおりである。




```python

# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO
import time
import signal
import sys
import subprocess
import math


def res_cmd(cmd):
               return subprocess.Popen(cmd, stdout=subprocess.PIPE,shell=True).communicate()[0]

def main():
               
               GPIO.setmode(GPIO.BCM)

               #GPIO4を制御パルスの出力に設定
               gp_outR = 4
               gp_outL = 6

               GPIO.setup(gp_outR, GPIO.OUT)
               GPIO.setup(gp_outL, GPIO.OUT)

               #「GPIO4出力」でPWMインスタンスを作成する。
               #GPIO.PWM( [ピン番号] , [周波数Hz] )
               #SG92RはPWMサイクル:20ms(=50Hz), 制御パルス:0.5ms～2.4ms, (=2.5%～12%)。
               servoR = GPIO.PWM(gp_outR, 50)
               servoL = GPIO.PWM(gp_outL, 50)
#パルス出力開始。　servo.start( [デューティサイクル 0~100%] )
#とりあえずゼロ指定だとサイクルが生まれないので特に動かないっぽい？
               servoR.start(0)
               servoL.start(0)
               time.sleep(1)



               cmd = ("/home/pi/codama/codama-doc/utils/./codama_i2c DOAANGLEKWD")
               value = res_cmd(cmd)
               target_angle = int(value.strip("DOAANGLEKWD:"))
               print 'Target angle:' + str(target_angle)
               time.sleep(0.5)
               if target_angle < 90:
               #sleep.timeで角度を調整
                              servoR.ChangeDutyCycle(0.5)
                              servoL.ChangeDutyCycle(0.5)
                              turnTime = round(math.cos(math.radians(target_angle)) ,3)
                              time.sleep(turnTime)
               elif target_angle > 90:
                              servoR.ChangeDutyCycle(12.5)
                              servoL.ChangeDutyCycle(12.5)
                              turnTime = round(-1*math.cos(math.radians(target_angle)) ,3)
                              time.sleep(turnTime)
               else:
                              time.sleep(0.2)
               
               servoR.ChangeDutyCycle(0.5)
               servoL.ChangeDutyCycle(12.5)
               time.sleep(1)
                              
               servoR.stop()
               servoL.stop()
               GPIO.cleanup()

if __name__ == '__main__':
     main()


```

ラジコンを動かすプログラムは大きく分けてこの順番で行った。
1. トリガーワードが発せられた角度に向ける
2. 直進へ移動する


項目1については、Codamaの機能の1つであるトリガーワード検出時の角度を検出する機能を使う。
方法は簡単で、```cmd = ("/home/pi/codama/codama-doc/utils/./codama_i2c DOAANGLEKWD") ```
の**DOAANGLEKWD**のコマンドをたたけばやってくれる。
また、サーボの動かし方であるが、Raspberry Piに接続してあるCodamaにはこのように接続している。
Codamaは公式サイトの図に示されている通り、Raspberry Piと同様にGPIOを使うことができる。
今回は、gp_outR,gp_out_LでGPIOピンを指定して2つのサーボを制御した。
マイコンボードには以下の写真の通りにジャンパワイヤを刺している。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384390/4fb94132-c284-41e9-66a4-12311b8d76fa.jpeg"width=50%>>
これは、公式サイトのGPIOの配置を参考にGPIOの番号を指定し、接続した。
次に角度の調整であるが、これはサーボを動かし左右に角度を向ける時間を制御することで行っている。
サーボが動作する時間は、``` time.sleep ``` で制御している。

``` 
   servoR.ChangeDutyCycle(0.5)
   servoL.ChangeDutyCycle(0.5)
```
で、サーボを制御をすれば**右**に向き、

``` 
    servoR.ChangeDutyCycle(12.5)
    servoL.ChangeDutyCycle(12.5)
```
で、**左**に向いてくれる。
また、移動する時間は

```
turnTime = round(math.cos(math.radians(target_angle)) ,3)
```
の通りcos関数で算出しているが、これは1秒動作すれば90°傾くことが分かったが、
角度をいい感じに向いてくれる動作時間の算出の式を色々試したところcos関数であったためである。

項目2は、

``` 
   servoR.ChangeDutyCycle(0.5)
   servoL.ChangeDutyCycle(12.5)
```

でサーボを制御すれば直進してくれることが分かった。
1秒動かすことで、ちょっとずつ前進するようにしている。

# まとめ
期間は2日ちょっとだが、突貫工事気味でも動くものが作れた。