Siriとfirebaseを連携させて、アプリを操作してみる

こんにちは。はじめまして。
クライアントワーク事業部でフロントエンドエンジニアをしている
マチダヨウスケと申します。

www.kayac.com

この記事は、Tech KAYAC Advent Calendar 2020 の21日目の記事になります。

本記事の内容について

TouchDesignerの公式ツイッターでこのような動画がリツイートされていました。

この動画では、どうやらHomebridgeというものを使っているようです。
Homebridgeは、HomeKitに対応していないIoT機器でも制御出来るようにするソフトウェアです。

homebridge.io

今回は、このHomebridgeを使わないで、
Siriとfirebase(realtime database) を使って他のアプリ(TouchDesigner)を操作するシステムを作成してみます。

結果

この動画では、hey siriという音声が流れるので、もしかしたら手持ちのiPhoneが反応する可能性があります。
イヤホンをつけて視聴することをおすすめします。また滑舌が非常に悪いのでご注意ください。

vimeo.com

今回作ったファイルはこちらから閲覧できます。 github.com

システムの概要

システムは単純です。
firebaseのrealtime databaseで何らかの値を保持し、
firebase functionsで値を変更、更新するAPIを作成、
Siriに命令して、そのAPIを叩かせます。

他のアプリ側はdatabaseの更新を監視し、
変更に合わせて動かせばいいだけです。

今回はTouchDesignerで勧めていきますが、
realtime databaseが扱える環境(ブラウザとかvvvvとか、たぶんアンリアルエンジンもできる)
ならなんでも自由にSiriから操作する事ができます。
(UnityはDesktop向けのfirebaseがないので、OSCとかNodeとかで連携される形になると思います)
techblog.kayac.com

firebaseの設定

firebaseの初期設定やデプロイ方法は、たくさんの人が記事をあげてるので割愛します。
今回、firebase functionsを使いますが、
firebase functionsは無料プランで使うことはできなくなったので
従量課金プランに変更します。

速度や手軽さを重視したいので、realtime databaseを使用します。

TouchDesignerのPythonを使ってfirebaseと接続するので、
サービスアカウントから秘密鍵の作成をします。
f:id:machida-yosuke:20201219203242p:plain

Siriの設定

Siriとfirebaseと連携させます。
Apple公式からSiriKitというiphoneアプリとsiriを連携できるものがあり、
このkitを使って専用のアプリを作成する、といったことを最初に考えると思いますが、

SiriKitからできるのは
・メッセージの送信
・メモの作成
・お金の送金
などなど

Siriに対してなんでも命令させて、何でもやってもらえるというわけではないようです。
(検証期間はあまりとれなかったので、もしかしたらできるかもしれないです)

なので、今回はショートカットという機能を使って、アクションを作成します。

アクションからfirebaseのdatabaseを更新するAPIをたたき、
尚且、Siriに任意の言葉を喋らせるアクションを作成します。

下記添付の画像では、Siriに対して「データを初期化して」と命令したときに、

・firebase functionsで初期化するAPIを叩く
・「データを初期化しました」とSiriに言わせる
アクションです。

f:id:machida-yosuke:20201219205500p:plain

あとは、アクションを何個か作るだけです。

TouchDesignerの設定

TouchDesignerの操作は、たくさんの人が記事をあげてるので割愛します。

techblog.kayac.com

TouchDesignerでは、pythonのコードを実行することができます。
また、pipでpythonのライブラリをインストールし、インポートして実行することができます。
pythonとfirebaseを連携するライブラリはpyrebaseを使用します。

まずはTouchDesigner側でpipでインストールしたライブラリへのパスを教えてあげます。

import sys
mypath= '/Users/admins/.pyenv/versions/3.7.2/lib/python3.7/site-packages'
if mypath not in sys.path:
    sys.path.append(mypath)
print(sys.path)

つぎにfirebaseとの接続、databaseの値を取得するクラスを作成します

"""
Extension classes enhance TouchDesigner components with python. An
extension is accessed via ext.ExtensionClassName from any operator
within the extended component. If the extension is promoted via its
Promote Extension parameter, all its attributes with capitalized names
can be accessed externally, e.g. op('yourComp').PromotedFunction().

Help: search "Extensions" in wiki
"""

from TDStoreTools import StorageManager
TDF = op.TDModules.mod.TDFunctions

import pyrebase

class firebase:
    """
  firebase description
  """
    def __init__(self, ownerComp):
        # The component to which this extension is attached
        
        self.ownerComp = ownerComp
        
        self.InitData = {}
        self.UpdateData = {}
        self.Firebase = {}
        self.DB = {}

        # properties
        TDF.createProperty(self, 'MyProperty', value=0, dependable=True,
                           readOnly=False)

        # attributes:
        self.a = 0 # attribute
        self.B = 1 # promoted attribute

        # stored items (persistent across saves and re-initialization):
        storedItems = [
            # Only 'name' is required...
            {'name': 'StoredProperty', 'default': None, 'readOnly': False,
                                    'property': True, 'dependable': True},
        ]
        # Uncomment the line below to store StoredProperty. To clear stored
        #  items, use the Storage section of the Component Editor
        
        # self.stored = StorageManager(self, ownerComp, storedItems)

    def myFunction(self, v):
        debug(v)

    def PromotedFunction(self, v):
        debug(v)
    
    def FirebaseSetup(self):
        config = {
            "apiKey": "AIzaSyDB9lRYBhq9K_IvRAkCnDqFQvSU3dlFLyM",
            "authDomain": "siri-to-app.firebaseapp.com",
            "databaseURL": "https://siri-to-app-default-rtdb.firebaseio.com",
            "storageBucket": "siri-to-app.appspot.com",
            "serviceAccount": "/Users/admins/siri-to-touchdesigner/siri-to-app-firebase-adminsdk-5hxtt-83294d32cf.json"
        }
        self.Firebase = pyrebase.initialize_app(config)
        self.DB = self.Firebase.database()
    
    def GetInitData(self):
        self.InitData = self.DB.child("status").get()

    def InitUpdateData(self):
        self.UpdateData = {}
        
    def StartStream(self):
        def StartDBStream(message):
            print(message["event"])
            print(message["path"])
            print(message["data"])
            self.UpdateData = message["data"]
        self.DB.child("status").stream(StartDBStream)

self.DB.child("status").stream(StartDBStream)
databeseの更新を監視し、更新があった場合、変数に入れます。

databaseの監視は別スレッドで行われ、
別スレッドで行われる処理の中で、TouchDesignerのオペレーターにアクセスすることはできないので、
更新内容を変数にいれ、
timerオペレーターを使って、2秒に1回の間隔で
メンバ変数に値が入ってるか監視します。(2秒に1回の間隔でonDoneを実行)
ここの間隔は使用するPCのスペックに応じて変更します。

def onDone(timerOp, segment, interrupt):
    print('onDone')
    # print(firebase.InitData, 'firebase.InitData')
    # print(firebase.UpdateData, 'firebase.UpdateData')

    if firebase.UpdateData == {}:
        print('none')
        return

    for k in firebase.UpdateData.keys():
        # print(k, firebase.UpdateData[k],'keys')

        for r in range(table.numRows):

            tableName = table[r,0]
            # print(tableName)
            
            if tableName == k:
                table[r,1] = firebase.UpdateData[k]
                print('!!BREAK!!')
                break
    firebase.InitUpdateData()
    return

firebaseクラスのメンバ変数の参照とメソッドを叩くコードを書きます。

firebase = op('firebase')

firebase.FirebaseSetup()
firebase.GetInitData()

for status in firebase.InitData.each():
    key = status.key()
    val = status.val()
    op('table_data').appendRow([key, val])

firebase.InitUpdateData()
firebase.StartStream()

あとは、値の変更にたいして、どのような演出をいれるかを考えて実装するだけです。
f:id:machida-yosuke:20201219212334p:plain

あとがき

Siriとfirebaseを使うことで遠隔でアプリを操作することができました。
これを実装すれば、
「Hey Siri デバックモードに変更して」
といえば、両手がふさがってても、または遠隔でも、
アプリをデバックモードにすることができたりします。
※実用性はほぼありません

最後まで読んでいただき、ありがとうございました!!

今回作ったファイルはこちらから閲覧できます。

github.com

その他リンク

maoudamashii.jokersounds.com

konchi.kayac.jp

音楽著作権:魔王魂