I am trying to get haptic feedback when an app is in the background. Essentially when a certain value exceeds a threshold there should be haptic feedback. From what I can see I have everything setup correctly; the function that I am calling when the threshold is surpassed I know is being called because I can see it in the log, but no sound or vibration is happening. So I am not sure what it is I am doing wrong. It works fine when the app is shown on the screen, but not in background.
This is using an older Apple Sample Code (I am aware of the depreciated items). My end goal is to have a baseball pitching analysis app, and the haptics is to reinforce learned muscle memory, kind of like clicker training with dogs.
Here is the MotionManager that pulls in CoreMotion.
import Foundation
import CoreMotion
import WatchKit
import os.log
protocol MotionManagerDelegate: class {
func didUpdateMotion(_ manager: MotionManager, gravityStr: String, rotationRateStr: String, userAccelStr: String, attitudeStr: String, rollInt: Double)
}
extension Date {
var millisecondsSince1970:Int64 {
return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
}
}
class MotionManager {
// MARK: Properties
let motionManager = CMMotionManager()
let queue = OperationQueue()
let wristLocationIsLeft = WKInterfaceDevice.current().wristLocation == .left
// MARK: Application Specific Constants
// The app is using 50hz data and the buffer is going to hold 1s worth of data.
let sampleInterval = 1.0 / 50
let rateAlongGravityBuffer = RunningBuffer(size: 50)
weak var delegate: MotionManagerDelegate?
var gravityStr = ""
var rotationRateStr = ""
var userAccelStr = ""
var attitudeStr = ""
var rollInt = 0.0
var recentDetection = false
// MARK: Initialization
init() {
// Serial queue for sample handling and calculations.
queue.maxConcurrentOperationCount = 1
queue.name = "MotionManagerQueue"
}
// MARK: Motion Manager
func startUpdates() {
if !motionManager.isDeviceMotionAvailable {
print("Device Motion is not available.")
return
}
os_log("Start Updates");
motionManager.deviceMotionUpdateInterval = sampleInterval
motionManager.startDeviceMotionUpdates(to: queue) { (deviceMotion: CMDeviceMotion?, error: Error?) in
if error != nil {
print("Encountered error: \(error!)")
}
if deviceMotion != nil {
self.processDeviceMotion(deviceMotion!)
}
}
}
func stopUpdates() {
if motionManager.isDeviceMotionAvailable {
motionManager.stopDeviceMotionUpdates()
}
}
// MARK: Motion Processing
func processDeviceMotion(_ deviceMotion: CMDeviceMotion) {
gravityStr = String(format: "X: %.1f Y: %.1f Z: %.1f" ,
deviceMotion.gravity.x,
deviceMotion.gravity.y,
deviceMotion.gravity.z)
userAccelStr = String(format: "X: %.1f Y: %.1f Z: %.1f" ,
deviceMotion.userAcceleration.x,
deviceMotion.userAcceleration.y,
deviceMotion.userAcceleration.z)
rotationRateStr = String(format: "X: %.1f Y: %.1f Z: %.1f" ,
deviceMotion.rotationRate.x,
deviceMotion.rotationRate.y,
deviceMotion.rotationRate.z)
attitudeStr = String(format: "r: %.1f p: %.1f y: %.1f" ,
deviceMotion.attitude.roll,
deviceMotion.attitude.pitch,
deviceMotion.attitude.yaw)
rollInt = deviceMotion.attitude.roll
let timestamp = Date().millisecondsSince1970
os_log("Motion: %@, %@, %@, %@, %@, %@, %@, %@, %@, %@, %@, %@, %@",
String(timestamp),
String(deviceMotion.gravity.x),
String(deviceMotion.gravity.y),
String(deviceMotion.gravity.z),
String(deviceMotion.userAcceleration.x),
String(deviceMotion.userAcceleration.y),
String(deviceMotion.userAcceleration.z),
String(deviceMotion.rotationRate.x),
String(deviceMotion.rotationRate.y),
String(deviceMotion.rotationRate.z),
String(deviceMotion.attitude.roll),
String(deviceMotion.attitude.pitch),
String(deviceMotion.attitude.yaw))
updateMetricsDelegate();
}
// MARK: Data and Delegate Management
func updateMetricsDelegate() {
delegate?.didUpdateMotion(self,gravityStr:gravityStr, rotationRateStr: rotationRateStr, userAccelStr: userAccelStr, attitudeStr: attitudeStr, rollInt: rollInt)
}
}
Here is my WorkOutMangager where the workout session is controlled. The function I am calling is at the very end. It is pulling in values from the MotionManagerDelagate.
import Foundation
import HealthKit
import WatchKit
import os.log
protocol WorkoutManagerDelegate: class {
func didUpdateMotion(_ manager: WorkoutManager, gravityStr: String, rotationRateStr: String, userAccelStr: String, attitudeStr: String)
}
class WorkoutManager: MotionManagerDelegate {
// MARK: Properties
let motionManager = MotionManager()
let healthStore = HKHealthStore()
var rollInt = 0.0
weak var delegate: WorkoutManagerDelegate?
var session: HKWorkoutSession?
// MARK: Initialization
init() {
motionManager.delegate = self
}
// MARK: WorkoutManager
func startWorkout() {
// If we have already started the workout, then do nothing.
if (session != nil) {
return
}
// Configure the workout session.
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .tennis
workoutConfiguration.locationType = .outdoor
do {
session = try HKWorkoutSession(configuration: workoutConfiguration)
} catch {
fatalError("Unable to create the workout session!")
}
// Start the workout session andKAKAHA.watchWatch.watchkitapp device motion updates.
healthStore.start(session!)
motionManager.startUpdates()
if(rollInt < -0.9){
WKInterfaceDevice.current().play(.failure)
os_log("ALERT!!!")
}
}
func stopWorkout() {
// If we have already stopped the workout, then do nothing.
if (session == nil) {
return
}
// Stop the device motion updates and workout session.
motionManager.stopUpdates()
healthStore.end(session!)
// Clear the workout session.
session = nil
}
// MARK: MotionManagerDelegate
func didUpdateMotion(_ manager: MotionManager, gravityStr: String, rotationRateStr: String, userAccelStr: String, attitudeStr: String, rollInt: Double) {
delegate?.didUpdateMotion(self, gravityStr: gravityStr, rotationRateStr: rotationRateStr, userAccelStr: userAccelStr, attitudeStr: attitudeStr)
self.rollInt = rollInt
rollAlert()
}
@objc fileprivate func rollAlert(){
if (rollInt < -0.9) {
WKInterfaceDevice.current().play(.failure)
os_log("TOO CLOSE TO FACE!!!")
}
}
}