DelphiでSwitchBotをHUB経由で利用する

スマートフォンから家電のスイッチを操作できるガジェットがSwitchBotです。
　[SwitchBot](https://www.switchbot.jp/)

DelphiのTHTTPClientを使ってSwitchBotをSwitchBot Hub経由で利用しようと思います。
THTTPClientはXE8で実装されましたのでそれ以降のDelphiでお試しください。

#最初に用意するもの
- Switchbot
- SwitchBot Hub (Hub miniまたはHub Plus)
- スマートフォンのSwitchBotアプリ

SwithBot HubはHub miniで十分です。違いについては以下のサイトに詳しく書かれています。
　[【迷っている方必見！】SwitchBot Hub MiniとPlusの違いを比較レビューで紹介](https://ryo-simplelife.com/switchbothub-mini-plus-comparison/)

SwitchBotがスマホのアプリからHub経由で利用できるようにします。
設置方法については，以下のサイトが詳しいです。
　[意外に簡単！Switch Bot(スイッチボット)の設定・設置方法](https://shorin-home.com/switch_bot_config)
SwitchBot Hubの設定では，Wifiの設定が必要になります。

SwitchBotをAPI経由で動かすための手順は以下のサイトから学びました。
　[SwitchBotをHub経由でAPIから操作する](https://qiita.com/itouuuuuuuuu/items/874cd992f473f30de45b)

SwitchBotのAPIの利用方法はGitHubに公式サイトがあります。
　[OpenWonderLabs/SwitchBotAPI - GitHub](https://github.com/OpenWonderLabs/SwitchBotAPI)

#認証用トークンの取得
SwitchBotのWeb APIを利用するためには認証用トークンが必要です。スマートフォンのSwitchBotアプリから取得します。
SwitchBotアプリの**`設定`**を表示し，**`アプリバージョン`**を10回タップすると表示されます。
表示された**`開発者向けオプション`**で認証用トークンを確認できます。

#SwitchBotのクラウドサービスをオンにする
アプリからAPIから操作するために，利用予定のSwitchBotの「クラウドサービス」をオンにします。
SwitchBotアプリで，操作するSwitchBotの**`設定`**を開いて，**`クラウドサービス`**をオンにします。

#Delphiでアプリケーションを作る
ここからDelphiでの作業になります。

###DeviceIDを取得する

Windows VCLアプリケーションを新規作成し，フォームに3つのTButtonとTMemoを貼り付けます。
![Snap0002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33902/42a4c254-fb6a-1352-1f27-5a3b1f195f23.png)
以下のUnitを保存して，プロジェクトに追加します。

<details><summary>uSwitchbot.pas</summary><div>

```uSwitchbot.pas
unit uSwitchbot;

interface
(*
OpenWonderLabsのSwitchBotのAPIをHub経由で利用する
p_kato 2021/05/17

press以外のコマンドについてはGitHub参照
https://github.com/OpenWonderLabs/SwitchBotAPI

実際の利用方法については
SwitchBotをHub経由でAPIから操作する
https://qiita.com/itouuuuuuuuu/items/874cd992f473f30de45b
が参考になります。
特に，
・認証用トークンの取得
・クラウドサービスをオンにする
が大切です。
*)

function SwitchBotDeviceListGet(AAuthToken:string):string; // AAuthTokenで登録されたデバイスの一覧を返す
function SwitchBotsFromDeviceList(AData:string):string;    // DeviceIDGetの返す文字列を解析し，デバイス名=デバイスIDを返す。複数ある場合は複数行を返す

function SwitchBotDeviceStatusString(AAuthToken,ADeviceID:string):string; // ADeviceIDのステータスを返す
function SwitchBotDeviceStatusBool(AAuthToken,ADeviceID:string):boolean;  // ADeviceIDのステータスを真偽で返す

function SwitchBotDevicePressString(AAuthToken,ADeviceID:string):string;  // ADeviceIDにPressを送る 結果を文字列で受け取る
function SwitchBotDevicePressBool(AAuthToken,ADeviceID:string):boolean;   // ADeviceIDの電源が入っているかを返す

implementation

uses
  System.Classes, System.SysUtils,
  System.Net.URLClient, System.Net.HttpClient,
  System.RegularExpressions;

const
  SwitchBotURI:string='https://api.switch-bot.com/v1.0/devices'; // SwitchBot API の既定URI

function SwitchBotHeaders(AAuthToken:string):TNetHeaders; // SwitchBot APIで利用するヘッダ
begin
  Result := [ TNameValuePair.Create('Content-Type' , 'application/json; charset=utf8'), //
              TNameValuePair.Create('Authorization', AAuthToken)];
end;

function SwitchBotDeviceListGet(AAuthToken:string):string; // 登録したデバイスの一覧を返す
var
  ResponseContent: TMemoryStream;
  HTTPClient:THttpClient;
  Headers: TNetHeaders;
  st:TStringList;
begin
  Result:='';
  HTTPClient:=THttpClient.Create;
  ResponseContent := TMemoryStream.Create;
  st:=TStringList.Create;
  try
    Headers:=SwitchBotHeaders(AAuthToken);
    HTTPClient.Get(SwitchBotURI, ResponseContent,Headers);
    st.LoadFromStream(ResponseContent,TEncoding.UTF8);
    Result:=st.Text;
  finally
    st.Free;
    ResponseContent.Free;
    HTTPClient.Free;
  end;
end;

function SwitchBotsFromDeviceList(AData:string):string; // DeviceIDGetの返す文字列を解析し，デバイス名=デバイスIDを返す。複数ある場合は複数行を返す

var
  regex: TRegEx;
  match: TMatch;
  s:string;
begin
  regex:=TRegEx.Create('\{"deviceId":"([0-9A-F]+)","deviceName":"([^"]*)","deviceType":"Bot","enableCloudService":true,"hubDeviceId":"([0-9a-fA-F]+)"\}');
  match:=regex.Match(AData);
  while match.Success do begin
    s:= s+match.groups[2].Value+'='+match.groups[1].Value+#13#10;
    match := match.NextMatch();
  end;
  Result:=s;
end;


function SwitchBotDeviceStatusString(AAuthToken,ADeviceID:string):string; // ADeviceIDのステータスを返す
var
  HTTPClient:THttpClient;
  ResponseContent: TMemoryStream;
  Headers: TNetHeaders;
  st:TStringList;
begin
  Result:='';
  HTTPClient:=THttpClient.Create;
  ResponseContent := TMemoryStream.Create;
  st:=TStringList.Create;
  try
    Headers:=SwitchBotHeaders(AAuthToken);
    HTTPClient.Get(SwitchBotURI+'/'+ADeviceID+'/status', ResponseContent,Headers);
    st.LoadFromStream(ResponseContent,TEncoding.UTF8);
    Result:=st.Text;
  finally
    st.Free;
    ResponseContent.Free;
    HTTPClient.Free;
  end;
end;

function SwitchBotDeviceStatusBool(AAuthToken,ADeviceID:string):boolean; // ADeviceIDの電源が入っているかを返す
begin
  Result:=pos('"power":"on"',SwitchBotDeviceStatusString(AAuthToken,ADeviceID))<>-1;
end;

// SwitchBotDeviceCommand SwitchBotに命令を送る

procedure BinaryWrite(AStream:TStream;AEncoding:TEncoding;Value:string); // POSTするDataをTStreamに格納する
var
  Bytes: TBytes;
begin
  Bytes := AEncoding.GetBytes(Value);
  AStream.WriteBuffer(Bytes, Length(Bytes));
end;

function SwitchBotDeviceCommand(AAuthToken,ADeviceID,ASource:string):string; // 汎用のコマンド
var
  HTTPClient:THttpClient;
  ResponseContent: TMemoryStream;
  Source:  TMemoryStream;
  Headers: TNetHeaders;
  st:TStringList;
begin
  Result:='';
  HTTPClient:=THttpClient.Create;
  ResponseContent := TMemoryStream.Create;
  Source:=TMemoryStream.Create;
  BinaryWrite(Source,TEncoding.UTF8,ASource);
  Source.Position:=0;
  st:=TStringList.Create;
  try
    Headers:=SwitchBotHeaders(AAuthToken);
    HTTPClient.Post(SwitchBotURI+'/'+ADeviceID+'/commands', Source, ResponseContent, Headers);
    st.LoadFromStream(ResponseContent,TEncoding.UTF8);
    Result:=st.Text;
  finally
    st.Free;
    Source.Free;
    ResponseContent.Free;
    HTTPClient.Free;
  end;
end;

function SwitchBotDevicePressString(AAuthToken,ADeviceID:string):string;
var
  r:string;
begin
  // BotにPressを送る コマンドについてはGitHub参照 他のコマンドもこの方法で拡張できるはず
  Result:=SwitchBotDeviceCommand(AAuthToken,ADeviceID,'{"command":"press","parameter":"default","commandType":"command"}');
end;

const
  SwitchBotCommandSuccess='{"statusCode":100,"body":{},"message":"success"}'; // コマンドの成功

function SwitchBotDevicePressBool(AAuthToken,ADeviceID:string):boolean;
var
  r:string;
begin
  Result:=SwitchBotDevicePressString(AAuthToken,ADeviceID)=SwitchBotCommandSuccess;
end;
end.
```

</div></details>

そして，フォームのButton1をダブルクリックし，以下のコードを追加します。

```unit1.pas
implementation

{$R *.dfm}

uses uSwitchBot; // 追加したuSwitchbot.pasを利用を宣言する

const
  AuthToken:string='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'; // 取得した認証トークンをここに記述する

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Clear;
  Memo1.Lines.Text:=SwitchBotsFromDeviceList(SwitchBotDeviceListGet(AuthToken));
end;
```

前述のプログラムしButton1をクリックすると，Memo1に**`デバイス名＝DeviceID`**が表示されます。

###取得したDeviceIDのSwitchBotのステータスを取得する
このDeviceIDをコピーしてグローバル定数SwitchBotIDを宣言し，そのステータスを取得できるようにします。
フォームのButton2をダブルクリックし，以下のコードを追加します。

```unit1.pas
implementation

{$R *.dfm}

uses uSwitchBot; // 追加したuSwitchbot.pasを利用を宣言する

const
  AuthToken:string='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'; // 取得した認証トークンをここに記述する
  SwitchBotID:string='XXXXXXXXXXXXX'; // SwitchBotのDeviceIDをここに記述する

procedure TForm1.Button2Click(Sender: TObject);
begin
  Memo1.Lines.Add(SwitchBotDeviceStatusString(AuthToken,SwitchBotID)); // 戻ってきたテキストを表示する
end;
```

実行してButton2をクリックするとMemo1に，
**`{"statusCode":100,"body":{"deviceId":"XXXXXXXXXXXX","deviceType":"Bot","hubDeviceId":"XXXXXXXXXXXX","power":"on"},"message":"success"}`**
が戻ってくるはずです。
なお，SwitchBotの電源が入っているかどうかを調べるためだけなら

```pascal
  if SwitchBotDeviceStatusBool(AuthToken,SwitchBotID) then begin
    Memo1.Lines.Add('ON');
  end else begin
    Memo1.Lines.Add('OFF or NG');
  end;
```
と書くことができます。

###SwitchBotでボタンをPressする
では，SwitchBotをPressしましょう。
フォームのButton3をダブルクリックし，以下のコードを追加します。

```unit1.pas
procedure TForm1.Button3Click(Sender: TObject);
begin
  Memo1.Lines.Add(SwitchBotDevicePressString(AuthToken,SwitchBotID));
end;
```
実行して，Button3をクリックするとMemo1に
**`{"statusCode":100,"body":{},"message":"success"}`**
が戻ってくるはずです。

今回はVCLで書きましたが，uSwitchbot.pasではVCL固有のコードを使っていませんので，FMXでも利用できるのではないかと思います。

#SwitchBot APIを使うにあたっての注意点
実際に試してみて気になることは以下の通りです。
#####一日の利用が1000回に制限されている
GitHubのドキュメントに書かれている通り2021/05/18現在で一日の利用回数が1000回に制限されています。
#####SwitchBotは電池で動作する
電池切れをチェックする方法を見つけることができませんでした。遠隔地にSwitchBotを配置する場合には注意がいると思います。バッテリーの状態はスマホのアプリからは確認できます。
#####ボタンを押している時間を変えることができない。
スマホのアプリでボタンを押している時間を変更することができますが，APIからは長さを自由に変えることができません。押す時間の長さはスイッチ固有の情報になるようです。

#謝辞
SwitchBotのAPIの利用方法について @itouuuuuuuuu 様の記事を参考にしました。手持ちにMacかLinuxがあり，APIの動作確認をまずやりたい場合にはCURLを使うのはありだと思います。
DelphiからAPIを利用するのにTHTTPClientを利用したのですが，なかなかうまくいがず，@pik (Jun HOSOKAWA)様にTwitterでいろいろ教えていただきました。
今回は同期接続で処理を書いていますが，非同期にするともっと使いやすくなるのではないかと思います。
皆様ありがとうございます。