AIスピーカーとFlashAirで鉄道模型を動かしてみた

#AIスピーカーが発売に
　2017年秋ごろから、日本でも各社からAIスピーカーが発売になりました。たまたまGoogle Homeを入手したので、これを使って鉄道模型をコントロールできたら面白いなと思い、構成を考えはじめました。もともとiOSアプリで音声認識を使って鉄道模型をコントロールすることは実現できていたのですが、スマフォよりもAIスピーカーのほうがより自然にコントロールできるような気がしたのでした。
　そこで、今回はAIスピーカー（Google Home）とFlashAirを連携させて、鉄道模型をコントロールしてみます。

#実現すること
　今回作るものが動作している様子を最初にご覧ください。最終的にこれを実現します。
　[YouTube:Google HomeとFlashAirで鉄道模型をコントロールしてみた](https://youtu.be/L_bepQhLWdM)

#全体構成
　今回作るものの全体構成を下図に示します。
![image1.png](https://qiita-image-store.s3.amazonaws.com/0/89137/8943e125-fac9-4bc7-4ef6-7086732293b7.png)
![image2.png](https://qiita-image-store.s3.amazonaws.com/0/89137/4a9964fa-d3a1-c902-d4fc-53631ff8928e.png)

#コンセプト
今回作るもののコンセプトはこのように設定しました。
#
- できるだけコーディングレスで構築すること
- できるだけMicrosoft AzureのPaaSを用いること
- さまざまなAIスピーカーに対応すること

#構成要素
　今回作るものの構成要素は、以下の通りです。
#
- [Google home](https://store.google.com/product/google_home?hl=ja)
- [IFTTT](https://ifttt.com/)
- [Azure Logic Apps](https://azure.microsoft.com/ja-jp/services/logic-apps/)
- [Language Understanding - LUIS](https://www.luis.ai)
- [Azure Functions](https://azure.microsoft.com/ja-jp/services/functions/)
- [Azure IoT Hub](https://azure.microsoft.com/ja-jp/services/iot-hub/)
- [FlashAir W-04](https://flashair-developers.com/ja/discover/overview/w04/)
- DRV8830 （[秋月電子通商](http://akizukidenshi.com/catalog/g/gK-06273/)）
- SDカードスロットDIP化モジュール([秋月電子通商](http://akizukidenshi.com/catalog/g/gK-05818/)
- ブレッドボード
- 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を設定しますので、この時点では空白で良いです。
 特定ワードは、「フラッシュエアー」としました。

![image3.png](https://qiita-image-store.s3.amazonaws.com/0/89137/86592a3e-c320-c20c-69fa-562c11661286.png)


#LUIS
　LUISによる鉄道模型コントロール用文章認識のアプリケーションを新規に作成しておきます。
　LUISアプリケーションの作成方法は、こちらのサイト[「LUIS 入門（Cognitive Services - 2017年12月版 - 1/2）」](http://beachside.hatenablog.com/entry/2017/12/07/000000)が参考になります。

#Azure Logic Apps
　Logic AppsによるオリジナルWebAPIを作成します。[こちらの記事](https://qiita.com/ayasehiro/items/05371e8f5539cc8daa2b)を参照してください。

#Azure Functions
　Logic AppsからAzure IoT HubのC2Dメッセージの送信を行うコネクタが標準で提供されていないため、Azure Functionでメッセージ送信用WebAPIを作成します。作成したメッセージ送信用WebAPIは、先のLogic Appsから呼び出します。[こちらの記事](https://qiita.com/ayasehiro/items/d6a267c10492c6986845)を参照してください。

#Azure IoT Hub
　Azure IoT Hubによるデバイス管理を行います。Azure IoT Hubを新規に作成し、FlashAirをデバイスとして登録します。
　ここでAzure IoT Hubを作成した時の名称と「設定」-「共有アクセスポリシー」にある共有アクセスキー（主キー）を取得しておきます。
　Azure IoT Hubの作成については、以下のチュートリアルを参考にしてください。
#
- [「IoT Hub の作成」](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-raspberry-pi-kit-python-get-started#create-an-iot-hub)

#FlashAir
　FlashAirでは、LuaスクリプトによりI2C接続のモータドライバーをコントロールできるようにします。その上で、さらにAzure IoT HubのC2Dメッセージを定期的に取得するようにコーディングします。

##CONFIGファイルの設定
　FlashAirのSD_WLANフォルダ内のCONFIGファイルに、以下のオプションを追加します。IFMODEはFlashAirのGPIOモードをONにしています。LUA_RUN_SCRIPTはFlashAirの電源投入後すぐに指定のLuaスクリプトを起動するための設定です。
#
```CONFIG
IFMODE=1
LUA_RUN_SCRIPT=/ai_speaker_sample.lua
```
　※SD_WLANフォルダとCONFIGファイルは不可視属性ファイルなので注意してください。

##Luaスクリプトファイル
　FlashAirをAzure IoT Hubに接続するにあたって、cocteau666さんがGitHubの[FlashAir_AzureIoT](https://github.com/cocteau666/FlashAir_AzureIoT)で公開されている以下のソースコードを利用させていただきます。
#
- [FaUtil.lua](https://github.com/cocteau666/FlashAir_AzureIoT/blob/master/FaUtil.lua)
- [FaAzureIoTSAS.lua](https://github.com/cocteau666/FlashAir_AzureIoT/blob/master/FaAzureIoTSAS.lua)

　また、Timestampを取得するため、同じくcocteau666さんがGitHubの[FlashAir_Timestamp](https://github.com/cocteau666/FlashAir_Timestamp)で公開されている以下のソースコードを利用させていただきます。
#
- [FaTimestamp.lua](https://github.com/cocteau666/FlashAir_Timestamp/blob/master/FaTimestamp.lua)

　ただし、今回使うFaTimestamp.luaは、コピーしたあとに９行目以降のコードを削除してください。saveTime関数で使われているos関数はFlashAirでは使えないためです。
　この３つのファイルを、FlashAirのルートフォルダにコピーしておきます。
　
　つぎに、FlashAirのルートフォルダに以下のコードを記述したテキストファイル（ai_speaker_sample.lua）をコピーします。
#
```drv8830sample.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)
--１秒周期に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を駆動させてみた」](https://qiita.com/ayasehiro/items/d44a975feb79c94747cf)を参照してください。

#動作確認
　いろいろな要素を組み合わせたので大変でしたが、これで動くはずです。さっそく試してみましょう。
　LUISで設定したインテントに沿って、Google Homeに命令してみましょう。
　IFTTTで設定した呼びかける特定ワードをつけるのを忘れずに。

　「ねぇ、グーグル。フラッシュエアー、速度150で前に進め」……模型が前に進みます
　「ねぇ、グーグル。フラッシュエアー、速度ゼロで前に進め」……模型が止まります
　「ねぇ、グーグル。フラッシュエアー、速度100で後ろに進め」……模型が後ろに進みます
　「ねぇ、グーグル。フラッシュエアー、止まれ」……模型が止まります。
　インテントに該当しない文言／文章は、速度0になるようにAzure Functionsのプログラムで指定しているため止まるのです。

#まとめ
　たった数センチ離れたところに置いたAIスピーカーとFlashAirを連携させるために、クラウドサービスを渡り歩くことになりましたが、無事、連携させることができました。今回は鉄道模型をコントロールしていますが、コントロール対象は工夫次第でなんでもできるでしょう。音声認識によるデバイスコントロールを、Azureの各サービスを使って実現してみてください。

#参考資料
　FlashAir W-04について
　[第4世代FlashAir W-04](https://flashair-developers.com/ja/discover/overview/w04/)
　[FlashAir開発者向け非公式wiki - W-04特設ページ](http://seesaawiki.jp/flashair-dev/d/W%2d04%C6%C3%C0%DF%A5%DA%A1%BC%A5%B8)
　[Lua関数リファレンス](https://flashair-developers.com/ja/documents/api/lua/#reference)
　[Lua関数リファレンス - I2C](https://flashair-developers.com/ja/documents/api/lua/#I2C)

　FlashAirとAzure IoT Hubの連携について
　[FlashAir_AzureIoT](https://github.com/cocteau666/FlashAir_AzureIoT)
　[FlashAir_Timestamp](https://github.com/cocteau666/FlashAir_Timestamp)

　Azure関連
　[「世界一やさしい「Logic Apps」の操作解説」](http://ascii.jp/elem/000/001/638/1638411/?series=1)