2. Vaporを動かす- iPhoneとラズパイ間をVaporで通信 3
この記事はiOSDC2022で発表したセッション、Swiftで我が家を より便利に、安全に!の発表の第2部の内容をまとめた記事の第3弾になります。
この記事は、「2. Vaporを動かす- iPhoneとラズパイ間をVaporで通信2」 の記事の続編です。もしまだ読んでいない方は読んでいただけると!
この記事では、CoreMotion
を使って、iPhoneの高度情報をラズパイに送信する方法をまとめます。
iPhoneで高度情報を取得する
家主が在宅中かどうかの判定に使用する位置情報と高度情報をラズパイに送るために、まずはiPhoneでのデータを取得できるようにしましょう
今回位置情報の取得方法については、割愛します(ネットの海にいっぱい参考記事があるので!)
CoreMotionを使って、高度情報を取得する
swiftで高度情報を取得するには、CoreMotion
のCMAltimeter
というclassを使います。CMAltimeter
から取得できる高度情報は、絶対高度
と相対高度
の2つがあります。
今回は、実際に家主(正確にはiPhone)が標高何mにあり、その高さが事前に設定した家の標高と一致するかの判定に利用するので、絶対高度を利用します!
ちなみにSwiftHomeAppのアプリでは参考のため、相対高度の取得方法も実装してあります。
またiPhoneでデータを実際に取得するには、Info.plist
に以下の追記が必要です
Privacy - Motion Usage Description
次に実際に高度情報を取得するコードを記載します。(GitHubはこちら)
①CMAltimeter
のインスタンスを作り、②デバイスで高度情報が取得できるかどうかを確認します。
そしてCMAltimeterインスタンスのstartAbsoluteAltitudeUpdates
メソッドを呼び出すと、そのクロージャーが定期的に呼ばれ、そこから高度情報を取得できます。
絶対高度の場合、data!.altitude
から、m単位でデータを取得できます。
import CoreMotion
private let altimeterMeter = CMAltimeter() // ① setup instance
if CMAltimeter.isAbsoluteAltitudeAvailable() { // ②Check if CMAltimeter is enable or not
altimeterMeter.startAbsoluteAltitudeUpdates(to: OperationQueue.main) { data, error in
// ③Called at a certain timing and can acquire data
if error != nil { return }
print(data!.altitude) // 25.007457757686876868(e.g, unit is m)
}
}
試しに相対高度をprintしてみました↓
実機を持って、それを上げたり、下げたり、屈伸みたいにすれば、色々な相対高度を出力できます(笑)
(キャプチャ撮った時は、iPhoneとlightningケーブル繋いだMacの両方持ってましたが、ワイヤレスデバッグすればよかった笑)
ちなみにワイヤレスデバッグについては、以前↓の記事で紹介しています!
実際にSwiftHomeApp
のデバイス情報を表示する画面↓でも、位置情報と高度情報は正しく表示できることが確認できました!(完全に個人情報なので、一部マスクしてます!)
高度情報をHeroku経由で、ラズパイに送信する
それでは最後に、第1弾、第2弾の記事で実装したAPIとHerokuのサーバーに送信してみましょう!
まずは取得したデータをSwiftHomeCoreに定義した共通モデルDeviceInfoModel
に設定します。
そしてそのモデルをDictionary型に変換します。
ちなみにこのasDictionary
メソッドは、自分がアプリ開発で共通で利用できるように作ったextensionをまとめたSwiftExtensions
というPackageに定義したものです。(詳しくは以下の記事に記載しています)
// ①Use common Model defined in SwiftHomeCore
let model = DeviceInfoModel(deviceId: UIDevice.current.identifierForVendor!.uuidString,
deviceLatitude: userCoordinate.latitude,
deviceLongitude: userCoordinate.longitude,
absoluteAltimeter: absoluteAltimeterValue)
let parameters = try! model.asDictionary(using: encoder) // ②extension to convert to Dictionary
あとはPostするだけです。
↓のコードでは、Alamofireを使ってPostしています。
headerには、WebSocketの時と同じようにBasic認証の設定をしています。
ちなみにemptyResponseCodes: [200]
の設定は、今回実装したAPIは200のレスポンスの時に、.ok
のレスポンスを返すだけで、jsonなどは返ってこないので、空です。
しかしAlamofireでは、空のレスポンスが返ってくると、decode失敗としてエラーになってしまったので、「200の場合は、空になる!」と明示的に書いてあげています。
AF.request(urlString,
method: .post, parameters: parameters, // ①Set DeviceInfoModel parameters
headers: headers) // ②Same setting as webSocket header
.responseDecodable(of: Empty.self, emptyResponseCodes: [200]) { response in
switch response.result {
case .success: print("Success")
// (Omit: error handling)
}
}
Postした後に、実際にHerokuのPostgresにデータが保存されているかを確認しましょう!
HerokuにはDataclips
というPostgresに保存されているデータを確認する機能があり、postgresql
を実行して、データを確認できます(参考)。
Herokuのダッシュボード > ユーザーアイコンの左側のメニュー > Dataclips > 作成したアプリを選択、と進むことで、クエリ入力画面↓へ進みます
今回は、以下のようなクエリを実行し、保存してあるデバイスの情報を全て表示しました
ちなみにTable名を””で囲っているのは、postgresqlではテーブル名は、自動で小文字に変換されるようで、そのまま実行すると、大文字始まりで登録したTable名を検索することができません。
そこで”DeviceInfo”とすることで、大文字始まりのTable名として、クエリを実行することができます。
SELECT * FROM "DeviceInfo";
その結果↓のようにちゃんとデータが保存されていることが確認できました!🎉
まとめ
これで2部の「Vaporを動かす- iPhoneとラズパイ間をVaporで通信」のまとめは終わりです。
かなりの長編になりました!!
Vaporを使うことで、簡単にSwiftでサーバーを構築でき、一部コードをクライアント側でも共通化できるのはかなり開発体験としてはいいなと思いました!
次は、「3. ラズパイでセンサーの値を読み取る – 重量センサーの値を取得する」のまとめ記事になります