Raspberry PiとChatGPTでつくるボイス・アシスタント・ロボット #7
ChatGPT実験ロボットにセンサーのデータを送信する"パキラ環境監視装置"
パキラ環境監視装置
実験ロボットのサンプルコードbot_wio_node.py
ファイルでrequests
モジュールを利用したGETメソッドを使用し、WioNodeからのデータ取得します。
ハードウェア: Wio Node
Wio Nodeは、Seeed社が開発した多彩なセンサーや通信機能を統合したArduino互換のボードです。Groveコネクタにつないだ各センサーのデータをクラウドプラットフォームで取得することが出来ます。
今回はWioNode2台に、植木鉢用の「水分センサー」、室内の環境をモニタリングする「気温・湿度センサー」「光センサー」を取り付け100円ショップで購入した小物入れに収めました。
- 本体: Grove - WIO NODE
- 水分センサー: Grove - 水分センサー
- デジタル光センサー: Grove - Digital Light Sensor
- 高精度温湿度センサー: Grove - I2C 高精度温湿度センサ(SHT35)
ソフトウェア: Wio Link Android App
Wio Nodeはスマートフォンのアプリを使って手軽にWifi接続出来ます。Android版はGoogle Playでの配布は終了していますが、以下のリンク(Wio Link Android Appのインストール手順 / Seeed K.K. エンジニアブログ)よりAPKファイルをダウンロードしてアプリをつかうことができます。
また、API用のサーバーが以前はアメリカと中国のみだったのですが、現在は日本リージョンが追加されています(日本にWioサーバーを設置しました / Seeed K.K. エンジニアブログ)。
アプリの使用方法はasanoqmさんのブログ[1]が参考になります。今回2つのWio Nodeを以下の様に接続しました。
APIは「デバイスの一覧」→「ファームウェア更新画面」→右上のメニューアイコン「API」を選択 により確認することができます。
この画像では光センサーのAPIにGETリクエストを送りセンサーの数値を得ています。APIのアクセストークンはこの画面で確認することができます。
実験ロボットからは、Pythonのrequest
モジュールのGET
メソッドを使い各センサーのデータを取得します。
サンプルプログラム
アクセストークンは.env
ファイルに記載してください。
wio_access_token_1 = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # WioNode1のAPI Token
wio_access_token_2 = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # WioNode2のAPI Token
各種データの取得と合わせ、気温と湿度から不快指数の計算する関数を追加してあります。
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)が不快指数の計算になります。
実行結果は以下のようになります。
$ python ex_bot_wio_node.py
(31.5, 58.3, 81, 172, 51)
左から「気温」・「湿度」・「不快指数」・「明るさ」・「水分」が表示されました。
コマンド実行(analyze関数)
独自関数analize()
により、入力されたテキストが事前に登録されたコマンドと一致するか、条件分岐により振り分けを行います。
2章で示したチャート図の赤い枠で囲んだ部分であり、サンプルプログラムはbot_analyzer.pyになります。このファイルの音声コマンドを一部抜粋したもので解説します。
サンプルプログラム
各ファイルのディレクトリは以下のようになります。
.
├── data
│ └── command_data.json
├── ex_bot_analyzer.py
└── ex_bot_wio_node.py
抜粋された音声コマンドのワードが格納されているJSONファイルcommand_data.json
は以下のようになります。
{ "wake": ["テスト", "始めてください"],
"exit": ["終わり","終わりにしてください"],
"command": {
"greeting": ["今日は","こんにちは" ,"おはよう","こんばんは"],
"day_now": ["今日の日付は", "今日は何日"],
"time_now": ["時間を教えて", "今何時"],
"room_data": ["部屋の温度は", "部屋の気温は"],
"pachira_data": ["パキラの水分は", "鉢植えの水分は"],
"exit": ["終わり","終わりにしてください"]
}
}
実行ファイルの仕組みはシンプルです。まずは音声コマンドがJSONファイルに記述されたコマンドワードと一致するかループ処理で照合します。そして、「一致すれば指定された関数を実行する」処理を繰り返します。
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_wio
をex_bot_wio_node
ファイルからインポートしています。
(※2)上記JSONファイルを読み込み、コマンドの配列COMMAND
に格納します。
(※3)コマンドを解析して適切な応答を行う関数analize()
を定義します。
(※4)ユーザーが入力したテキストが特定のコマンドに一致するかをチェックし、最初に一致したコマンドを特定します。見つかったら、プログラムはそのコマンドを返し、ループを終了します。
一致した各コマンドは、指定された関数の実行とともに、ロボットの回答を返します。通常は複数の回答を配列に格納しランダムで返す処理をしますが、このサンプルプログラムでは、一律の回答に留めています。コマンドに一致しない音声が入力された場合は、全て"ごめんなさいよく分かりません"
という回答になります。(※5)
(※6)〜(※10)は各コマンドに対しての実行関数と回答の指定になります。
3章で解説したように、コマンドの音声認識ループ処理は「終了ワード」でブレイクします。ロボットの回答"会話を終了しました"
はここで設定します(※11)。
以上で 実験ロボット "Voice Assistant Robot" の解説を終わります。次章より、本章で作成した独自関数analize()
をChatGPTに置き換える実験をしていきます。
Discussion