ShellScriptでBluetooth機器にサクッと接続する

## まえがき

この記事は [ちょっとした工夫で効率化！パソナテック Advent Calendar 2020 ](https://qiita.com/advent-calendar/2020/pasonatech-tips)の10日目の記事です。
昨日は @yoshikyoto さんの[CURL の代わりに IntelliJ でカンタン API の動作確認【JetBrains IDE】](https://qiita.com/yoshikyoto/items/39a40c1b59e2bcba8c7f)でした。

## Bluetooth機器の接続をTerminalからやりたい

複数の端末で1つのBluetooth機器を使いまわしている場合、大体のBluetooth機器は最後に接続されたデバイスのみ自動で接続してくれる。

例えばAirpodsをiPhoneとMacで使いまわしてる場合、

iPhoneで接続
↓
接続解除
↓
Macで接続
とするとMacでは接続ボタンを再度押さないといけない。
![20200610184825.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142791/4373e240-d546-b0b5-ff47-bc7b4d6026fe.png)
結構この作業をやることが多いので、Terminalから接続のON/OFFをできるようにする。

## AppleScript in ShellScriptで実現

fzfでデバイスを選択できるようにした。previewにはなんとなくデバイスの情報を出している。
![](https://user-images.githubusercontent.com/17779386/97555696-c1098d80-1a1b-11eb-8646-30e2999d5a94.gif)
**fzfでの選択時、Tabで複数選択すれば一撃で複数のBluetooth機器に接続してくれる。**

ちゃちゃっと試したい人は以下

```bash
$ brew tap Rasukarusan/tap
$ brew install Rasukarusan/tap/fzf-bluetooth-connect
$ bluetooth-fzf
```



<details><summary>ソース</summary><div>
[Github](https://github.com/Rasukarusan/fzf-bluetooth-connect)

```bash
#!/bin/bash

pairedDevices() {
osascript << EOF
    use framework "IOBluetooth"
    use scripting additions
    set _results to {}
    repeat with device in (current application's IOBluetoothDevice's pairedDevices() as list)
        if device's isPaired()
            set _address to device's addressString as string
            set _name to device's nameOrAddress as string
            set _isConnected to device's isConnected as string
            if _isConnected = "1"
                set _isConnected to "✔"
            else 
                set _isConnected to "✗"
            end if
            set end of _results to {_address, "\t", _name, "\t", _isConnected, "\n"}
        end if
    end repeat
    return _results as string
EOF
}

connect() {
local address=$1
osascript << EOF
    use framework "IOBluetooth"
    use scripting additions
    repeat with device in (current application's IOBluetoothDevice's pairedDevices() as list)
        set _address to device's addressString() as string
        if _address = "${address}"
            if device's isConnected()
                device's closeConnection()
            else
                device's openConnection()
            end if
        end if
    end repeat
EOF
}

main() {
    local selected=("$(pairedDevices \
        | sed '/^$/d' \
        | fzf \
            --delimiter $'\t' --with-nth 2,3 \
            --preview \
                'system_profiler SPBluetoothDataType -json 2>/dev/null \
                    | jq -r ".SPBluetoothDataType[].device_title[]["\"{2}\""]
                    | select(type != \"null\")"' \
    )")
    echo "${selected[@]}" | while read line; do
        local address name
        read address name <<< $(echo "$line" | cut -f1-)
        echo "${name}"
        connect ${address} >/dev/null
    done
}

main "$@"
```
</div></details>

## AppleScriptでframework読み込めるの知らんかった

実はAppleScript内では`use framework "フレームワーク名"`とすることでCocoaの機能が使える。
今回はBluetooth関連をいじりたかったので`use framework "IOBluetooth"`を使っている。

```bash
pairedDevices() {
osascript << EOF
    use framework "IOBluetooth"
    use scripting additions
    ...
EOF
```

SwiftやObjective-Cとは若干使い方が異なるが、ほとんど直感的にいける。
例えばBluetoothのペアリング済みのデバイスを取得したいときは下記のような感じ。
([IOBluetooth](https://developer.apple.com/documentation/iobluetooth/iobluetoothdevice)のpairedDevices()を使用)

```bash
pairedDevices() {
osascript << EOF
    use framework "IOBluetooth"
    use scripting additions
    current application's IOBluetoothDevice's pairedDevices() as list
EOF
}

# 実行すると下記のような出力が得られる
# «class ocid» id «data optr0000000070F2E86AB97F0000», «class ocid» id «data optr000000002006E96AB97F0000»
```

`application's IOBluetoothDevice's pairedDevices()`のように`〇〇's`で繋いでいくだけでOK。

## Bluetoothの接続情報はコマンドでも取得可能

今回はペアリング済みのデバイスを取得する際にAppleScriptを使っているが、下記コマンドでも取得できる。

```bash
$ system_profiler SPBluetoothDataType -json
```

jsonで吐き出すことができるので、あとは`jq`でイージーですね。

`system_profiler`で取得できる情報は`SPBluetoothDataType`の他にもあり、`system_profiler -listDataTypes`で一覧が出る。


## 終わり

GUIではなくCUIでサクッと接続することで人生の何秒間は節約できますね！！ﾔｯﾀﾈ！