codamaで遊んでみた

#codamaとは？
raspberry piのVUIキット。オリジナルのトリガーワードを作成できたり、マイクアレイを使って音源の方向検知ができたりと面白い。
https://codama.ux-xu.com/
https://robotstart.info/2018/12/28/cidama-dev-01.html

#作ってみたいもの:codamaちゃん
声に反応するキャラクターロボットをやってみようと思った。
現時点では、下記サンプルを動かしてみたというところまで。最終的には外装もつける予定。
・サンプル1：オリジナルトリガーワードに反応して効果音
・サンプル2：喋っている方向にサーボモータを使ってなんとなく向く

![IMG_1377.JPG](https://qiita-image-store.s3.amazonaws.com/0/348615/a85c04a3-845b-8780-04f8-68cbe0c548f7.jpeg)

つまずきポイントもいくつかあるので共有。なお、私はハードウェアエンジニアなので、ソースの信頼度はそこまで高くないことをご了承いただけるとありがたく。

# 設定

基本的には、以下に沿って設定、トリガーワードを作成する。
https://github.com/YUKAI/codama-doc-r0/wiki

躓いたポイントは、codamaのメインボードとマイクボードをつなぐケーブルが逆刺しできてしまうということ。このケーブルの向きには気をつける必要がある。赤いラインが写真の向きになっているのが正解。（逆刺ししても壊れなかったが、動作しない）
<img width="676" alt="スクリーンショット 2019-01-24 12.08.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/348615/7bbb2453-9418-b03c-7447-3c34cf1d6cce.png">

あと、ウェイク・アップ・ワード登録のときに、ボード上のジャンパーを操作する必要がある。出荷時はジャンパーがついている状態となっているが、この状態でデフォルトファームウェアが起動するようになっている。update.binを書き込んだあとは、ジャンパーを外して運用する。私は、下記写真のように、片側だけつけてジャンパーをなくさないようにしている。
![IMG_1379.JPG](https://qiita-image-store.s3.amazonaws.com/0/348615/4fbd560f-3e4a-21d0-e129-9cde551970c0.jpeg)
その2点以外は、基本的にチュートリアル通りにやればすんなり動くようになる。

#ハードウェア製作

下記を用意
- raspi
- codama
- スピーカ
- サーボモータ

ハードウェアは、あまり推奨されないが、raspiの5V系でサーボを動かしてしまう。codamaボード上のCN10上のGND、5V, GPIO4にサーボのGND, VCC, Signalを接続。
スピーカーはcodamaメインボードのCN9に接続。
![layout.png](https://qiita-image-store.s3.amazonaws.com/0/348615/26a72fb0-a78f-260c-d261-e029975e1fcc.png)
あとは、両面テープと結束バンドでいい感じにつくる。
![IMG_1380.JPG](https://qiita-image-store.s3.amazonaws.com/0/348615/298c93bf-cf13-3a37-1d01-86b3707d8396.jpeg)

#サンプル1：オリジナルトリガーワードに反応して効果音再生
至ってシンプル。トリガーワードが検知されると、GPIO27がHIGHになるので、それを監視して効果音を再生するシェルスクリプト。

```Trigger_detect.sh
#!/bin/sh

if [ ! -f /sys/class/gpio/gpio27/value ]; then
  echo 27 > /sys/class/gpio/export
fi

TRGWORD=0
while true
do
  if [ "`cat /sys/class/gpio/gpio27/value`" = 1 ] ; then
    if [ $TRGWORD = 0 ] ; then
      aplay -c1 -r16000 -fS16_LE jump07.wav &
      TRGWORD=1
    fi
  else
    if [ $TRGWORD = 1 ] ; then
      TRGWORD=0
    fi
  fi
  sleep 0.2
done
```

デモ動画
https://youtu.be/vFRrV7NrGRw

オリジナルトリガーワードはかなり正確に入る。

#サンプル2：喋っている方向にサーボモータを使ってなんとなく向く
codamaの音源方向検知を利用する。
以下コマンドで、ユーザーが触れるパラメータがわかる。

```
pi@raspberrypi:~ $ cd codama/codama-doc/utils/
pi@raspberrypi:~/codama/codama-doc/utils $ ./codama_i2c -p
paramater              type    max          min          r/w          info                                    
---------              ----    ---          ---          ---          ----                                    
HPFONOFF               int     3            0            read-write   High-pass Filter on microphone signals.
                                                                      0 = OFF
                                                                      1 = ON - 70 Hz cut-off
                                                                      2 = ON - 125 Hz cut-off
                                                                      3 = ON - 180 Hz cut-off
AECSILENCELEVEL        float   0.93         1e-09        read-write   Threshold for signal detection in AEC [-inf .. 0] dBov (Default: -80dBov = 10log10(1x10-8))
AECSILENCEMODE         int     1            0            read-only    AEC far-end silence detection status. 
                                                                      0 = false (signal detected) 
                                                                      1 = true (silence detected)
AGCONOFF               int     1            0            read-write   Automatic Gain Control. 
                                                                      0 = OFF 
                                                                      1 = ON
AGCMAXGAIN             float   1000         1            read-write   Maximum AGC gain factor. 
                                                                      [0 .. 60] dB (default 30dB = 20log10(31.6))
AGCDESIREDLEVEL        float   0.99         1e-08        read-write   Target power level of the output signal. 
                                                                      [-inf .. 0] dBov (default: -23dBov = 10log10(0.005))
AGCGAIN                float   1000         1            read-write   Current AGC gain factor. 
                                                                      [0 .. 60] dB (default: 0.0dB = 20log10(1.0))
AGCTIME                float   1            0.1          read-write   Ramps-up / down time-constant in seconds.
BEAMWIDTH              float   1            0.2          read-write   Width of the beam for desired speech sources. 
                                                                      [23 .. 180] (default: 60 = sin-1(0.5) 360/ pi)
BEAMANGLE              float   1            -1           read-write   Center of the beam for desired speech sources. 
                                                                      [-90 .. 90] (default: 0 = sin-1(0.0) 360/2pi)
STATNOISEONOFF         int     1            0            read-write   Stationary noise suppression.
                                                                      0 = OFF
                                                                      1 = ON
GAMMA_NS               float   3            0            read-write   Over-subtraction factor of stationary noise. min .. max attenuation
MIN_NS                 float   1            0            read-write   Gain-floor for stationary noise suppression.
                                                                      [-inf .. 0] dB (default: -16dB = 20log10(0.15))
NONSTATNOISEONOFF      int     1            0            read-write   Non-stationary noise suppression.
                                                                      0 = OFF
                                                                      1 = ON
GAMMA_NN               float   3            0            read-write   Over-subtraction factor of non- stationary noise. min .. max attenuation
MIN_NN                 float   1            0            read-write   Gain-floor for non-stationary noise suppression.
                                                                      [-inf .. 0] dB (default: -10dB = 20log10(0.3))
ECHOONOFF              int     1            0            read-write   Echo suppression.
                                                                      0 = OFF
                                                                      1 = ON
VOICEACTIVITY          int     1            0            read-only    VAD voice activity status.
                                                                      0 = false (no voice activity)
                                                                      1 = true (voice activity)
SR_GAMMA_VAD           float   1000         0            read-write   Set the threshold for voice activity detection.
                                                                      [-inf .. 60] dB (default: 3.5dB 20log10(1.5))
KEYWORDDETECT          int     1            0            read-only    Keyword detected. Current value so needs polling.
DOAANGLE               int     359          0            read-only    DOA angle. Current value. Orientation depends on build configuration.
DOAANGLEKWD            int     359          0            read-only    DOA angle when keyword detects.
SOFTRESET              int     1            0            write-only   Soft Reset Request
MIC_ATTEN              int     0            -100         read-write   MIC input signal attenuator in decibels. [0 .. -100] dB (Default 0dB)
AEC_REF_ATTEN          int     0            -100         read-write   AEC reference signal attenuator in decibels. [0 .. -100] dB (Default: 0dB)
VERSION                int     2147483647   0            read-only    Version number
```

今回は、`DOAANGLE`を使ってみる。
以下のpythonサンプルコードを作成。raspiからサーボモータを動かすライブラリは`pigpiod`を使う。当初、`RPi.GPIO`でやっていたのだが、pwmがノイジーでサーボがピクピク動いてしまった。

```servo_test.py
import pigpio
import time
import subprocess
import signal
import sys

pin=4
pi = pigpio.pi()
pi.set_servo_pulsewidth(pin, 1500)


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

def main():
        current_angle = 90
        cmd = ("/home/pi/codama/codama-doc/utils/./codama_i2c DOAANGLE")
        while True:
                n=20
                i=0
                value = res_cmd(cmd).strip()
                target_angle = int(value.strip("DOAANGLE:"))
                if target_angle < 30 or target_angle > 150:
                        target_angle = 90
                print 'Target angle:' + str(target_angle)
                divided_angle = (current_angle - target_angle)/n
                while i<n:
                        current_angle -= divided_angle;
                        pi.set_servo_pulsewidth(pin, (85/9)*current_angle + 650)
                        time.sleep(0.05)
                        i+=1
                time.sleep(0.5)

if __name__ == '__main__':
  main()
```
以下で実行。

```
pi@raspberrypi:~/workspace $ sudo pigpiod
pi@raspberrypi:~/workspace $ sudo python servo_test.py 
```

デモ動画
https://youtu.be/A05lSUaqGUc

喋っていてこちらに向いてくるという機能はとてもおもしろく、可能性を感じた。ただ、反応にラグがある。また、codamaマイクの使い方として、マイクアレイは筐体に完全固定して使うことが推奨されており、今回私のサンプルでは、サーボの先の動く部分にマイクがついていることもやや精度を落としている一つの要因かと思う。

#ハマって未だに解決していないこと
i2cが見えなくなる現象が起こる。
連続して動かすと、稀に発生し、エラーメッセージは下記のとおり。再現条件は不明だが、自分の環境以外でも起きている模様。

```
rdwr ioctl error -1: No such device or address
Error: Control read command failed
```
解決方法としては、一度raspiとcodamaの電源もオフにして再起動すると復帰することが多い。（ハードウェア的に5Vを抜き差しする。rebootではNG）
これでもだめな場合、以下で直ったこともある。

```
$ sudo raspi-config
  --> 5 Interfacing Options
   --> P5 I2C
       Select <No>
       Select <OK>
       Select <Finish>
一度、Raspberry Piを含め電源を落とし、入れ直す。起動したら、以下でI2Cデバイスが存在する事を確認。
$ ls -al /dev/i2c-1
```

以上、codamaを使って遊んでみたのとハマったポイントの共有でした。