AIY Voice KIT にAlexaとGoogleAssistantを同居

3月26日のOSアップグレードで、少しインタフェースが変わったため、確実な同居方法を
メモした。また、04-13版のレベルダウンへも対応追記。

1.AIYプロジェクトのVoice Kit SD imageをダウンロード
 aiyprojects-2018-04-13.img.xz
 https://dl.google.com/dl/aiyprojects/aiyprojects-latest.img.xz
2.MicroSDに書き込み、インストール
 aiyprojects-2018-04-13.img.xzを解凍し、書き込みする
 参照:https://aiyprojects.withgoogle.com/voice/#assembly-guide-5-2–boot-the-device
2.1 Audio音量関数の修正
 2018-04-13.imgにはレベルダウンがあり、AIY-project*/src/aiy/audio.py を修正する。
 113行 db_range = -60.0 – (-60.0 * (volume / 100.0)) を
     db_range = -6.6 – (-6.6 * (volume / 100.0)) に書き換え。
3.AIYでの日本語動作確認
 PulseAudioの初期値が未設定なので、/etc/pulse/default.pa を編集します。
 バックエンドドライバーをロードする行を探してアンコメント(#削除)し、
 以下のように device パラメータを追加してください。
 さらに autodetect モジュールをロードする行をコメントアウトしてください。
 load-module module-alsa-sink device=dmix
 load-module module-alsa-source device=dsnoop
 # load-module module-udev-detect
 # load-module module-detect
 再起動
 amixer sset Master 50%  (初期音量を変更したい)
 参照:https://kureuetan.com/web/raspberrypi/4998/#OS
 ~/bin/AIY-projects-shell.sh
 src/examples/voice/assistant_grpc_demo.py
 この後、GoogleAssistantアプリで、デバイスVoiceKitの言語選択を日本語に設定してください。
4.RasPi用Snowboyのインストール
 参照:https://github.com/wanleg/snowboyPi
4.1 事前準備
 sudo apt update && sudo apt -y upgrade && sudo apt-get -y auto-remove && sudo reboot
 OSのバージョンは、3月末で、次のようになります。
  Linux raspberrypi 4.14.30-v7+ #1102 SMP Mon Mar 26 16:45:49 BST 2018 armv7l GNU/Linux
 sudo apt -y install python-pyaudio python3-pyaudio sox python3-pip python-pip libatlas-base-dev
 sudo pip3 install pyaudio
 sudo cp ~/.asoundrc /root/
4.2 Snowboyの準備
 wget https://s3-us-west-2.amazonaws.com/snowboy/snowboy-releases/rpi-arm-raspbian-8.0-1.1.1.tar.bz2
tar xvf rpi-arm-raspbian-8.0-1.1.1.tar.bz2
mv rpi-arm-raspbian-8.0-1.1.1 snowboy
4.3 サウンドテスト
  speaker-test -c 2
  arecord -d 3 test.wav
  aplay test.wav
4.4 Hotwordの作成
 pip install requests
 cd snowboy
 wget https://github.com/wanleg/snowboyPi/raw/master/training_service.py
https://snowboy.kitt.ai にログインし、Profile settings をクリック、
 作成された API token をコピーしてメモする。
 training_service.pyの次のパラメタを設定する
 ############# MODIFY THE FOLLOWING #############
 token = “コピーしてメモしたAPI token”
 hotword_name = “ホットワードの名前”
 language = “jp”
 age_group = “30_39”
 gender = “M”
 microphone = “usb microphone”
 ############### END OF MODIFY ##################

 rec -r 16000 -c 1 -b 16 -e signed-integer 1.wav
 rec -r 16000 -c 1 -b 16 -e signed-integer 2.wav
 rec -r 16000 -c 1 -b 16 -e signed-integer 3.wav
 python training_service.py 1.wav 2.wav 3.wav NeGoogle.pmdl
 cp NeGoogle.pmdl resources/NeGoogle.pmdl
4.5 Hotwordのテスト
 python3 demo.py ~/snowboy/resources/NeGoogle.pmdl

 startAlexa.sh,assistant_grpc_snow_demo.pyのダウンロードと走行確認
 wget -O startAlexa.sh http://pmp-jp.com/test/startAlexa.txt
 wget -O _snowboydetect.so http://pmp-jp.com/test/_snowboydetect.so
 wget -O assistant_grpc_snow_demo.py http://pmp-jp.com/test/snow_demo.txt
 python3 assistant_grpc_snow_demo.py

5. Alexaのインストール
 参照:https://github.com/alexa/avs-device-sdk/wiki/Raspberry-Pi-Quick-Start-Guide-with-Script

cd
    wget https://raw.githubusercontent.com/alexa/avs-device-sdk/master/tools/Install/setup.sh
    wget https://raw.githubusercontent.com/alexa/avs-device-sdk/master/tools/Install/config.txt
    wget https://raw.githubusercontent.com/alexa/avs-device-sdk/master/tools/Install/pi.sh
    vi setup.sh で次の変更を実施
       en-US を ja-JP
    vi config.txtで次の設定を実施
       Client ID, Client Secret, and Product IDの設定
   vi avs-device-sdk/build/BuildDefaults.cmake で include(KeywordDetector) をコメント化
  
   sudo bash setup.sh config.txt
    sudo bash startauth.sh の起動後、http://localhost:3000をアクセス
    sudo cp ~/.asoundrc /root/
  sudo bash startsample.sh を実行し、c、1、6と入力して日本語モードにする

6. AlexaとGoogleAssistantの同時実行
  cd ~/snowboy
  python3 assistant_grpc_snow_demo.py NeGoogle.pmdl
  この他、
  python3 assistant_grpc_snow_demo.py snowboy.umdl も試せます。

カテゴリー: RaspberryPi, お知らせ パーマリンク

AIY Voice KIT にAlexaとGoogleAssistantを同居 への3件のフィードバック

  1. fujii のコメント:

    4月2日
    「アレクサ」のヒット率を高めた、alexa_02092017.umdl を使うように変更した。

  2. fujii のコメント:

    ついでに、次をサポート。Googleの方が偉そうになりました。
    ・ね~グーグル、Alexaを英語モードで呼び出して
     What time is it?
    ・ね~グーグル、Alexaを日本語モードで呼び出して
     今日の選抜野球の結果は?

    やっぱりエスカレートの機能追加です。フフフ・・・
    ・ね~グーグル、Alexa も一緒に聞いて
     「日本の首都はどこですか」
     Alexaは?
      日本の首都は東京です。
     そだね~、
      日本の首都は、トウキョウです。
    ・ね~グーグル、Alexa も一緒に聞いて
     「選抜高校野球で優勝したのはどこですか」
     Alexaは?
      5対2で、大阪の大阪桐蔭高校が勝ちました。
     そだね~、
      すみません、お役に立てそうにありません。

  3. fujii のコメント:
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    # Copyright 2017 Google Inc.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    """A demo of the Google Assistant GRPC recognizer."""
    
    # from google.assistant.library.event import Event, EventType, IterableEventQueue
    import snowboydecoder  # Hotword 検出器
    import sys, os, signal, time, threading, select,fcntl
    from subprocess import Popen, PIPE, call, check_output
    
    import logging
    
    import aiy.assistant.grpc
    import aiy.audio
    import aiy.voicehat
    
    logging.basicConfig(
        level=logging.INFO,
        format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
    )
    
    buttonFlag = False  # 押しボタン関連
    buttonNone = False  # Ctrl+Cの伝達に使用
    speakFlag = False   # 発話中
    status_ui = None    # LED状態表示アクセス
    detector  = None    # Hotword検出器インスタンス
    player = None       # Alexaプロセス
    player2= None       # Alexaプロセス(sudo)
    think  = None       # Thinking...
    speak  = None       # Speaking...
    arch_v6 = check_output(["uname -a"],shell=True).decode('utf-8')
    if arch_v6.find("armv6")<0:arch_v6 = False
    else:arch_v6 = True
    print ("ArchitectureV6=",arch_v6)   # Pi3 or PiZero
    
    # events = IterableEventQueue()
    interrupted = False # Hotword検出器脱出要求
    def signal_handler(signal, frame):
        global buttonNone, interrupted
        aiy.voicehat.get_led().stop()
        buttonNone = True 
        interrupted = True
    # capture SIGINT signal, e.g., Ctrl+C
    signal.signal(signal.SIGINT, signal_handler)
    
    def interrupt_callback():        # Hotword検出中の脱出チェックループ
        global interrupted
        return interrupted
    
    def detect_callback1(word="t"):  # Hotword検出出口(Alexa)
        global player, detector, interrupted, speakFlag, buttonFlag
        if player:player.poll()
        if player.returncode==None:
            player.stdin.write((word+"\n").encode('utf-8'))
            player.stdin.flush()     # Alexaへの単発要求(tはTalk)
            if word!="t": return
            detector.stream_in.stop_stream()  # 検出中断
            speakFlag = True
            butttonFlag = False
            interrupted = True
            status_ui.status('listening')
            print('Listening...')
            snowboydecoder.play_audio_file(snowboydecoder.DETECT_DING)
    
    def detect_callback2():          # Hotword検出出口(Google)
        global buttonFlag, interrupted, detector
        if buttonFlag==False:
            buttonFlag = True        # ボタン押下と同等
    #       events.offer(Event(EventType.ON_NO_RESPONSE,{'button':'ON'}))
        detector.stream_in.stop_stream()  # 検出中断
        interrupted = True
    
    def on_button_press():           # ボタン押下検出出口(Google)
        global buttonFlag, interrupted, think, speak, speakFlag
        print('Button pressed')
        think = False
        speak = False
        if speakFlag==True:
            speakFlag = False        # Alexaニュースの発話終了を手動で
            return
        if buttonFlag==False:
            buttonFlag = True
    #       events.offer(Event(EventType.ON_NO_RESPONSE,{'button':'ON'}))
        interrupted = True           # Hotword検出中の脱出
    
    def myTh_out(player):            # Alexa Print出力の監視チェック
        global detector, buttonNone, interrupted, speakFlag, think, speak
        think = False
        speak = False
        # set non-blocking flag while preserving old flags
        fl = fcntl.fcntl(player.stdout, fcntl.F_GETFL)
        fcntl.fcntl(player.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        while not player.stdout.closed:
            (ready,_,_) = select.select([player.stdout],[],[],0.1)  # 100msごと
            if ready and not player.stdout.closed:
                buf = player.stdout.read()
                if buf:
                    buf = buf.decode().rstrip('\n')
                    print(buf)
                    if ("not connected!" in buf) or ("idle!" in buf):
                        if think:          # Thinkingのまま終了
                            think = False
                            speak = True
                            speakFlag = True
                        elif speak==True:  # Speakingの順調な終了
                            if arch_v6: time.sleep(3)
                            speak = False
                            speakFlag = False
                            detector.stream_in.start_stream()
                    else:                  # 途中の状態遷移
                        if "Thinking.." in buf: think = True
                        if "Speaking.." in buf:
                            think = False
                            speak = True
                        continue
    
    def myThread(player):            # Alexa へのコマンド入力チェック
        global detector, buttonNone, interrupted, speakFlag, speak
        while not player.stdin.closed:
            (ready,_,_) = select.select([sys.stdin],[],[],3)  # 3秒ごとにチェック
            if ready:
                word = input()       # 入力バッファに文字がある
            else: continue
            if len(word)==0:         # Ctrl+Cの押下を想定
                buttonNone = True 
                interrupted = True
                return
            if player:player.poll()
            if player.returncode==None:
                if word[0]=="p" or len(word)==1 and word in "tshi1234": # 単発+p
                    speak = False
                    speakFlag = False
                    detect_callback1(word.rstrip('/').replace('/','\n')) #"p"
                    continue 
                if word=="q":                                           # "q"
                    stop_alexa()
                    break
                sudo_exec(word.rstrip('/').replace('/','\n'))           # それ以外
    
    def stop_alexa():      # Alexaの停止
        global player
        if player:player.poll()
        if player.returncode==None:
            player.stdin.write('q\n'.encode('utf-8'))
            player.stdin.flush()
            try:
                outs, errs = player.communicate()
            except BlockingIOError:
                Popen(["killall","python3"])
    
    def sudo_exec(word):       # 一旦Alexaを停止し、sudo Alexaでコマンド実行
        global player2
        print(word)
        stop_alexa()
        forAlexa(True)
        time.sleep(5)
        if arch_v6: time.sleep(5)
        player2.stdin.write((word+"\n").encode('utf-8'))
        player2.stdin.flush()
        time.sleep(1)
        player2.stdin.write(("q\n").encode('utf-8'))
        player2.stdin.flush()
        outs, errs = player2.communicate()
        print (str(errs))
        time.sleep(1)
        forAlexa()
        time.sleep(2)
    
    thread = None
    th_out = None
    def forAlexa(sudo=False):  # Alexaの起動と入出力2スレッド化
        global thread, player, player2
        if sudo:
            player2 = Popen(["sudo","/home/pi/build/SampleApp/src/SampleApp", \
                "/home/pi/build/Integration/AlexaClientSDKConfig.json"],stdin=PIPE)
        else:
            player = Popen(["bash","-lc","'/home/pi/startAlexa.sh'"],stdin=PIPE,stdout=PIPE)
            thread = threading.Thread(target=myThread, args=(player,))
            thread.start()
            th_out = threading.Thread(target=myTh_out, args=(player,))
            th_out.start()
            time.sleep(2)
            detect_callback1('p/1/2/2/2/2/q'.replace('/','\n'))
    
    def main():
        global thread, player, status_ui, detector, buttonFlag, buttonNone, \
               interrupted, speakFlag, think, speak
        model = "NeGoogle.pmdl"
        if len(sys.argv)>1: model = sys.argv[1]        # GoogleのHotwordは引数から
        status_ui = aiy.voicehat.get_status_ui()
        status_ui.status('starting')
        assistant = aiy.assistant.grpc.get_assistant()
        button = aiy.voicehat.get_button()
        button.on_press(on_button_press)               # ボタン押下イベント出口登録
        hotwords  = ["resources/alexa_02092017.umdl" , "resources/"+model]
        senses    = [0.2, 0.3]                             # Hotwordの揺れ許容度
        callbacks = [detect_callback1, detect_callback2]   # それぞれの出口登録
        detector = snowboydecoder.HotwordDetector(hotwords, sensitivity=senses)
        forAlexa()                                     # Alexaの起動
        print('\033[96mListening\033[0m... Press Ctrl+C to exit')
    
        with aiy.audio.get_recorder():
            while True:
                status_ui.status('ready')
                print('Press the button and speak')
                buttonFlag = False
                interrupted = False
                detector.start(detected_callback=callbacks,
                   interrupt_check=interrupt_callback,
                   sleep_time=0.05)
                if buttonFlag==False:                  # Hotword検出以外の脱出
                    if buttonNone: break                  # Ctrl+C
                    while speakFlag==True:                # Alexaの呼び出しの場合
                        time.sleep(1)
                    continue  # Alexa
                butttonFlag = False                       # Google呼び出し
                status_ui.status('listening')
                print('\033[96mListening\033[0m...')
                text, audio = assistant.recognize()       # Google音声認識
                if text:
                    print('You said "', text, '"')
                    if 'Alexa も' in text and '聞いて' in text:     # Alexaとの共聴
    #                                   detect_callback1()の代わり
                        detector.stream_in.stop_stream()
                        player.stdin.write('t\n'.encode('utf-8'))
    #                   call(["n2tts -o - Alexaからね| aplay"],shell=True)
    #                   detect_callback1()
                        player.stdin.flush()
                        text, audio = assistant.recognize()     # Google音声認識
                        text2, audio2 = assistant.recognize()     # Google,Alexa音声認識
                        print('You said "', text, '"')
                        if audio:
                            print('Alexa said "', text2, '"')
                            if text2 and "ニュース" in text2:
                                player.stdin.write('2\n2\n'.encode('utf-8'))
                                player.stdin.flush()
                                time.sleep(0.5)
                                player.stdin.write('2\n2\n'.encode('utf-8'))
                                player.stdin.flush()
                                time.sleep(0.5)
                                call(["n2tts -o - Alexa、あとでね?| aplay"],shell=True)
    #                                                  話の腰を折る
                                speakFlag = True
                        if audio:
                            if speakFlag==False: call(["n2tts -o - そぅだねえ~?| aplay"],shell=True)
                                                          # 正常発話に相打ちする
                            aiy.audio.play_audio(audio)   # 自分の主張
                            if speakFlag==True:
                                detector.stream_in.stop_stream()
                                call(["n2tts -o - それでAlexaは?ボタンを押すまでね。| aplay"],shell=True)
                                player.stdin.write('1\n1\n'.encode('utf-8'))
                                player.stdin.flush()      # Alexaに話を続行させる
                                while speakFlag:
                                    text, audio2 = assistant.recognize()
                                    print('Alexa said "', text, '"')  # Alexa発話の筆記
                                    if text=="": break    # ボタン押下又は発話終了
                        detector.stream_in.start_stream()
                        continue
                    if 'Alexa を' in text and '呼び出し' in text:
                        if '英語' in text:              # 英語モードでのAlexa呼出
                            sudo_exec("c\n1\n1\n")
                        elif '日本語' in text:          # 日本語モードでのAlexa呼出
                            sudo_exec("c\n1\n6\n")
                        detect_callback1()              # Hotword検出済み
                        while speakFlag==True:
                            time.sleep(1)               #    発話終了を待つ
                        continue
                    if text == 'さようなら':
                        print('Bye!')
                        break
                if audio:
                    aiy.audio.play_audio(audio)
                detector.stream_in.start_stream()
        aiy.voicehat.get_led().stop()
        stop_alexa()
        if th_out:th_out.join()
        if thread:thread.join()
        print("Alexaは打ち切られました ")
        detector.terminate()
        Popen(["killall","python3"])
    
    if __name__ == '__main__':
        main()
    

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です