黒電話 Bluetooth ハンズフリー機化-その8

KRay
Written by KRay on
黒電話 Bluetooth ハンズフリー機化-その8

前回、D-Bus 経由で oFono サービスを呼び出して iPhone で電話をかけました(Dialing 状態 → Outgoing 状態)。
今回は、oFono から通話開始の通知を受け取って Talking 状態にするところを実装していきます。

そろそろ説明が困難になってきました

oFono のサンプルコードから電話を掛ける時は、 VoiceCallManager オブジェクトを使って行うことがわかりました。
VoiceCallMagager の仕様はリポジトリの doc ディレクトリ配下にありました。
voicecallmanager-api.txt

メソッドが Dial 以外にもたくさんあり、いろいろできそうですね。
Methods より下の方を見ると Signals という項目があります。

Method と Signal

D-Bus の世界において、Method が アプリからサービスに対して送る通知であるのに対し、Signal はサービスからアプリに送られてくる通知のことです。
D-Bus Tutorial

アプリは、サービスの中の関心のある通知をリスンしておき、通知が発生したときに受け取ることができます。
Pub/Sub メッセージングモデルですね。

Pub/Sub メッセージングモデルは、雑誌の購読がモチーフになっています。
雑誌(Topic)を発行する発行者(Publisher)と、雑誌を購読する購読者(Subscriber)とが登場人物です。
発行者は雑誌を発行(Publish)し、購読者は興味のある雑誌が発行されたら購読(Subscribe)します。

CallAdded シグナル

仕様書の Signals の項目をみると、CallAdded という Signal があります。
これは、通話が開始されたときに発生する通知です。

Signal that is sent when a new call is added. It contains the object path of the new voice call and also its properties. Applications get the whole properties via this signal and don’t need to call GetProperties on the voice call object.

つまり、アプリではこの通知を受け取ったときに通話が開始されたと判断すればいいわけでです。

VoiceCall オブジェクト

この通知には、 VoiceCall オブジェクトのパスが含まれます。
[variable prefix]/{modem0,modem1,…}/{voicecall01,voicecall02,…}
このようなフォーマットです。

VoiceCall オブジェクトとはなんでしょう?仕様書を見てみます。
voicecall-api.txt
なんなのか、書いてないですね。

Methods には Hugup や Answer があります。それぞれ、電話を切る、(かかってきた)電話に応答するもののようです。

また、d-feet の画面で oFono サービスを監視していると VoiceCall オブジェクトは、通話が開始されると作成され、通話が終了すると消滅していました。
つまり、VoiceCall オブジェクトは通話中の間だけ存在します。 VoiceCall Object

これらのことから、 VoiceCall オブジェクトは通話を表すオブジェクトであるようです。 どこかで使いそうですね。

また、VoiceCall オブジェクトは複数存在することができるようです。
直感的には1つですよね。複数存在するのはどういったシチュエーションなんだろう。

Signal のリスン

ruby-dbus でのリスン方法は、ここに記載がありました。

Signals are calls from the remote object to your program. As a client, you set yourself up to receive a signal and handle it with a callback. Then running the main loop triggers the callback. You can register a callback handler as allows:

オブジェクトに対して受け取りたい通知を on_signal で指定し、通知を受け取ったら実行する処理をコールバック関数で記載するようですね。
そして、main ループを回せとあります。

main ループに関しては ここに記載がありました。

loop = DBus::Main.new
loop << bus
loop.run

プログラミングして実行してわかりましたが、この DBus::Main ループはスレッドになっています。

マルチスレッド

すでに、黒電話アプリには GPIO を検知するためのスレッドが存在します(前出の main.rbloop)。
そして、D-Bus のシグナル検知用の DBus::Main ループが追加されます。
つまり黒電話アプリはこれからマルチスレッドになります。

イベント処理の順番

そこで困るのがイベント処理の順番です。
イベントは、GPIO に応じて発生しますし、D-Bus からのシグナルに応じても発生します。
イベントの発生した順に処理すると、状態遷移がめちゃくちゃになってしまいます。

具体的に、電話をかける場面で説明します。

電話をかけて(Outgoing 状態)、相手が電話に出る前に(通話開始を待たずに)受話器をフックに置いた場合、GPIO 検知スレッドで ON_HOOK イベントが発生し、状態遷移図の通り Waiting 状態にします。
Waiting 状態になった後、通話開始されたらどうなるでしょうか。通話オブジェクトが作成され、通話が始まったことになってしまいます。 受話器が置いてあるのに通話が始まるのは明らかにおかしいです。

状態遷移図(再掲)

順を追って考えれば、矛盾が出ないようにプログラミングできるかもしれません。
しかし、D-Bus の Method のレスポンスは非同期で、Signal もいつ受け取るかわかりません。
さらに GPIO のイベントもいつ発生するかわかりません。
発生する順番のパターンを洗い出すのは非常に面倒です。

あつかう Method や Signal, GPIO が増えれば、パターンもねずみ算的にふえます。

むり

Queue の導入

そこで、どのスレッドでイベントが発生しても順を追ってイベントを処理できるように Queue を導入します。

class Thread::Queue

Queue はスレッド間の FIFO(first in first out) の通信路です

各スレッドで発生するイベントを一旦 Queue に入れるようにし、取り出して一つ一つ実行するようにすれば、実行する順番が保証されます。

うってつけ!

Queue に格納されたイベントの取り出しも、独自のスレッドで行います。これは EventDispatcher で行うようにします。
これでスレッドは以下の3つです。

  • GPIO
    GPIO の信号を読み取り、イベントを発生させ Queue に格納する(push)。
  • D-Bus
    D-Bus の通知を受け取り、イベントを発生させ Queue に格納する(push)。
  • EventDispatcher
    Queue に格納されたイベントを取り出して(pop)、Context に渡す。

イベント発生時にそのままイベントを Queue に 格納していたら、取り出す順番は変わらずこれまでと同じです。Queue に格納する順番を制御する必要があります。

通常時

通常時は、逐次 Queue に push し、順次 pop して実行します。

優先イベント発生時

コールバックを待つ必要がある処理を優先処理と名付けます。

優先処理が発生した場合、以下のことを行います。

  • EventDispatcher を Lock 状態にする
    • pop 処理 を一時停止する
  • 通常 Queue に加え、優先 Queue、予約 Queue を使い分ける
    • 通常 Queue
      取り出して実行するための Queue
    • 優先 Queue
      コールバックが帰ってきたときに発生させるイベントを一時的に格納するための Queue
    • 予約 Queue
      優先処理が終わるまでの間に発生したイベントを一時的に格納するための Queue

pop を一時的に停止してコールバックを待ちます。
コールバックされると、コールバック関数内で以下の処理を行います。

  • イベントを発行し、優先 Queue に格納する
  • pop を再開する

KRay

KRay

KRay です。物作りが好きでいつも何かしら作っています。できるまでの過程から共有できたら嬉しいです。こんな情報でも必要な人がいると信じて。

Comments

comments powered by Disqus