AIスピーカーが発売に
2017年秋ごろから、日本でも各社からAIスピーカーが発売になりました。たまたまGoogle Homeを入手したので、これを使って鉄道模型をコントロールできたら面白いなと思い、構成を考えはじめました。もともとiOSアプリで音声認識を使って鉄道模型をコントロールすることは実現できていたのですが、スマフォよりもAIスピーカーのほうがより自然にコントロールできるような気がしたのでした。
そこで、今回はAIスピーカー(Google Home)とFlashAirを連携させて、鉄道模型をコントロールしてみます。
実現すること
今回作るものが動作している様子を最初にご覧ください。最終的にこれを実現します。
YouTube:Google HomeとFlashAirで鉄道模型をコントロールしてみた
全体構成
コンセプト
今回作るもののコンセプトはこのように設定しました。
- できるだけコーディングレスで構築すること
- できるだけMicrosoft AzureのPaaSを用いること
- さまざまなAIスピーカーに対応すること
構成要素
今回作るものの構成要素は、以下の通りです。
- Google home
- IFTTT
- Azure Logic Apps
- Language Understanding - LUIS
- Azure Functions
- Azure IoT Hub
- FlashAir W-04
- DRV8830 (秋月電子通商)
- SDカードスロットDIP化モジュール(秋月電子通商
- ブレッドボード
- ACアダプター(5V/1A)
IFTTT
最初に、AIスピーカーからWebリクエストを発行するアプレットを新規に作成します。
IFTTTのアカウントを持っていない場合は、アカウントを作成してください。その後、新しいアプレットを作成します。
「IF this THEN that」において、「this」に「Google Assistant」の「Say a phrase with a text ingredient」を選択します。また「that」には「Web Hooks」の「Make a web request」を選択します。
各設定項目は、下図のように設定します。
なお、WebHooksのURLは、後ほど作成するLogic AppsのオリジナルWebAPIのURLを設定しますので、この時点では空白で良いです。
特定ワードは、「フラッシュエアー」としました。
LUIS
LUISによる鉄道模型コントロール用文章認識のアプリケーションを新規に作成しておきます。
LUISアプリケーションの作成方法は、こちらのサイト「LUIS 入門(Cognitive Services - 2017年12月版 - 1/2)」が参考になります。
Azure Logic Apps
Logic AppsによるオリジナルWebAPIを作成します。こちらの記事を参照してください。
Azure Functions
Logic AppsからAzure IoT HubのC2Dメッセージの送信を行うコネクタが標準で提供されていないため、Azure Functionでメッセージ送信用WebAPIを作成します。作成したメッセージ送信用WebAPIは、先のLogic Appsから呼び出します。こちらの記事を参照してください。
Azure IoT Hub
Azure IoT Hubによるデバイス管理を行います。Azure IoT Hubを新規に作成し、FlashAirをデバイスとして登録します。
ここでAzure IoT Hubを作成した時の名称と「設定」-「共有アクセスポリシー」にある共有アクセスキー(主キー)を取得しておきます。
Azure IoT Hubの作成については、以下のチュートリアルを参考にしてください。
FlashAir
FlashAirでは、LuaスクリプトによりI2C接続のモータドライバーをコントロールできるようにします。その上で、さらにAzure IoT HubのC2Dメッセージを定期的に取得するようにコーディングします。
CONFIGファイルの設定
FlashAirのSD_WLANフォルダ内のCONFIGファイルに、以下のオプションを追加します。IFMODEはFlashAirのGPIOモードをONにしています。LUA_RUN_SCRIPTはFlashAirの電源投入後すぐに指定のLuaスクリプトを起動するための設定です。
IFMODE=1
LUA_RUN_SCRIPT=/ai_speaker_sample.lua
※SD_WLANフォルダとCONFIGファイルは不可視属性ファイルなので注意してください。
Luaスクリプトファイル
FlashAirをAzure IoT Hubに接続するにあたって、cocteau666さんがGitHubのFlashAir_AzureIoTで公開されている以下のソースコードを利用させていただきます。
また、Timestampを取得するため、同じくcocteau666さんがGitHubのFlashAir_Timestampで公開されている以下のソースコードを利用させていただきます。
ただし、今回使うFaTimestamp.luaは、コピーしたあとに9行目以降のコードを削除してください。saveTime関数で使われているos関数はFlashAirでは使えないためです。
この3つのファイルを、FlashAirのルートフォルダにコピーしておきます。
つぎに、FlashAirのルートフォルダに以下のコードを記述したテキストファイル(ai_speaker_sample.lua)をコピーします。
-- FlashAir W-04 + DRV8830 + AzureIoTHub + IFTTT + GoogleHome
require "FaTimestamp"
require "FaUtil"
require "FaAzureIoTSAS"
local md_addr = 0x64 --モータードライバのI2Cアドレス(7bit表記)
STATE_SPEED = 0
STATE_TR = 1
-- config
local iotName = "ここにIoTHubの名称を記載する"
local signingKey = "ここに共有アクセスキー(主キー)を記載する"
local policy = "iothubowner"
local expire = 86400
local deviceName = "ここにデバイス名を記載する"
-- create SAS
local timestamp = getTimestamp()
local expires = timestamp + expire
local resourceUri = iotName ..".azure-devices.net"
local auth = sas.create(fa, resourceUri, signingKey, expires, policy)
--Azure IoT HubのC2Dメッセージの読み込みと完了通知
function getIoTHubMessage()
local b,c,h = fa.request{url="https://"..iotName..".azure-devices.net/devices/"..deviceName.."/messages/deviceBound?api-version=2016-11-14",
method = "GET",
headers = { ["Authorization"] = auth,
["Content-Type"] = "application/json"
},
}
if(c ~= 200) then return end
setSharedMem(b) --取得したメッセージを共有メモリに書き込み
--ヘッダー内のETag部分を抜き出して、完了通知を送信
local spos, epos = string.find(h, "ETag:")
if(spos ~= nil) then
local etag = string.sub(h, spos+7, spos+42)
local b,c,h = fa.request{url="https://"..iotName..".azure-devices.net/devices/"..deviceName.."/messages/deviceBound/"..etag.."?api-version=2016-11-14",
method = "DELETE",
headers = { ["Authorization"] = auth,
["Content-Type"] = "application/json"
},
}
end
end
--I2C通信でデータを送信する
function write_i2c_command(addr, data1, data2)
res = fa.i2c{ mode="start", address=addr, direction="write" }
res = fa.i2c{ mode="write", data=data1 }
res = fa.i2c{ mode="write", data=data2 }
res = fa.i2c{ mode="stop"}
end
--モータードライバーにI2Cコマンドを送信する
function sendMotorDrive(addr, reg, vset, data)
local vdata = bit32.bor(bit32.lshift(vset, 2), data)
write_i2c_command(addr, reg, vdata)
end
--スピード:speednum、方向:trを指定してモータードライバーを駆動する
function controlSpeed(speednum, tr)
local tr_reg = 0x00
if(speednum < 0) then return end
if(speednum > 200) then return end
if (tr == 1) then
tr_reg = 0x01
else
tr_reg = 0x02
end
if(speednum == 0) then
sendMotorDrive(md_addr, 0x00, 0x00, 0x03)
else
sendMotorDrive(md_addr, 0x00, (speednum/10)*3+3, tr_reg)
end
end
--共有メモリからデータを取得する
function getSharedMem()
local b = fa.sharedmemory("read", 0, 4)
if (b == nil) then
return 0
else
STATE_SPEED = tonumber(string.sub(b, 1, 3))
STATE_TR = tonumber(string.sub(b, 4, 4))
end
return 1
end
--共有メモリにデータを書き込む
function setSharedMem(cmd)
local c = fa.sharedmemory("write", 0, 13, cmd)
if (c ~= 1) then
return 0
end
return 1
end
--モータードライバーを初期化、念のため速度0でコマンド送信
res = fa.i2c{ mode="init", freq="100" }
sendMotorDrive(md_addr, 0x00, 0x00, 0x00)
--共有メモリを初期化しておきます
local r = setSharedMem("0000000000000")
if(r ~= 1) then
return
end
sleep(1000)
--1秒周期にAzure IoT hubのC2Dメッセージを確認
--共有メモリの情報を読み込んでモータドライバーを駆動します。
while(1) do
local tmp_spd = STATE_SPEED
local tmp_tr = STATE_TR
getIoTHubMessage()
r = getSharedMem()
if(r == 1) then
if(tmp_spd ~= STATE_SPEED or tmp_tr ~= STATE_TR) then
controlSpeed(STATE_SPEED, STATE_TR)
end
end
sleep(1000)
collectgarbage("collect")
end
モータードライバー
今回使用したモータードライバーとFlashAirの接続は、こちらの記事「FlashAir W-04のI2C機能で、DRV8830を駆動させてみた」を参照してください。
動作確認
いろいろな要素を組み合わせたので大変でしたが、これで動くはずです。さっそく試してみましょう。
LUISで設定したインテントに沿って、Google Homeに命令してみましょう。
IFTTTで設定した呼びかける特定ワードをつけるのを忘れずに。
「ねぇ、グーグル。フラッシュエアー、速度150で前に進め」……模型が前に進みます
「ねぇ、グーグル。フラッシュエアー、速度ゼロで前に進め」……模型が止まります
「ねぇ、グーグル。フラッシュエアー、速度100で後ろに進め」……模型が後ろに進みます
「ねぇ、グーグル。フラッシュエアー、止まれ」……模型が止まります。
インテントに該当しない文言/文章は、速度0になるようにAzure Functionsのプログラムで指定しているため止まるのです。
まとめ
たった数センチ離れたところに置いたAIスピーカーとFlashAirを連携させるために、クラウドサービスを渡り歩くことになりましたが、無事、連携させることができました。今回は鉄道模型をコントロールしていますが、コントロール対象は工夫次第でなんでもできるでしょう。音声認識によるデバイスコントロールを、Azureの各サービスを使って実現してみてください。
参考資料
FlashAir W-04について
第4世代FlashAir W-04
FlashAir開発者向け非公式wiki - W-04特設ページ
Lua関数リファレンス
Lua関数リファレンス - I2C
FlashAirとAzure IoT Hubの連携について
FlashAir_AzureIoT
FlashAir_Timestamp
Azure関連
「世界一やさしい「Logic Apps」の操作解説」