🪴

Raspberry PiとChatGPTでつくるボイス・アシスタント・ロボット #7

2023/09/17に公開

パキラ環境監視装置1
ChatGPT実験ロボットにセンサーのデータを送信する"パキラ環境監視装置"

パキラ環境監視装置

実験ロボットのサンプルコードbot_wio_node.pyファイルでrequestsモジュールを利用したGETメソッドを使用し、WioNodeからのデータ取得します。

ハードウェア: Wio Node

https://wiki.seeedstudio.com/Wio_Node/

Wio Nodeは、Seeed社が開発した多彩なセンサーや通信機能を統合したArduino互換のボードです。Groveコネクタにつないだ各センサーのデータをクラウドプラットフォームで取得することが出来ます。

今回はWioNode2台に、植木鉢用の「水分センサー」、室内の環境をモニタリングする「気温・湿度センサー」「光センサー」を取り付け100円ショップで購入した小物入れに収めました。

Wio Node

Wio Nodeはスマートフォンのアプリを使って手軽にWifi接続出来ます。Android版はGoogle Playでの配布は終了していますが、以下のリンク(Wio Link Android Appのインストール手順 / Seeed K.K. エンジニアブログ)よりAPKファイルをダウンロードしてアプリをつかうことができます。

また、API用のサーバーが以前はアメリカと中国のみだったのですが、現在は日本リージョンが追加されています(日本にWioサーバーを設置しました / Seeed K.K. エンジニアブログ)。

アプリの使用方法はasanoqmさんのブログ[1]が参考になります。今回2つのWio Nodeを以下の様に接続しました。

スクリーンショット1

APIは「デバイスの一覧」→「ファームウェア更新画面」→右上のメニューアイコン「API」を選択 により確認することができます。

スクリーンショット2

この画像では光センサーのAPIにGETリクエストを送りセンサーの数値を得ています。APIのアクセストークンはこの画面で確認することができます。

実験ロボットからは、PythonのrequestモジュールのGETメソッドを使い各センサーのデータを取得します。

サンプルプログラム

アクセストークンは.envファイルに記載してください。

.env
wio_access_token_1 = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # WioNode1のAPI Token
wio_access_token_2 = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # WioNode2のAPI Token

各種データの取得と合わせ、気温と湿度から不快指数の計算する関数を追加してあります。

ex_bot_wio_node.py
import os, requests, json, math, dotenv # ---(※1)

# .envファイルからAPI Tokenをロード ---(※2)
dotenv.load_dotenv()
wio_access_token_1 = os.getenv("wio_access_token_1")
wio_access_token_2 = os.getenv("wio_access_token_2")

wio_jp_server = "https://wiolink.seeed.co.jp/v1/node/"  # WioNodeの日本サーバーアドレス

def get_temp(): # ---(※3)
    url_temp = wio_jp_server + "GroveTempHumiSHT35I2C0/temperature?access_token=" + wio_access_token_2
    response = requests.get(url_temp)
    data = json.loads(response.text)
    temp = round(data["temperature"],1)
    return temp

def get_hum(): # ---(※4)
    url_hum = wio_jp_server + "GroveTempHumiSHT35I2C0/humidity?access_token=" + wio_access_token_2
    response = requests.get(url_hum)
    data = json.loads(response.text)
    hum = round(data["humidity"],1)
    return hum

def get_lux(): # ---(※5)
    url_lux = wio_jp_server + "GroveDigitalLightI2C0/lux?access_token=" + wio_access_token_1
    response =requests.get(url_lux)
    data = json.loads(response.text)
    lux = data["lux"]
    return lux

def get_moist(): # ---(※6)
    url_moist = wio_jp_server + "GroveMoistureA0/moisture?access_token=" + wio_access_token_1
    response =requests.get(url_moist)
    data = json.loads(response.text)
    moist = data["moisture"]
    return moist

def get_wio(): # ---(※7)
    temprature = get_temp()
    humidity = get_hum()
    # 不快指数を計算する ---(※8)
    discomfort = math.floor(0.81 * temprature + 0.01 * humidity * (0.99 * temprature - 14.3) + 46.3)
    lux = get_lux()
    moisture = get_moist()
    return temprature, humidity, discomfort, lux, moisture

if __name__ == "__main__":
    room_data =get_wio()
    print(room_data)

各モジュールをインポートし(※1)、.envファイルはdotenvモジュールを使用してロードします(※2)。

各関数は、APIリクエストのエンドポイントを指定し、リクエストを送信します。(※3)で気温、(※4)で湿度、(※5)で明るさ、(※6)で鉢植えの水分のデータを取得します。

(※7)で各関数を実行し、まとめてデータを取得します。(※8)が不快指数の計算になります。

実行結果は以下のようになります。

terminal
$ python ex_bot_wio_node.py
(31.5, 58.3, 81, 172, 51)

左から「気温」・「湿度」・「不快指数」・「明るさ」・「水分」が表示されました。

コマンド実行(analyze関数)

独自関数analize()により、入力されたテキストが事前に登録されたコマンドと一致するか、条件分岐により振り分けを行います。

chart_5

2章で示したチャート図の赤い枠で囲んだ部分であり、サンプルプログラムはbot_analyzer.pyになります。このファイルの音声コマンドを一部抜粋したもので解説します。

サンプルプログラム

各ファイルのディレクトリは以下のようになります。

terminal
.
├── data
│   └── command_data.json
├── ex_bot_analyzer.py
└── ex_bot_wio_node.py

抜粋された音声コマンドのワードが格納されているJSONファイルcommand_data.jsonは以下のようになります。

command_data.json
{   "wake": ["テスト", "始めてください"],
    "exit": ["終わり","終わりにしてください"],

    "command": {
        "greeting": ["今日は","こんにちは" ,"おはよう","こんばんは"],
        "day_now": ["今日の日付は", "今日は何日"],
        "time_now": ["時間を教えて", "今何時"],
        "room_data": ["部屋の温度は", "部屋の気温は"],
        "pachira_data": ["パキラの水分は", "鉢植えの水分は"],
        "exit": ["終わり","終わりにしてください"]
    }
}

実行ファイルの仕組みはシンプルです。まずは音声コマンドがJSONファイルに記述されたコマンドワードと一致するかループ処理で照合します。そして、「一致すれば指定された関数を実行する」処理を繰り返します。

ex_bot_analyzer.py
from pathlib import Path # ---(※1)
import json, datetime
from ex_bot_wio_node import get_wio

# Jsonファイルからコマンドの配列を読み込む ---(※2)
with open(Path("data/command_data.json"), "rb") as f:
    data = json.load(f)

COMMAND = data["command"]

# コマンドを解析して適切な応答を行う関数 ---(※3)
def analyze(user_input):
    try:
        for word, phrases in COMMAND.items(): # ---(※4)
            command = "unknown"  # 初期値を "unknown" に設定
            for phrase in phrases:
                if user_input in phrase:
                    command = word
                    break  # 一致した場合にループを終了
            if command != "unknown":
                break  # コマンドが一致した場合に外側のループも終了

        if command == "unknown": # ---(※5)
            robot_reply =  "ごめんなさいよく分かりません"

        elif command == "greeting": # ---(※6)
            robot_reply = "ゆっくり霊夢です ゆっくりしていってね"

        elif command == "day_now": # ---(※7)
            # 現在時刻を取得して合成音声で出力
            day_now = datetime.datetime.today().strftime("%-Y年%-m月%-d日")
            robot_reply = "今日の日付は" + day_now + "です"

        elif command == "time_now": # ---(※8)
            # 現在時刻を取得して合成音声で出力
            time_now = datetime.datetime.now().strftime("%-H時%-M分")
            robot_reply = "現在時刻は" + time_now + "です"

        elif command == "room_data": # ---(※9)
            room_data = get_wio()
            robot_reply = "リビングの 気温は" + str(room_data[0]) + "度 湿度は" + str(room_data[1]) + "% 不快指数は" + str(room_data[2]) + " 明るさは" + str(room_data[3]) + "ルクス です"

        elif command == "pachira_data": # ---(※10)
            room_data = get_wio()
            robot_reply = "パキラの水分は" + str(room_data[4]) + "% です"

        elif command == "exit": # ---(※11)
            robot_reply = "会話を終了しました"

        else:
            pass

        return robot_reply

    except TypeError:
        pass

if __name__ == "__main__":
    print(analyze("今日の日付は"))
    print(analyze("今何時"))
    print(analyze("部屋の気温は"))
    print(analyze("パキラの水分は"))

(※1)各モジュールをインポートします。ここでは、別ファイルにある関数get_wioex_bot_wio_nodeファイルからインポートしています。

(※2)上記JSONファイルを読み込み、コマンドの配列COMMANDに格納します。

(※3)コマンドを解析して適切な応答を行う関数analize()を定義します。

(※4)ユーザーが入力したテキストが特定のコマンドに一致するかをチェックし、最初に一致したコマンドを特定します。見つかったら、プログラムはそのコマンドを返し、ループを終了します。

一致した各コマンドは、指定された関数の実行とともに、ロボットの回答を返します。通常は複数の回答を配列に格納しランダムで返す処理をしますが、このサンプルプログラムでは、一律の回答に留めています。コマンドに一致しない音声が入力された場合は、全て"ごめんなさいよく分かりません"という回答になります。(※5)

(※6)〜(※10)は各コマンドに対しての実行関数と回答の指定になります。

3章で解説したように、コマンドの音声認識ループ処理は「終了ワード」でブレイクします。ロボットの回答"会話を終了しました"はここで設定します(※11)。


以上で 実験ロボット "Voice Assistant Robot" の解説を終わります。次章より、本章で作成した独自関数analize()をChatGPTに置き換える実験をしていきます。

脚注
  1. Wio Nodeのセットアップとデータの取得 / fabble ↩︎

Discussion