スマートスピーカの自作⑨ ~別Piとの相互通信による温度・湿度・圧の測定と合成音声での読み上げ~

 本投稿は,「スマートスピーカ with Raspberry Pi」に関する投稿です.投稿は内容ごとに分け、下記の通り分割して投稿してゆきます.そして本投稿は投稿⑨の内容になります.
 この投稿⑨では,図1の一連の処理を実現します.

Raspberry Piの初期設定
② Google Assistant SDKの初期設定
③ ウェイクワードを任意ワード化
④ ローカルストレージ上の音楽ファイルを再生する機能の実装
⑤ Raspberry Piの起動と同時にGoogleAssistantの自動実行
⑥ Wake on LANを用いた同ネットワーク内PCの起動
⑦ OpenSSHを用いた同ネットワーク内PCのシャットダウン
⑧ Pi Zero Wと自作シールドとの連携による家電操作
⑨ 別Piとの相互通信による室内の温湿度・気圧測定と合成音声での読み上げ (本投稿)

図1 本投稿で実現する処理内容

 自作スマートスピーカと連携させる別Raspberry Piとして,本投稿では例としてPi 4 Model B(RAM 8GB)を扱っています(OSは,Raspbian 11.1).しかし筆者は,実際の運用時には⑧のPi Zero Wに追加実装して使用しています.つまり本投稿と同じようにPi 4である必要はありません.

 BME搭載モジュールによる計測と通信に用いているPythonのソースコード,また自作スマートスピーカにて使用している「hotword.py」のソースコードは,後ほどGithubにて公開する予定です(公開したら本投稿にリンクを記載します).

目次

  1. 結果
  2. 使うもの
  3. BME搭載センサモジュールの配線
  4. BME搭載センサモジュールによる計測
  5. 計測した温湿度・気圧のtxt出力と自作スマートスピーカへの転送
  6. txtファイル上の文字列を合成音声で読み上げ
  7. 自作スマートスピーカにおける「hotword.py」への処理の組み込み
    参考

1. 結果

 本投稿の内容を実装した結果を,図2に動画で示します.

図2 本投稿の内容を実装した結果

2. 使うもの

  • セットアップ済みのRaspberry Pi
  • BME搭載 温湿度・気圧センサモジュール(スイッチサイエンス
  • ジャンパワイヤ
  • ブレッドボード
  • 連携する自作スマートスピーカ

3. BME搭載センサモジュールの配線

 第3~5章までの作業は,自作スマートスピーカと連携する別Piに対し行うものです.第6~7章での作業は,自作スマートスピーカに対し行うものです.

 BME搭載センサモジュールとRaspberry Piの配線の仕方を図3および表1に示します.また,Raspberry PiのGPIOピンヘッダの内容を,Raspberry Pi公式[1]より引用して,図4に示します.

図3 BME搭載センサモジュールとRaspberry Piの配線
表1 BME搭載センサモジュールとRaspberry Piの配線
BMEセンサモジュールのピン Raspberry Piのピンヘッダへの接続先
SDO GND
SCK GPIO3(SCL)
SDI GPIO2(SDA)
CSB 3V3 power
GND GND
Vcore 未接続
Vio 3V3 power
図4 Raspberry PiのGPIOピンヘッダの内容[1]

 BME搭載センサモジュールのレジスタマップに関しては,スイッチサイエンス[2]に記載されてます.こちらには,Arduino UNOを例に,配線およびサンプルコードが記載されてます.

4. BME搭載センサモジュールによる計測

 BME搭載センサモジュールは,Raspberry PiとI2Cで通信を行うため,「リモートGPIO」と共に「I2C」を有効化します(図5).

図5 メニュー>設定>Raspberry Piの設定>インターフェイス

 BME搭載センサモジュールによる温湿度・気圧は,Pythonで行います.スイッチサイエンス[3]より,Python2.7向けのサンプルコードが公開されています.リンク先より「bme280_sample.py」をダウンロードします.

 さらにI2C通信用のライブラリを,下記コマンドでインストールします(図6).

sudo pip3 install smbus2
図6 I2C通信用ライブラリのインストール

 インストールしたI2C通信用ライブラリは,どうやらPython3で実行しないとエラーを吐きます.そのため,ダウンロードしたサンプルコードを,下記のようにPython2.7からPython3系の記述に書き換えます.書き換える内容は,print文の記述の仕方です.全部で3ヶ所あります.
 筆者は,Python3系用に書き換えたファイル名を「bme280_python3.py」としました.

#def compensate_P(adc_P): におけるprint文の修正

print("pressure : %7.2f hPa" % (pressure/100))
#def compensate_T(adc_T): におけるprint文の修正

print("temp : %-6.2f ℃" % (temperature))
#def compensate_H(adc_H): におけるprint文の修正

print("hum : %6.2f %" % (var_h))

5. 計測した温湿度・気圧のtxt出力と自作スマートスピーカへの転送

  「bme280_python3.py」のコードを元に書き換え,Python上で計測した温度・湿度・気圧を,それぞれ.txtファイルとして出力します.

# bme280_python3.pyの冒頭でインポートしておくもの

import os# ファイル作成のためのもの
#def compensate_P(adc_P): の末尾に加筆

# txtファイルの作成と書き込み
f = open('/home/pi/roomCondition/pressure.txt', 'w')
f.write(" 気圧は、%6.1fヘクトパスカル" % (pressure/100))
f.close()
#def compensate_T(adc_T): の末尾に加筆

# txtファイルの作成と書き込み
f = open('/home/pi/roomCondition/temperature.txt', 'w')
f.write(" 温度は、%-4.1f度" % (temperature))
f.close()
#def compensate_H(adc_H):  の末尾に加筆

# txtファイルの作成と書き込み
f = open('/home/pi/roomCondition/humidity.txt', 'w')
f.write(" 湿度は、%4.1fパーセント" % var_h)
f.close()

 次に,作成した各txtファイルを,自作スマートスピーカの任意のディレクトリに転送します.
 まずはSSHでの接続を行うためのライブラリParamikoを,下記のコマンドでインストールします.

pip3 install paramiko

 もし以降のPythonの実行時にSSHでの通信に対しエラーが出るようであれば,下記コマンドでのインストールも試してみてください.

sudo -H pip install paramiko --ignore-installed

 次に「bme280_python3.py」 に,転送の処理を加筆します.作成した各txtファイルと,転送元(別Pi)と転送先(自作スマートスピーカ)のディレクトリの様子を図7に示します.

# .pyの冒頭でインポートしておくもの

import paramiko# SSHで通信するためのもの
#def readData(): の末尾に加筆

with paramiko.SSHClient() as ssh:
    try:
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect('転送先のIPアドレス', port = 22, username = '転送先のユーザ名', password = '転送先のパスワード')
        sftp_connection = ssh.open_sftp()

        # 下記の括弧内は('転送元のディレクトリとファイル名', '転送先のディレクトリとファイル名')
        # 送り先にもファイルを事前に用意しておく必要がある
        sftp_connection.put('/home/pi/roomCondition/temperature.txt', '/home/pi/roomCondition/temperature.txt')
        sftp_connection.put('/home/pi/roomCondition/humidity.txt', '/home/pi/roomCondition/humidity.txt')
        sftp_connection.put('/home/pi/roomCondition/pressure.txt', '/home/pi/roomCondition/pressure.txt')
        stdin, stdout, stderr = ssh.exec_command('exit')
    except:
        print("対象PCへの接続エラー")
図7 転送元(別Pi)と転送先(自作スマートスピーカ)のディレクトリの様子

 別Pi上で下記コマンドを実行すると,「bme280_python3.py」 が実行できます(図8).

python3 bme280_python3.py
図8 「bme280_python3.py」の実行

6. 任意の文字列を読み上げる合成音声Open JTalkの準備

 この第7章では,自作スマートスピーカの処理上から,txtファイルを指定して合成音声で読み上げます.合成音声には,Open JTalkを使用します.

 これから記述するOpen JTalkのインストールから,女性音声への変更に関しては,ものものテック[4]を参照して行いました.

 下記のコマンドで,Open JTalkをインストールします.ここで,筆者は自作スマートの開発において仮想環境を使用しているため,Open JTalkのインストールコマンドを実行する前に,仮想環境をアクティブにします.

source env/bin/activate
sudo apt install -y open-jtalk open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001

 Open JTalkで任意の文字列を発話させるためのスクリプトファイルを作成します.下記のコマンドで「jtalk.sh」という名前のスクリプトファイルをエディタnanoを用いて作成します.

sudo nano jtalk.sh

 「jtalk.sh」の中身は,下記の用に記述します[4].記述したら,エディタnanoでは「Ctrl+O」で書き込み,「Enter」で書き込むファイルを決定し,「Ctrl+X」でエディタを終了します.

#!/bin/bash
tempfile=$(mktemp)
option="-m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice \
-x /var/lib/mecab/dic/open-jtalk/naist-jdic \
-ow $tempfile"

echo "$1" | open_jtalk $option
aplay -q $tempfile
rm $tempfile

 「jtalk.sh」 を作成し終えたら,下記コマンドで実行可能なように権限を与えます.

sudo chmod 755 jtalk.sh

 ここまでで,Open JTalkで任意の文字列を発話させることはできます.
 しかし音声は,Open JTalkのデフォルトである男性音声になります.筆者は別室でも聞き取りやすいよう,Goodle Assistant SDKによる自作スマートスピーカの音声を,女性音声に設定しています.温度・湿度・気圧の読み上げだけ全く異なる音声にならないよう,Open JTalkの音声も女性音声に変更します.
 まず下記コマンドによって,女性音声のダウンロードと,ダウンロードしたzipファイルの展開を行います.

wget https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/MMDAgent_Example-1.7/MMDAgent_Example-1.7.zip --no-check-certificate

unzip MMDAgent_Example-1.7.zip

 展開してできた「MMDAgent_Example-1.7」フォルダに対し,その中にある「Voice」フォルダを,「/usr/share/hts-voice」下にコピーします.

sudo cp -R ./MMDAgent_Example-1.7/Voice/mei /usr/share/hts-voice/

 先ほど作成した「jtalk.sh」をエディタnanoで実行し,下記のように編集します[4].

#!/bin/bash
tempfile=$(mktemp)
option="-m /usr/share/hts-voice/mei/mei_normal.htsvoice \
-x /var/lib/mecab/dic/open-jtalk/naist-jdic \
-ow $tempfile"

echo "$1" | open_jtalk $option
aplay -q $tempfile
rm $tempfile

7. 自作スマートスピーカにおける「hotword.py」への処理の組み込み

 Goodle Assistant SDKを用いて自作したスマートスピーカにおける「hotword.py」に対し,下記のように加筆します.

# hotword.pyの冒頭でインポートしておくもの

import subprocess # サブプロセスに用いる
import time # sleep()に用いる
import paramiko # PythonからSSH接続及びコマンド実行するために用いる
import os # ファイルの読み書きに使用
#def process_event(event, assistant): への加筆

    if event.type == EventType.ON_RECOGNIZING_SPEECH_FINISHED:
        tempText = event.args['text']

        # 温度・湿度・気圧の計測
        if ("部屋" in tempText) and ("温度" in tempText or "湿度" in tempText or "気圧" in tempText) :
            assistant.stop_conversation()
            responseSoundSubprocess = subprocess.Popen(responseSoundAffirmative.split(' ',4), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
            with paramiko.SSHClient() as ssh:
                try:
                    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                    ssh.connect('別PiのIPアドレス', port = 22, username = '別Piのユーザ名', password = '別Piのパスワード')
                    stdin, stdout, stderr = ssh.exec_command('python3 bme280_python3.py')    
                    time.sleep(0.5) # 計測と転送が終わるまで処理を停止しておく
                    stdin, stdout, stderr = ssh.exec_command('exit')
                except:
                    print("対象PCへの接続エラー")
                # 温度の.txtファイル内の文字列を読み込む
                f = open('/home/pi/roomCondition/temperature.txt', 'r')
                stringTemperature = f.read()                
                subprocess.run(['/home/pi/jtalk.sh', str(stringTemperature)])# 読み込んだ文字列をJtalkで発話させる
                # 湿度の.txtファイル内の文字列を読み込む
                f = open('/home/pi/roomCondition/humidity.txt', 'r')
                stringHumidity = f.read()
                subprocess.run(['/home/pi/jtalk.sh', str(stringHumidity)])# 読み込んだ文字列をJtalkで発話させる                    
                # 気圧の.txtファイル内の文字列を読み込む
                f = open('/home/pi/roomCondition/pressure.txt', 'r')
                stringPressure = f.read()
                subprocess.run(['/home/pi/jtalk.sh', str(stringPressure)])# 読み込んだ文字列をJtalkで発話させる
                # 音声で読み上げるだけでなくコンソールにテキストでも表記する
                print(stringTemperature)
                print(stringHumidity)
                print(stringPressure)
            tempText = ""
            print() 

 以上が,本投稿の内容になります.

参考

  1. Raspberry Pi, GPIO and the 40-pin Header, https://www.raspberrypi.com/documentation/computers/os.html#gpio-and-the-40-pin-header, (参照 2021-1-22).
  2. スイッチサイエンス, BME280搭載 温湿度・気圧センサモジュールの使い方, http://trac.switch-science.com/wiki/BME280, (参照 2021-1-22).
  3. スイッチサイエンス, SWITCHSCIENCE/samplecodes, https://github.com/SWITCHSCIENCE/samplecodes/tree/master/BME280, (参照 2021-1-22).
  4. ものものテック, ラズパイで音声合成をしゃべらせよう, https://monomonotech.jp/kurage/raspberrypi/raspi_speech_synthesis.html, (参照 2021-1-22).