オーディオデバイスをMacに接続した時にDenoでSwitchBotのAPIを叩く

2024/04/14

会議中にライトを点灯させたい

先日、オンラインミーティングが始まったら自動で点灯するオンエアーネオンライト作った という記事を読んで自分もやりたくなり、早速ライトとSwitchBotのスマートプラグを購入しました。

こちらの記事ではカメラの監視をlsofで行うことでオンラインミーティング中であることを判別していましたが、自分の環境の場合は必ずしもカメラをONにしているわけではなかったので、別の方法でオンラインミーティングを判別したいな〜と調べていると、テレワーク中、WEB 会議のひと工夫という記事でオーディオデバイスの接続によって判別することができると知り、こちらを参考にさせてもらい実装を進めました。

実装

完成したものはこちらです。

やることは以下です。

  1. plistWatchPathsによりオーディオデバイスの設定ファイルの変更を監視する
  1. 設定ファイルが変更された時、Denoを実行する

オーディオデバイスの設定ファイルの変更を監視する

macにはlaunchctlというコマンドと仕組みがあり、これを使うことで特定のファイルの監視やコマンドの定期実行などを設定できます。詳しくはman launchd.plist してみてください。

com.mrskiro.on-air.plist というファイルを用意します。ファイル名は一意であれば何でもいいのかなと思いますが、慣例的にcom.xxxのようにしました。

このファイルはPC再起動しても読み込まれるように、/Library/LaunchAgents にシンボリックリンクを貼っておくと良いです。

以下のような内容を指定します。

  • Label
    • 一意なもの
  • WatchPaths
    • オーディオデバイスの設定ファイルを監視したいので/Library/Preferences/Audio/com.apple.audio.SystemSettings.plistを指定します
  • ProgramArguments
    • WatchPathsに指定したパスの内容が変化した時に実行するコマンドを指定します
  • StandardOutPath
  • StandardErrorPath
    • ProgramArgumentsで指定したコマンドの出力先を決めます、今回であればデバッグに使います
    • 不要であれば指定しないか、/dev/null にしておけばいいんだと思います

全体像は以下です。フルパスにGOPATHのなごりがありますが気にしないで下さい。

また、このファイルはシェルスクリプトで動的に生成するようにしています。理由は後述します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>com.mrskiro.on-air</string>

    <key>WatchPaths</key>
    <array>
      <string>/Library/Preferences/Audio/com.apple.audio.SystemSettings.plist</string>
    </array>

    <key>ProgramArguments</key>
    <array>
      <string>/opt/homebrew/bin/deno</string>
      <string>run</string>
      <string>--allow-write</string>
      <string>--allow-run</string>
      <string>--allow-read</string>
      <string>--allow-env</string>
      <string>--allow-net</string>
      <string>/Users/purpleeeee/go/src/github.com/mrskiro/on-air/scripts/index.ts</string>
    </array>
    
    <key>StandardOutPath</key>
    <string>/Users/purpleeeee/go/src/github.com/mrskiro/on-air/log.output.log</string>

    <key>StandardErrorPath</key>
    <string>/Users/purpleeeee/go/src/github.com/mrskiro/on-air/log.err.log</string>
  </dict>
</plist>

設定ファイルが変更された時にDenoを実行する

ProgramArgumentsに指定したscripts/index.tsを用意します。

WatchPathsによる監視は、ファイル変更は感知できるものの何が変更されたかまではわかりません。そのためProgramArgumentsによって実行されたプログラム(今回はDeno)の中で判別した上で、必要であればSwitchBotのAPIをcallするようにします。

全体像はこちら

詰まった点

認証

SwitchBotAPIのREADMEにJavaScriptのサンプルがありますが、これをDenoに置き換えるのにやや時間がかかりました。

https://deno.land/[email protected]/node/crypto.ts を使ったりしてなんやかんやしてましたが、最終的にはhttps://deno.land/x/[email protected]/mod.tshmac すればいいことに気づいてクリアしました。

plist内で変数が使えない

plistに記述する実行コマンドはフルパスである必要があるため、例えばdenoはbrewでインストールしていると/opt/homebrew/bin/deno と書く必要があります。

これらを静的に書いてもよかったのですが、複数PCで動かすことを想定したこともあり、環境依存をさせないようplistファイルをシェルスクリプトで生成するようにしました。シェルスクリプト内であれば動的に各種pathを埋めこめます。

#!/bin/sh

WORKING_DIR=$(pwd)
OUTPUT_LOG_PATH="$WORKING_DIR/log.output.log"
ERROR_LOG_PATH="$WORKING_DIR/log.err.log"
DENO=$(which deno)
SCRIPT_PATH="$PWD/scripts/index.ts"
OUTPUT_FILE="$WORKING_DIR/com.mrskiro.on-air.plist"

CONTENT=$(cat <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>com.mrskiro.on-air</string>

    <key>WatchPaths</key>
    <array>
      <string>/Library/Preferences/Audio/com.apple.audio.SystemSettings.plist</string>
    </array>

    <key>ProgramArguments</key>
    <array>
      <string>$DENO</string>
      <string>run</string>
      <string>--allow-write</string>
      <string>--allow-run</string>
      <string>--allow-read</string>
      <string>--allow-env</string>
      <string>--allow-net</string>
      <string>$SCRIPT_PATH</string>
    </array>
    
    <key>StandardOutPath</key>
    <string>$OUTPUT_LOG_PATH</string>

    <key>StandardErrorPath</key>
    <string>$ERROR_LOG_PATH</string>
  </dict>
</plist>
EOF
)

echo "$CONTENT" > "$OUTPUT_FILE"

echo "The plist file has been created: $OUTPUT_FILE"

やってみて

途中まではNode.jsで書いてましたが、crypto周りで型が欲しくなってきたタイミングでtsxやらts-nodeを用意するのが面倒でDenoに移行しました。

サクッとスクリプト書くのにDenoがとても便利だったのでまた機会があれば使いたい。

そしてミーティングでライトが光るのかっこいい。

画像
by me a coffee