# Provides a nice DSL for interacting with the standard CMMotionManager from # CoreMotion module BubbleWrap # These module methods provide the main interface. It uses a shared manager # (per Apple's recommendation), and they all have a common set of supported # methods: # available? # active? # repeat(opts) # once(opts) # every(time_interval, opts) # # @example # if BW::Motion.accelerometer.available? # BW::Motion.accelerometer.every(5) do |result| # # see the README for the keys that are available in result. # end # end # # If you insist on using your own manager, or you want more than one # BW::Motion::Whatever running at the same time, you'll need to instantiate # them yourself. # # @example # mgr = CMMotionManager.alloc.init # accel = BW::Motion::Accelerometer.new(mgr) # accel.once do |result_data| # end # # => BW::Motion::accelerometer.once do |result_data| ... end module Motion module_function def manager @manager ||= CMMotionManager.alloc.init end def accelerometer @accelerometer ||= Accelerometer.new(self.manager) end def gyroscope @gyroscope ||= Gyroscope.new(self.manager) end def magnetometer @magnetometer ||= Magnetometer.new(self.manager) end def device @device ||= DeviceMotion.new(self.manager) end end module Motion module Error end class GenericMotionInterface def initialize(manager) @manager = manager end def repeat(options={}, &blk) raise "A block is required" unless blk blk.weak! if BubbleWrap.use_weak_callbacks? self.start(options, &blk) return self end def every(time=nil, options={}, &blk) raise "A block is required" unless blk blk.weak! if BubbleWrap.use_weak_callbacks? if time.is_a?(NSDictionary) options = time elsif time options = options.merge(interval: time) end self.start(options, &blk) return self end def once(options={}, &blk) raise "A block is required" unless blk blk.weak! if BubbleWrap.use_weak_callbacks? @called_once = false every(options) do |result, error| unless @called_once @called_once = true blk.call(result, error) end self.stop end return self end private def convert_queue(queue_name) case queue_name when :main, nil return NSOperationQueue.mainQueue when :background queue = NSOperationQueue.new queue.name = 'com.bubble-wrap.core-motion.background-queue' return queue when :current return NSOperationQueue.currentQueue when String queue = NSOperationQueue.new queue.name = queue_name return queue else queue_name end end private def internal_handler(handler) retval = -> (result_data, error) do handle_result(result_data, error, handler) end retval.weak! if BubbleWrap.use_weak_callbacks? retval end end class Accelerometer < GenericMotionInterface def start(options={}, &handler) if options.key?(:interval) @manager.accelerometerUpdateInterval = options[:interval] end if handler queue = convert_queue(options[:queue]) @manager.startAccelerometerUpdatesToQueue(queue, withHandler: internal_handler(handler)) else @manager.startAccelerometerUpdates end return self end private def handle_result(result_data, error, handler) if result_data result = { data: result_data, acceleration: result_data.acceleration, x: result_data.acceleration.x, y: result_data.acceleration.y, z: result_data.acceleration.z, } else result = nil end handler.call(result, error) end def available? @manager.accelerometerAvailable? end def active? @manager.accelerometerActive? end def data @manager.accelerometerData end def stop @manager.stopAccelerometerUpdates end end class Gyroscope < GenericMotionInterface def start(options={}, &handler) if options.key?(:interval) @manager.gyroUpdateInterval = options[:interval] end if handler queue = convert_queue(options[:queue]) @manager.startGyroUpdatesToQueue(queue, withHandler: internal_handler(handler)) else @manager.startGyroUpdates end return self end private def handle_result(result_data, error, handler) if result_data result = { data: result_data, rotation: result_data.rotationRate, x: result_data.rotationRate.x, y: result_data.rotationRate.y, z: result_data.rotationRate.z, } else result = nil end handler.call(result, error) end def available? @manager.gyroAvailable? end def active? @manager.gyroActive? end def data @manager.gyroData end def stop @manager.stopGyroUpdates end end class Magnetometer < GenericMotionInterface def start(options={}, &handler) if options.key?(:interval) @manager.magnetometerUpdateInterval = options[:interval] end if handler queue = convert_queue(options[:queue]) @manager.startMagnetometerUpdatesToQueue(queue, withHandler: internal_handler(handler)) else @manager.startMagnetometerUpdates end return self end private def handle_result(result_data, error, handler) if result_data result = { data: result_data, field: result_data.magneticField, x: result_data.magneticField.x, y: result_data.magneticField.y, z: result_data.magneticField.z, } else result = nil end handler.call(result, error) end def available? @manager.magnetometerAvailable? end def active? @manager.magnetometerActive? end def data @manager.magnetometerData end def stop @manager.stopMagnetometerUpdates end end class DeviceMotion < GenericMotionInterface def start(options={}, &handler) if options.key?(:interval) @manager.deviceMotionUpdateInterval = options[:interval] end if options.key?(:reference) reference_frame = convert_reference_frame(options[:reference]) else reference_frame = nil end if handler queue = convert_queue(options[:queue]) if reference_frame @manager.startDeviceMotionUpdatesUsingReferenceFrame(reference_frame, toQueue: queue, withHandler: internal_handler(handler)) else @manager.startDeviceMotionUpdatesToQueue(queue, withHandler: internal_handler(handler)) end else if reference_frame @manager.startDeviceMotionUpdatesUsingReferenceFrame(reference_frame) else @manager.startDeviceMotionUpdates end end return self end private def handle_result(result_data, error, handler) if result_data result = { data: result_data, attitude: result_data.attitude, rotation: result_data.rotationRate, gravity: result_data.gravity, acceleration: result_data.userAcceleration, magnetic: result_data.magneticField, } if result_data.attitude result.merge!({ roll: result_data.attitude.roll, pitch: result_data.attitude.pitch, yaw: result_data.attitude.yaw, matrix: result_data.attitude.rotationMatrix, quaternion: result_data.attitude.quaternion, }) end if result_data.rotationRate result.merge!({ rotation_x: result_data.rotationRate.x, rotation_y: result_data.rotationRate.y, rotation_z: result_data.rotationRate.z, }) end if result_data.gravity result.merge!({ gravity_x: result_data.gravity.x, gravity_y: result_data.gravity.y, gravity_z: result_data.gravity.z, }) end if result_data.userAcceleration result.merge!({ acceleration_x: result_data.userAcceleration.x, acceleration_y: result_data.userAcceleration.y, acceleration_z: result_data.userAcceleration.z, }) end if result_data.magneticField case result_data.magneticField.accuracy when CMMagneticFieldCalibrationAccuracyLow accuracy = :low when CMMagneticFieldCalibrationAccuracyMedium accuracy = :medium when CMMagneticFieldCalibrationAccuracyHigh accuracy = :high end result.merge!({ field: result_data.magneticField.field, magnetic_x: result_data.magneticField.field.x, magnetic_y: result_data.magneticField.field.y, magnetic_z: result_data.magneticField.field.z, magnetic_accuracy: accuracy, }) end else result = nil end handler.call(result, error) end def convert_reference_frame(reference_frame) case reference_frame when :arbitrary_z CMAttitudeReferenceFrameXArbitraryZVertical when :corrected_z CMAttitudeReferenceFrameXArbitraryCorrectedZVertical when :magnetic_north CMAttitudeReferenceFrameXMagneticNorthZVertical when :true_north CMAttitudeReferenceFrameXTrueNorthZVertical else reference_frame end end def available? @manager.deviceMotionAvailable? end def active? @manager.deviceMotionActive? end def data @manager.deviceMotion end def stop @manager.stopDeviceMotionUpdates end end end end