iOS
GoogleAppsScript
spreadsheet
Swift
WatchKit

AppleWatchからデバイスモーションを取得してGoogleSpreadsheetに書き込む。

前置き

Watchアプリのデバイスモーションから機械学習用のデータセットを作る。
これの続編です。

GAS経由でAppleWatch → iOS → GAS → スプレッドシートに書き込み、って感じのところです。
image.png

では以下から。


環境

Swift 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2)
Xcode 9.2
WatchOS 4.2.2
iOS 11.2.5

実装

import WatchKit
import Foundation
import WatchConnectivity
import CoreMotion

class InterfaceController: WKInterfaceController, WCSessionDelegate {

    @IBOutlet var StartBtn: WKInterfaceButton!
    @IBOutlet var StopBtn: WKInterfaceButton!

    @IBAction func pushStartBtn() {
        StartBtn.setHidden(true)
        StopBtn.setHidden(false)
        print("start")
        // randNum = String(arc4random_uniform(10000000))
        sendMessage()
    }

    @IBAction func pushStopBtn() {
        motionManager.stopDeviceMotionUpdates()
        print("stop")
        StartBtn.setHidden(false)
        StopBtn.setHidden(true)
        // randNum = ""
        sendMessage()
    }

    let motionManager = CMMotionManager()
    let queue = OperationQueue()

    var randNum =  String(UInt32())
    var applicationDict = [String: String]()
    var attitude = ""
    var gravity = ""
    var rotationRate = ""
    var userAcceleration = ""


    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        activateSession()
        StopBtn.setHidden(true)
    }

    func activateSession(){
        if WCSession.isSupported() {
            let session: WCSession = WCSession.default
            session.delegate = self
            session.activate()
        }
    }

    func sendMessage(){
        if !motionManager.isDeviceMotionAvailable {
            print("Device Motion is not available.")
            return
        }

        if self.randNum == "" {
            motionManager.stopDeviceMotionUpdates()
            return
        }else{
            motionManager.startDeviceMotionUpdates(to: queue) { (deviceMotion: CMDeviceMotion?, error: Error?) in

                if error != nil {
                    print("Encountered error: \(error!)")
                }

                if deviceMotion != nil {
                    self.attitude = "\(deviceMotion!.attitude)"
                    self.gravity = "\(deviceMotion!.gravity)"
                    self.rotationRate = "\(deviceMotion!.rotationRate)"
                    self.userAcceleration = "\(deviceMotion!.userAcceleration)"
                }

                if WCSession.default.isReachable {

                    let date = Date()

                    self.applicationDict = [
                        "date": String(describing: date),
                        "attitude": self.attitude,
                        "gravity": self.gravity,
                        "rotationRate": self.rotationRate,
                        "userAcceleration": self.userAcceleration
                    ]
                    sleep(UInt32(0.5))
                    WCSession.default.sendMessage(self.applicationDict, replyHandler: {(reply) -> Void in
                        print(reply)
                    }){(error) -> Void in
                        print(error)
                    }
                }
            }
        }
    }

    @available(watchOS 2.2, *)
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print("activationDidComplete")
    }

    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
    }
}

ボタンを押すとsendMessage()が走ってデバイスモーションの値を取得するようになっています。

test.gif
取得した値をWCSession.default.sendMessage()で0.5秒おきにDictでiOSに渡すようにしています。

iOSの方の実装に行く前にスプレッドシートの方。
スプレッドシートは以下のリンクを参考に実装をしました。
JSONをPOSTしてGoogle SpreadSheetに書き込む
スプレッドシートをDBに使う系の記事はたくさんあるのですが、何個か見たところ上記の記事が最も信頼できそうという感じでした🤔

次にiOS.

iimport UIKit
import WatchConnectivity

class ViewController: UIViewController, WCSessionDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        activateSession()
    }

    func activateSession(){ 
        if WCSession.isSupported() {
            let session: WCSession = WCSession.default
            session.delegate = self
            session.activate()
        }
    }

    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {

        let urlString = "https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxx/exec"
        let request = NSMutableURLRequest(url: URL(string: urlString)!)
        let params:[String:Any] = message 

        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        do{
            request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
            let task:URLSessionDataTask = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {(data,response,error) -> Void in
                let resultData = String(data: data!, encoding: .utf8)!
                print("result:\(resultData)")
                print("response:\(String(describing: response))")
            })
            task.resume()
        }catch{
            print("Error:\(error)")
            return
        }
        replyHandler(["reply" : "Done"])
    }

    @available(iOS 9.3, *)
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print("activationDidComplete")
    }

    func sessionDidBecomeInactive(_ session: WCSession) {
        print("sessionDidBecomeInactive")
    }

    func sessionDidDeactivate(_ session: WCSession) {
        print("sessionDidDeactivate")
    }
}

通常のAPIをPostするときと同じようにParamsに渡したい値をDictで渡しています。
先程取得したSpreadsheetのリンク/execにPOSTする形です。

ss.mov.gif

まとめ

最新のデバイスを使うということis大事(buildが死ぬほど遅かったり何回もiPhoneとAppleWatchをつなぎ直したり消耗した。)

参考文献

watchOSプログラミングガイド iOSの技術の活用
watchOSプログラミングガイド Watchアプリケーションのアーキテクチャ
watchOSプログラミングガイド データの共有

WCSession
Core Motion

watchOS Watch Connectivity
RealmデータをwatchOSとiOSとの間でやりとりする(3)
[watchOS 3] Watch Connectivity のデータ受け取りをバックグラウンドで行う
AppleWatchとiPhoneの通信 watchkit swift 開発

Apple Watch に Core Motion を使って色々なデータを取得して表示させてみる
[watchOS 3] Apple Watch でデバイスモーションを取得する

JSONをPOSTしてGoogle SpreadSheetに書き込む
スプレッドシートとgoogle apps scriptで簡易APIを作ってalamofireからget/postする