SwitchBotをPCから操作するPower Automateフロー

在宅中にPCの前にいる時間が全体の90%を超過する私は、PCの前から一歩もうごかず、リモコンにもスマホにもさわらず、さまざまな機器をコントロールすべくSwitchBot APIを利用するためのツールを作成してきました。

これまでVBSやPowershellを使ってAPIをコールしていましたが、今回はPower Automate(デスクトップアプリ:以下PAD)で、SwitchBot APIを使えるようにします。

今回の方式の利点は一度作成してしまえば、あとはPower Automateにログインすれば、どのPCからでも利用できるようになることです。それとPADはWindows標準装備になった(はず)ので、追加でいろいろインストールしたり、スクリプトを用意したりせずに使えます。

準備

SwitchBotのスマホアプリから機器が操作可能な状態になっている必要があります。

SwitchBot APIを利用するためのトークンとクライアントシークレットを発行します。
スマホアプリのメニューからプロフィール → 設定 → アプリバージョンを10回くらいタップすると新たに開発者向けオプションが追加されるのでタップします。トークンを取得をタップするとトークンが表示されます。コピーをタップしてテキストファイルなどに保存しておきます。同様にクライアントシークレットも保存しておきます。

これらが他人に知られると、勝手に照明がついたり、TVのチャンネルが変わったりといったポルターガイスト現象の原因となるので、絶対に漏洩しないように厳重に管理してください。

署名作成フロー

APIコールそのものは1ステップで完了します。が、そこにいたるまでにいろいろやる必要があり、その中で一番めんどくせーのが署名作成です。

SwitchBot APIの要求する署名はトークンと送信時刻(UNIXタイムスタンプ:13桁)とUUIDを連結した文字列を、クライアントシークレットをキーとしてSHA256関数でハッシュした文字列です。

これをそのままフローで作っていけばいいわけですが、Mainフローに組み込むと見通しが悪くなるので「GenerateSign」としてサブフローにしておきます。次のようになりました。

特別なことはしていません。要求通りに作っています。最難関はHMACをどうやっつけるかだなと覚悟していましたが、まさかのPADにそれ用のアクションがありました。1ステップで作成できてしまいました。

コピペで使えるコードはこちら。詳しい使い方はページの最後の方にあります。トークンとシークレットを実際の値にしてください。

FUNCTION GenerateSign GLOBAL
SET token TO $'''ここにトークンを入力'''
SET secret TO $'''ここにクライアントシークレットを入力'''
Text.Random UseUpperCaseLetters: False UseLowerCaseLetters: True UseDigits: True UseSymbols: False MinimumLength: 20 MaximumLength: 20 RandomText=> nonce
DateTime.GetCurrentDateTime.Local DateTimeFormat: DateTime.DateTimeFormat.DateAndTime CurrentDateTime=> CurrentDateTime
DateTime.Subtract FromDate: CurrentDateTime SubstractDate: $'''1970/01/01 09:00:00''' TimeUnit: DateTime.DifferenceTimeUnit.Seconds TimeDifference=> TimeDifference
SET UnixTime TO TimeDifference * 1000
Variables.TruncateNumber.GetIntegerPart Number: UnixTime Result=> t
Cryptography.HashTextWithKey HashAlgorithm: Cryptography.KeyedHashAlgorithm.HMACSHA256 Encoding: Cryptography.EncryptionEncoding.UTF8 TextToHash: $'''%token%%t%%nonce%''' HashKey: secret HashedText=> sign
END FUNCTION

機器リスト取得フロー

SwitchBot API経由で機器を操作するには対象の機器のIDが必要です。機器IDはスマホアプリから知ることはできないので、これもSwitchBot APIで取得します。フローは次のようになります。

たぶん、もう少しステップを減らせるでしょうが、基本1回だけしか使わないフローなので、これでよしとします。APIのレスポンスをCSVに整形してファイルに出力します。Excelがある環境の場合は、Excelファイルにした方が取り回しが楽だと思います。

Hub miniに赤外線リモコンを何も登録していない場合は2回目のループは不要です。そのままだとエラーするかも知れません。

PADでのAPIコールは HTTP → Webサービスを呼び出します アクションでできます。ここでヘッダーに作成した署名をセットするわけですが、複数のパラメーターを持たせる書き方がインターネッツのどこにもなくて「,」で区切ったり「;」で区切ったりしてもダメで苦戦しました。次のように書いたらうまくいきました。

コピペ用コードです。CSVファイルのパスを実際のものにしてください。

CALL GenerateSign
Web.InvokeWebService.InvokeWebService Url: $'''https://api.switch-bot.com/v1.1/devices''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json;charset:utf8''' CustomHeaders: $'''Authorization:%token%
sign:%sign%
t:%t%
nonce:%nonce%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: False EncodeRequestBody: True UserAgent: $'''Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.21) Gecko/20100312 Firefox/3.6''' Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> WebServiceResponseHeaders Response=> WebServiceResponse StatusCode=> StatusCode
    Variables.ConvertJsonToCustomObject Json: WebServiceResponse CustomObject=> JsonAsCustomObject
    Variables.CreateNewDatatable InputTable: { ^['deviceId', 'deviceName', 'deviceType'], [$'''''', $'''''', $''''''] } DataTable=> DataTable
    Variables.CreateNewList List=> List
    LOOP FOREACH CurrentItem IN JsonAsCustomObject['body']['deviceList']
        Variables.AddItemToList Item: CurrentItem['deviceId'] List: List
        Variables.AddItemToList Item: CurrentItem['deviceName'] List: List
        Variables.AddItemToList Item: CurrentItem['deviceType'] List: List
        Variables.AddRowToDataTable.InsertItemToDataTable DataTable: DataTable RowIndex: 0 RowToAdd: List
        Variables.ClearList List: List
    END
    LOOP FOREACH CurrentItem2 IN JsonAsCustomObject['body']['infraredRemoteList']
        Variables.AddItemToList Item: CurrentItem2['deviceId'] List: List
        Variables.AddItemToList Item: CurrentItem2['deviceName'] List: List
        Variables.AddItemToList Item: CurrentItem2['remoteType'] List: List
        Variables.AddRowToDataTable.InsertItemToDataTable DataTable: DataTable RowIndex: 0 RowToAdd: List
        Variables.ClearList List: List
    END
    File.WriteToCSVFile.WriteCSV VariableToWrite: DataTable CSVFile: $'''ここにCSVファイルのパスを入力''' CsvFileEncoding: File.CSVEncoding.SystemDefault IncludeColumnNames: True IfFileExists: File.IfFileExists.Overwrite ColumnsSeparator: File.CSVColumnsSeparator.SystemDefault

機器操作フロー

機器操作のフローはシンプルです。

機器IDは機器リスト取得フローで作成したCSVファイルに書いてあります。操作したい機器のIDをdeviceId変数にコピペしてください。

command変数とcommandParam変数は機器をどう操作するかを指定する文字列で機器によって異なります。commandTypeは”command”のままでいいです。どの機器がどう指定できるかは公式サイトに載っているので、自分のやりたいことをさがしてコピペしてきてください。

たとえば、ほぼすべての機器で使える「電源を入れる」操作は次のように指定します。

commandTypecommandcommandParam
commandturnOndefault

それらの変数をWebサービスを呼び出しますアクションの要求本文にJSON形式で入れています。

どうみてもこれで問題ないですが、リクエストしてみるとサーバーから「おまえのリクエストボディの書式JSONじゃねーから!そんな文字コードねーから!」というエラーが返ってきました。

で、原因ですが、詳細の中に隠れている設定項目で「要求本文をエンコードします」がON(→●)になっていたからでした。なんで半角英数字なのにURLエンコードしたらはじかれるかわかりませんが、これをOFF(●←)にしたらすんなり通りました。

コピペ用コードです。

CALL GenerateSign
SET deviceId TO $'''ここに機器IDを入力'''
SET commandType TO $'''command'''
SET command TO $'''turnOn'''
SET commandParam TO $'''default'''
Web.InvokeWebService.InvokeWebService Url: $'''https://api.switch-bot.com/v1.1/devices/%deviceId%/commands''' Method: Web.Method.Post Accept: $'''application/json''' ContentType: $'''application/json;charset=utf8 ''' CustomHeaders: $'''Authorization:%token%
sign:%sign%
t:%t%
nonce:%nonce%''' RequestBody: $'''{
    \"command\": \"%command%\",
    \"parameter\": \"%commandParam%\",
    \"commandType\": \"%commandType%\"
}''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: False EncodeRequestBody: False UserAgent: $'''Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.21) Gecko/20100312 Firefox/3.6''' Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> WebServiceResponseHeaders Response=> WebServiceResponse StatusCode=> StatusCode

シーンの操作

シーンも同様にAPI経由で操作できます。シーン操作の流れも機器と同じく、まずシーンID一覧をゲットして、実行したいシーンのIDを特定したら、それを変数に設定して操作フローを実行します。

シーンID一覧をCSVファイルにするフローです。

シーンを実行するフローです。

シーンの方がcommandをどうするか等を調べる必要がないので、スマホアプリでやりたいことをシーンにしてしまって、それをAPIで実行する作戦が簡単です。

シーンID取得フローのコードです。

CALL GenerateSign
Web.InvokeWebService.InvokeWebService Url: $'''https://api.switch-bot.com/v1.1/scenes''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''charset:utf8
Authorization:%token%
sign:%sign%
t:%t%
nonce:%nonce%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: False EncodeRequestBody: True UserAgent: $'''Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.21) Gecko/20100312 Firefox/3.6''' Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> WebServiceResponseHeaders Response=> WebServiceResponse StatusCode=> StatusCode
    Variables.ConvertJsonToCustomObject Json: WebServiceResponse CustomObject=> JsonAsCustomObject
    Variables.CreateNewDatatable InputTable: { ^['sceneId', 'sceneName'], [$'''''', $''''''] } DataTable=> DataTable
    Variables.CreateNewList List=> List
    LOOP FOREACH CurrentItem IN JsonAsCustomObject['body']
        Variables.AddItemToList Item: CurrentItem['sceneId'] List: List
        Variables.AddItemToList Item: CurrentItem['sceneName'] List: List
        Variables.AddRowToDataTable.InsertItemToDataTable DataTable: DataTable RowIndex: 0 RowToAdd: List
        Variables.ClearList List: List
    END
    File.WriteToCSVFile.WriteCSV VariableToWrite: DataTable CSVFile: $'''ここにCSVファイルのパスを入力''' CsvFileEncoding: File.CSVEncoding.SystemDefault IncludeColumnNames: True IfFileExists: File.IfFileExists.Overwrite ColumnsSeparator: File.CSVColumnsSeparator.SystemDefault

シーン実行フローのコードです。

CALL GenerateSign
SET sceneId TO $'''ここにシーンIDを入力'''
Web.InvokeWebService.InvokeWebService Url: $'''https://api.switch-bot.com/v1.1/scenes/%sceneId%/execute''' Method: Web.Method.Post Accept: $'''application/json''' ContentType: $'''application/json;charset=utf8 ''' CustomHeaders: $'''Authorization:%token%
sign:%sign%
t:%t%
nonce:%nonce%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: False EncodeRequestBody: False UserAgent: $'''Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.21) Gecko/20100312 Firefox/3.6''' Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> WebServiceResponseHeaders Response=> WebServiceResponse StatusCode=> StatusCode

コード使用方法

コピペ用コードはPADにコピペで使えます。

1.新しいフローを作成したら、署名作成フローのコードをコピーします。

2.Mainフローのタブの部分を右クリックして貼り付けます。

GenerateSignがサブフローに追加されます。

3.機器リスト取得フローのコードをコピペします。

4.Mainフローの何もない部分(ここにはまだアクションがありません)を右クリックして貼り付けます。

Mainフローに機器リスト取得フローができます。

この要領でGenerateSign+実行したいMainフローの組み合わせで作成します。あとは各変数を実際の値に置き換えて実行してください。

簡単(?)な実行方法

PADのメニューを開いて、そこから選んで実行では手順が多すぎてスマートじゃないので、デスクトップにショートカットを置いてダブルクリックだけで実行する・・・はずができません。以前普通にできていた記憶があるのでググってみると、なんとフローのショートカットアイコンを作る機能が有料版限定に変更されたようです。何故・・・間違った方向に進んでいるとしか思えない・・・。

やむを得ないので、ショートカットキーを登録できる機能を使うとします。

フロー一覧で対象のフローのメニューからプロパティを表示します。キーボードショートカットで実行のテキストボックス内で登録したいキーの組み合わせを押すと登録されます。

PADを開いていなくてもショートカットキーを押すとフローが実行されます。

ただ、このキーの組み合わせをいちいち覚えてられないので、VBSとかでキーコード送出スクリプトを書いて、それをダブルクリックで実行することになりそうです。結局VBSかよ。

キーコード送出スクリプトはインターネッツに山ほどサンプルがあるので、Bing先生にお願いすれば秒でコーディングしてくれます。

さすが先生、私のスペルミスも華麗に修正してちゃんとうごくコードになっています。実際にあなたが使用するキーで書いてもらいましょう。

それにしても、PADのショートカット作成を突如として有料化したMicrosoftのせこさにがっかりしましたとさ。

おわり。

SwitchBot API公式サイト

GitHub - OpenWonderLabs/SwitchBotAPI: SwitchBot Open API Documents
SwitchBot Open API Documents. Contribute to OpenWonderLabs/SwitchBotAPI development by creating an account on GitHub.