家庭内でビデオ通話中を知らせる仕組みを作る


動機

テレワーク時代。夫婦揃ってそれぞれの仕事においてビデオ通話なんてことをやっている日々ではあるが、こんなことは無いだろうか?

  • ビデオ通話中なのに近くで声を出してしまった。
  • ドア閉めて部屋に入っていると、ビデオ通話中かわからない。(そしてノックされるなど。)

ということで、手始めにビデオ通話中かどうかを知らせるための仕組みとして、ON AIRランプなるものを買ってみた。
Amazon で1,440円也。

大変シンプルなランプで、単3電池3本もしくはUSB-C給電により動く。スイッチも物理的にカチカチするものである。
ビデオ通話をしているときには、これをオンにして周りに知らせるという用途に使えそうだ。

理想としては遠隔かつ自動で制御したい。

この記事では、Mac環境+Zoomでそのような装置を作るという内容になっている。

理想形

理想形は以下のような感じ。何も意識しなくても、ビデオ通話をしているのであれば自動点灯するのが望ましい。
(ON AIR ランプをオンにするのをそもそも忘れるからね。)

結論から言うと、この仕組は現状実現できていない。

Zoom Web API は現状ビデオ通話中かどうかを得ることはできない

Zoom Web APIは現在、自分がビデオ通話中かどうかを取得することはできないようだ。

以下のようなユーザ状態をセットするAPIはある。

ただし、ユーザの状態が変化したときに、WebHookにて通知してくれるものはあるようだが、GETで取得できる方法はなさそうである

WebHookでなんとかしてもいいかもしれないが、これでは初期状態がわからないので、
完全に自動で制御するのは諦ることとなった。

ということで、まずは遠隔で気軽にオン・オフできる程度に留める。

MaBeee を使った電池制御

せっかくいいランプを買ったのに、Raspberry PIの配線がむき出しなってかっこ悪くなってしまうのは良くない。
穴をあけるとか、そういうことはしたくないし、そもそもRaspberry PI自体の電力供給について考えるのは面倒くさい。

そこで、今回購入したON AIRランプが電池駆動できるという特性を生かして、Bluetoothで電池を制御 MaBeee を購入してみた。

単純に、単3電池1本を、単4電池の入ったMaBeeに取り替えるだけで良さそうだ。

ただし、液漏れが怖いので、単3電池を、単4電池+ダイソーで買ったスペーサーに切り替えている。
実際には、MaBeee自体が電気を使うことになり、どうしても他の2本との容量差が生まれ、結果として液漏れが起きる危険性がありそうなので、定期的に点検したほうが良さげではある。

iPhoneアプリのMaBeeeライトで制御するとこんな感じになる。

動画: https://twitter.com/ooharabucyou/status/1351127844081045504

MaBeeeAppMac を利用した制御

正式にはサポートしないとしているが、Mac用の接続用のアプリが用意されている。

から情報を確認し、対象のリポジトリをCloneしてドキュメントの指示に従い、MaBeee.app を起動する。

まずは、適当にクライアント的なものを作る。

mabeee_client.py
import requests
import time
import json

class MabeeeClient:
    base_url = 'http://localhost:11111'

    def __init__(self, options = {}):
        self.options = options

    def __call_api(self, path):
        response = requests.get(MabeeeClient.base_url + path)
        if response.status_code == 200:
            if response.content:
                return json.loads(response.content)
            else:
                return True
        else:
            raise RuntimeError('Failed to call MaBeeeAPI')


    def start_scan(self):
        self.__call_api('/scan/start')

    def stop_scan(self):
        self.__call_api('/scan/stop')

    def search_device(self, device_name, delay=1, limit=30):
        for i in range(limit):
            time.sleep(delay)
            print('Search device...')
            response = self.__call_api('/devices')
            found_devices = [d for d in response['devices'] if d['name'] == device_name]
            if len(found_devices) > 0:
                return found_devices[0]

        raise RuntimeError('Device not found')

    def connect_device(self, device_id):
        self.__call_api('/devices/' + str(device_id) + '/connect')

    def disconnect_device(self, device_id):
        self.__call_api('/devices/' + str(device_id) + '/disconnect')

    def wait_connected(self, device_id, delay=1, limit=30):
        for i in range(limit):
            time.sleep(delay)
            print('Wait connected...')
            response = self.__call_api('/devices/' + str(device_id))
            if response['state'] == 'Connected':
                return True

        raise RuntimeError('Cannot connect device')

    def set_device_pwm_duty(self, device_id, value):
        self.__call_api('/devices/' + str(device_id) + '/set?pwm_duty=' + str(value))

そして、ランプをONにするスクリプトと、OFFにするスクリプトを書く。
(エラーが起きた場合は、Macの通知にメッセージが飛ぶようにしている。)

switch_on.py
#!/usr/bin/python

from mabeee_client import MabeeeClient

# https://localhost:11111/scan/start -> https://localhost:11111/devices をブラウザやPostmanなどで
# コールして、自分のMaBeeeのデバイス名を探し、こちらに設定します。
device_name = 'MaBeeeXXXXX'

client = MabeeeClient()
try:
    client.start_scan()
    device = client.search_device(device_name)
    device_id = device['id']
    client.connect_device(device_id)
    client.wait_connected(device_id)
    # ScanをStopしないと、デバイスに値を設定できないというエラーが出ます。
    client.stop_scan()
    client.set_device_pwm_duty(device_id, 100)
except:
    import traceback
    import os
    traceback.print_exc()
    # エラーが起きたら、Macの通知に知らせます。
    os.system("osascript -e 'display notification \"MaBeee connection error\"'")
switch_off.py
#!/usr/bin/python

from mabeee_client import MabeeeClient

# switch_on.py と同様
device_name = 'MaBeeeXXXXX'

client = MabeeeClient()
try:
    client.start_scan()
    device = client.search_device(device_name)
    device_id = device['id']
    client.disconnect_device(device_id)
    client.stop_scan()
except:
    import traceback
    import os
    traceback.print_exc()
    os.system("osascript -e 'display notification \"MaBeee connection error\"'")

上記、3つのスクリプトを適当な場所に起き、switch_on.py, switch_off.py について 755 のパーミッションを設定する。

chmod 755 switch_on.py
chmod 755 switch_off.py

ショートカットとして登録

仕上げに、キーボードショートカットで、ON AIRランプをオン・オフできるようにする。
Automatorを開き、クイックアクションを新規作成する。

シェルスクリプトを実行を右のエリアにドラッグ

ワークフローが受ける項目を「入力なし」、検索対象を「すべてのアプリケーション」とする。
シェルスクリプトは、先程作った switch_on.py を実行するようにする。

名前は ONAIRオンとしてファイルを保存する。この作業をオフについても実施する。

システム環境設定 -> キーボード -> ショートカットから、
「ONAIRオン」「ONAIRオフ」が選べるようになっているので、ショートカットを登録しておく。

これで、ビデオ通話開始時には「ONAIRオン」ショートカットを実行することで、ランプを点灯させることができる。

動画: https://twitter.com/ooharabucyou/status/1351132734429802497

まとめ

これでビデオ通話ライフがますます向上すると思われる。
が、Zoom Web APIからビデオ通話中の情報が取得できなかったことは大変痛い。

あとから気づいたが、ブラウザやMacのアプリからZoomを制御するSDKがあるようだ。
SDKからミーティングを起動して、自分のマイク・ビデオの状態をON AIRランプに反映させるという線も試せそうだ。
https://marketplace.zoom.us/docs/sdk/custom/introduction

できたらPART2をやりたい