switchbot Handをobnizから動かした


ちょっと前に [switchbot 温湿度計をobnizと連携させた](https://qiita.com/wicket/items/a3529c82171e407eeef3)の次として、switchbot hand
（一番みんながswitchbotといわれて連想するやつ）を連携させました


## Swtichbot APIを読む

前回はMeter（温湿度計）のを読んだので、今回はHandを読みます。


https://github.com/OpenWonderLabs/SwitchBotAPI-BLE


温湿度計の場合はデータの流れがSwitchbotデバイス→obniz→nodejsのみだったので簡単でしたが、
今回はその逆向きもあるので、ちょっとだけ複雑になります。


- ブロードキャスト（アドバタイズメント）のメッセージ
- 接続して行うコミュニケーションモード

のうち、コミュニケーションモードが必須になります。

ブロードキャストの方は温湿度計と同じ形で実装できますので、そちらを参考にしてください。

## コミュニケーションモード

ドキュメントだとこのあたりですね。

![SS20231225120551.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227015/d6bd8127-cda2-f8ba-6ca2-bad0c38b8128.png)


BLEの知識がないと何を言っていることやら、となるわけですが、
要するに、コマンド方式（Request/Response方式）でいろいろコミュニケーション取れるよ、と書いてあります。

例えば、

- obniz → Switchbot にスイッチONにして！と依頼する
- Switchbot → obniz に OKと帰ってくる

とか、

- obniz → Switchbot に状態を尋ねる
- Switchbot → obniz で状態を教えてもらう

みたいな感じで、必ずobniz側からスタートする感じになります。


コマンドが当然バイナリ配列になるわけですが、例えばスイッチONにするコマンドはこの形のようです
![SS20231225121011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227015/3eed507a-5fdb-f5e1-56b0-93198c488c1d.png)

![SS20231225121031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227015/90e621a3-07bc-7efa-ae71-b195b489d3df.png)



読解する前に、ご丁寧にサンプルが書いてありました
![SS20231225121051.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227015/b781423d-eefd-b92b-911f-fd7dc6a4d661.png)


つまり、 `[0x57 0x01 0x00]` を送れば動く、ということです。
obniz的には`characteristics.write([0x57 0x01 0x00])` ですね。

これを書くだけだとResponseが受け取れてないので、responseも受け取れるように作れば、動かすことができます。


## 実装してみる
上記を踏まえて実装を抜粋するとこんな感じ

```typescript


  public async connectWait(
    setting?: Pick<BleConnectSetting, 'retry' | 'forceConnect'>
  ) {
    await this._peripheral.connectWait(setting);

    this._peripheral.ondisconnect = (reason: any) => {
      if (typeof this.ondisconnect === 'function') {
        this.ondisconnect(reason);
      }
    };
    const service = this._peripheral.getService(
      'cba20d00-224d-11e6-9fb8-0002a5d5c51b'
    );
    if (!service) {
      throw new Error(`no service found`);
    }
    const rxFromTargetCharacteristic = service.getCharacteristic(
      'cba20003-224d-11e6-9fb8-0002a5d5c51b'
    )!;
    const txToTargetCharacteristic = service.getCharacteristic(
      'cba20002-224d-11e6-9fb8-0002a5d5c51b'
    )!;

    this._commandSequence = new BleRemoteCommandSequence(
      txToTargetCharacteristic,
      rxFromTargetCharacteristic
    );

    await this._commandSequence.setupWait();
  }

  protected _createCommand(command: number, payload: number[]): number[] {
    return [
      0x57, // Magic Number
      command,
      ...payload,
    ];
  }
  
  protected async executeActionWait(action: SwitchbotBotAction) {
    if (!this._peripheral.connected || !this._commandSequence) {
      throw new Error('connect device at first');
    }
    const results = await this._commandSequence.transactionWait(
      this._createCommand(0x01, [action])
    );
    if (results[0] !== 0x01) {
      throw new Error('execute action failed ' + results[0]);
    }
    return results;
  }

  public async pressWait() {
    return await this.executeActionWait(SWITCHBOT_BOT_ACTION.PushAndPullBack);
  }


```


connect直後に`BleRemoteCommandSequence`を使ってコマンド方式の遣り取りをするインスタンスを作ってます。
`pressWait`が押されたら ` [0x57 0x01 0x00]`のバイナリを`_createCommand`で作り、`_commandSequence.transactionWait`で送ってます。


## 実験してみる



ちゃんと動いているのが確認できました！

https://x.com/9wick/status/1739124944045564269?s=20

そのうちパーツライブラリ化して公開します。