よくAPIを見ていたらv1.1とあったので、v1.1でちゃんとやりたいなと思ってリトライ。 最新は公式を読んでください。 https://github.com/OpenWonderLabs/SwitchBotAPI
アプリを更新
SwitchBotのアプリがまず古すぎたので(V4.5.0だった…すみませんズボラで…)、ちゃんと更新。2023年10月時点でiOSアプリはV7.7。更新したら登録してた設定が全部消えたと思って一瞬絶望したが、ログアウトしてただけだった。ログインしたらちゃんと復活した。
リクエストに必要な値を準備する
APIがv1.1になったことにより、リクエストのために必要な値が追加で増えたので、準備していく。
tokenとsecret
アプリの 設定 > 開発者向けオプション を10回連打で出てくる「開発者向けオプション」に、「トークン」と「クライアントシークレット」の2種類の文字列が表示されるようになった。トークンはアプリ更新前後で変更がなかったが、クライアントシークレットを今回新しくゲットした。
timestamp(t)
13桁のタイムスタンプが必要とのこと。これはUNIX時間(ミリ秒)のこと。
GitHubのサンプルにある t = 1661927531000
は、UNIX時間の解釈でいうと 2022/08/31 15:32:11(JST) となる。
参考: https://ja.wikipedia.org/wiki/UNIX時間
nonce
nonceとは何かをちょっとよくわかってない状態から開始。ランダムに生成するワンタイムの暗号トークンのことを指し、自由に決めてよいものと解釈した。
signを生成
公式APIを参考にsignを生成する。自分はRubyが好きなのでRubyで試みる。以下たくさん参考。無事取れた!
- OpenWonderLabs/SwitchBotAPI: SwitchBot Open API Documents - #how-to-sign
- Ruby (Rails) で HMAC-SHA256
- class OpenSSL::HMAC (Ruby 3.2 リファレンスマニュアル)
- SwitchBot API v1.1 の認証(Ruby バージョン) | 高木のブログ
- SwitchBot API v1.1をRubyから呼び出す - Qiita
require 'securerandom' require 'base64' require 'httpclient' TOKEN = 'xxxxx...' SECRET = 'xxxxx...' t = (Time.now.to_i * 1000).to_s nonce = SecureRandom.uuid sign = OpenSSL::HMAC.digest('sha256', SECRET, TOKEN + t + nonce) header = { 'Authorization' => TOKEN, 'sign' => Base64.strict_encode64(sign), 't' => t, 'nonce' => nonce, } client = HTTPClient.new res = client.get('https://api.switch-bot.com/v1.1/devices', header: header) puts res.body #=> {"statusCode":100,"body":{"deviceList":[{"deviceId":"D7B...","deviceName":"ハブミニ AD","deviceType":"Hub Mini","hubDeviceId":"...."},...] }, "message": "success"}
余談: SwitchBotサーバ側、Headerのパラメータが先頭大文字だと受け入れてくれなかった件
当初、Rubyの標準ライブラリ net/http で実装していたが、うまく返事を得られなかった。
uri = URI.parse('https://api.switch-bot.com/v1.1/devices') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true req = Net::HTTP::Get.new(uri) req['Authorization'] = token req['sign'] = Base64.strict_encode64(sign) req['t'] = t req['nonce'] = nonce res = http.request(req) puts res.body #=> {"message":"Unauthorized"}
Rubyで生成したsignを、サンプルにあるJavaScriptのコードに適用すると使える…という状況だったためかなり頭を抱えていたが、調べた結果、次のようなことがわかった。
- net/http では、ヘッダーフィールド名の先頭を大文字に変換する。
- SwitchBot API では、ヘッダーフィールド名を先頭大文字にしたもの (
Sign
,T
,Nonce
) は受け付けていない(らしい)。- ゆえに、net/http を使って SwitchBot API を叩いても受け付けてくれない。
- おまけの気付き: 1度認証すると数十秒くらいは Authorization (token) しか見ずに返事をくれている気がする。
- jsサンプルコードで 200 を得てから Rubyの動かないコードを動かすと 同じ200 をしばらく返してくれる。
今回は問題解決のために net/http の利用を諦めて、httpclient を使ったところ、無事リクエストを成功させることができた。
参考:
- class Net::HTTPRequest (Ruby 3.2 リファレンスマニュアル)
- curlでアクセスできるのにrubyでアクセスできない、そんな状況でもデバッグがしたい! - Qiita
- Rubyのnet/httpにおけるヘッダフィールド名の大文字小文字の扱い - Qiita
- Rubyのnet/http でHTTPリクエスト/レスポンスをdumpする方法 - ( ꒪⌓꒪) ゆるよろ日記
デバッグの様子(net/http)
# net/httpの様子をチェック uri = URI.parse('https://api.switch-bot.com/v1.1/devices') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.set_debug_output $stderr # 標準出力に出す req = Net::HTTP::Get.new(uri) # …略…
↓
opening connection to api.switch-bot.com:443... opened starting SSL for api.switch-bot.com:443... SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256 <- "GET /v1.1/devices HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: api.switch-bot.com\r\nAuthorization: xxxxx...\r\nSign: xxxxx...\r\nT: 1696770306000\r\nNonce: 34523524-e0a1-406d-bb20-f2dfcc2f0155\r\nConnection: close\r\n\r\n" -> "HTTP/1.1 401 Unauthorized\r\n" -> "Date: Sun, 08 Oct 2023 13:05:07 GMT\r\n" -> "Content-Type: application/json\r\n" -> "Content-Length: 26\r\n" -> "Connection: close\r\n" -> "x-amzn-RequestId: ae44ece8-bd88-41fd-8842-2c12caafa200\r\n" -> "x-amzn-ErrorType: UnauthorizedException\r\n" -> "x-amz-apigw-id: Me9YoEy3oAMEeIg=\r\n" -> "\r\n" reading 26 bytes... -> "{\"message\":\"Unauthorized\"}" read 26 bytes Conn close
デバッグの様子(httpclient)
# httpclientの様子をチェック client = HTTPClient.new client.debug_dev = STDOUT # 標準出力に出す res = client.get('https://api.switch-bot.com/v1.1/devices', header: header) puts res.body
↓
= Request ! CONNECT TO api.switch-bot.com:443 ! CONNECTION ESTABLISHED GET /v1.1/devices HTTP/1.1 Authorization: xxxxx... sign: xxxxx... t: 1696770516000 nonce: 90c09bc6-08b1-4078-b560-241a4c428e87 User-Agent: HTTPClient/1.0 (2.8.3, ruby 3.2.1 (2023-02-08)) Accept: */* Date: Sun, 08 Oct 2023 13:08:36 GMT Host: api.switch-bot.com = Response HTTP/1.1 200 OK Date: Sun, 08 Oct 2023 13:08:37 GMT Content-Type: application/json Content-Length: 543 Connection: keep-alive x-amzn-RequestId: 32b88a02-9b34-4a76-a4d4-73916e2ca52b x-amz-apigw-id: Me95VGk7IAMEh5Q= X-Amzn-Trace-Id: Root=1-6522a9d5-491163743186f65919874a15;Sampled=0;lineage=c8c2b0f2:0|bf95bacf:0