1.センサーデータを取得する – SwiftからPythonコードを呼び出す
この記事はiOSDC2023で発表したセッション、Swiftでりんごを育ててみたの発表の第1部の内容をまとめた記事になります。
以下の図の①の実装についてのものです
「0.導入 – 発表やSwiftGardenの概要紹介 」の記事の続編です。もしまだ読んでいない方は読んでいただけると!
SwitchBotのセンサーからデータを取得する
今回センサーとして利用しているのは以下のSwitchBotの温湿度センサーです
SwitchBotのデータはAPI経由で取得できます(こちらも去年の発表の資料に使用しました)
ただし昨年の発表時と異なり、認証周りの仕組みがv1.1に変更され、API実行時に一意のsignatureを発行する必要があります(参考)。
それ以外に点については、上記の記事に記載の情報と同様です
今回はSwiftをLinux上で動かす想定で、CryptoKitは使用できないので、Linuxでも利用できるswift-cryptoを使用する想定でした。
上記のSwitchBotのサイトには、このsignatureを発行するサンプルが複数の言語で記載されていますが、Swiftを使って実装したサンプルは載っていませんでした。
swift-cryptoを使用したコードの実装例は以下のとおりです
Tokenとシークレットは、SwitchBotアプリから発行できます
import Crypto
static func generateAuthInfo() throws -> SwitchbotAuthInfo {
let token = Secrets.Switchbot.token
let secret = Secrets.Switchbot.clientSecret
let timestamp = String(Int64(Date().timeIntervalSince1970 * 1000))
let nonce = UUID().uuidString
let data = token + timestamp + nonce
let key = SymmetricKey(data: secret.data(using: .utf8)!)
let hmac = HMAC<SHA256>.authenticationCode(for: data.data(using: .utf8)!, using: key)
let sign = Data(hmac).base64EncodedString()
return .init(nonce: nonce, timestamp: timestamp, sign: sign, token: token)
}
しかし今回は諸事情(別の優先度の高いasync-http-clientと一緒にビルド時にRaspberryPiでエラーが出たため)のため、このswift-cryptoを使用したSwiftでの発行ではなく、Pythonを後述のPythonを利用した方法でsignatureを発行します(他の方法を使えばSwiftでも発行できそうでしたが、セキュリティ的に危なそうだったので、断念しました)
PythonのコードをSwiftから呼び出す
昨年の発表でも使用したPythonKitを使ったSwiftからのPythonの呼び出し処理です
昨年の実装と異なる点は、①Macでの実行時にpyenvでバージョンを管理したPythonを利用する、②Pythonで処理した結果を戻り値として返却し、Swift側で利用する、という点です
まずpyenvを使用した場合の実装ですが、そのままだとPythonのコマンド側を読み込めないので、どのバージョンのライブラリを利用するかを指定する必要があります
以下はMacの場合は3.10.6
を利用、ラズパイの場合は3.9.0
を利用するように指定した場合のサンプルコードです
let pythonPath: String
#if os(Linux)
pythonPath = "/lib/arm-linux-gnueabihf/libpython3.9.so"
#else
pythonPath = "/Users/${USER}/.pyenv/versions/3.10.6/lib/libpython3.10.dylib"
#endif
PythonKit.PythonLibrary.useLibrary(at: pythonPath)
do {
try PythonLibrary.loadLibrary()
} catch {
print(error)
}
そしてSwitchBotのページに記載のあるサンプルコードを参考に、以下のようなコードを用意します
import json
import time
import hashlib
import hmac
import base64
import uuid
import sys
def generate_sign(token, secret):
nonce = str(uuid.uuid4())
t = str(int(round(time.time() * 1000)))
string_to_sign = '{}{}{}'.format(token, t, nonce)
string_to_sign = bytes(string_to_sign, 'utf-8')
secret = bytes(secret, 'utf-8')
sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest())
data = {
"token": token,
"timestamp": t,
"sign": str(sign, 'utf-8'),
"nonce": nonce
}
return json.dumps(data) // return string json-formatted
if __name__ == "__main__":
token = sys.argv
secret = sys.argv
result = generate_sign(token, secret)
Package.swiftに上記のgenerate_sign.py
をresourceとして登録し、以下のような形式で上記のコードを呼び出し、受け取るJSON形式の文字列をDecodable
のstruct
に変換することで、Swift側のコードでAPI実行に必要な要素を用意できます
struct SwitchbotAuthInfo: Decodable {
let nonce: String
let timestamp: String
let sign: String
let token: String
}
static func generateAuthInfo() throws -> SwitchbotAuthInfo {
let jsonString = try PythonCall.generateSwitchbotSign()
let jsonData = jsonString.data(using: .utf8)!
let info = try JSONDecoder().decode(SwitchbotAuthInfo.self, from: jsonData)
return info
}
あとは以下のような形式でheaderに必要な情報をセットして、APIを実行すれば、OKです!(以下はAlamofireを使った実装です)
let authInfo = try generateAuthInfo()
let headers: Alamofire.HTTPHeaders = [
.authorization(authInfo.token),
.init(name: "sign", value: authInfo.sign),
.init(name: "t", value: String(authInfo.timestamp)),
.init(name: "nonce", value: authInfo.nonce)
]
これで成功すると、以下のようなJSONを取得できます!(一部の値は省略しています)
{
"message": "success",
"body": {
"humidity": 79,
"deviceType": "Meter",
"temperature": 24.1,
"version": "V2.5",
"battery": 87
}
}
これで必要な温度と湿度のデータを取得できました!
まとめ
この記事では、SwiftGardenでSwitchBotのセンサーのデータをPythonとSwiftを組み合わせて取得する方法をまとめました
次の記事は「2. ラズパイでFirebaseにデータを送る – SwiftのCLIツールを動かす」です!