Posted at

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

More than 1 year has passed since last update.


前置き

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する