rubymotion-community / BubbleWrap

Cocoa wrappers and helpers for RubyMotion (Ruby for iOS and OS X) - Making Cocoa APIs more Ruby like, one API at a time. Fork away and send your pull requests
Other
1.18k stars 208 forks source link

An interface proposal for a CoreMotion wrapper #357

Closed colinta closed 10 years ago

colinta commented 10 years ago

Hi, this is a first-draft of an interface for CoreMotion. @markrickert, @clayallsopp - what do you guys think?

Motion

Interface for the accelerometer, gyroscope, and magnetometer sensors (the CoreMotion framework). You can access each sensor individually, or you can get data from all of them at once using the BW::Motion.device interface, which delegates to CMMotionManager#deviceMotion.

Each sensor has an every and once method. every expects a time interval, and you will need to retain the object it returns and call #stop on it when you are done with the data.

The every and once methods can accept a :queue option. The default value is a queue that runs on the main loop, so that UI updates can be processed in the block. This is useful, but not recommended by Apple, since the events can come in at a high rate. If you want to use a background queue, you can either specify an NSOperationQueue object, or you can use one of these symbols:

If you pass a string instead, a new queue will be created and its name property will be set to that string.

The CMDeviceMotion interface (BW::Motion.device) accepts a :reference option, which specifies the CMAttitudeReferenceFrame. The default value is the same as the one that CMMotionManager uses, which is returned by the CMMotionManager#attitudeReferenceFrame method. This option should be passed to the every or once method.

Accelerometer
BW::Motion.accelerometer.available?
BW::Motion.accelerometer.data  # returns CMAccelerometerData object or nil

# ask the CMMotionManager to update every 5 seconds
listener = BW::Motion.accelerometer.every(5) do |result|
  # result contains the following data (from CMAccelerometerData#acceleration):
  p result[:x]
  p result[:y]
  p result[:z]
  p result[:data]  # the CMAccelerometerData object
end
# you can specify a :queue where the operations will be executed.  See above for details
listener = BW::Motion.accelerometer.every(5, queue: :background) { |result| ... }
listener = BW::Motion.accelerometer.every(5, queue: :main) { |result| ... }
listener = BW::Motion.accelerometer.every(5, queue: :current) { |result| ... }
listener = BW::Motion.accelerometer.every(5, queue: 'my queue') { |result| ... }

# later, you will need to turn off these events:
listener.stop

BW::Motion.accelerometer.once do |result|
  # ...
end
Gyroscope
BW::Motion.gyroscope.available?
BW::Motion.gyroscope.data  # returns CMGyroData object or nil

# ask the CMMotionManager to update every second.
listener = BW::Motion.gyroscope.every(1) do |result|
  # result contains the following data (from CMGyroData#rotationRate):
  p result[:x]
  p result[:y]
  p result[:z]
  p result[:data]  # the CMGyroData object
end
listener.stop

BW::Motion.gyroscope.once do |result|
  # ...
end
Magnetometer
BW::Motion.magnetometer.available?
BW::Motion.magnetometer.data  # returns CMMagnetometerData object or nil

# ask the CMMotionManager to update every second
listener = BW::Motion.magnetometer.every(1) do |result|
  p result[:x]
  p result[:y]
  p result[:z]
  p result[:accuracy]  # this will be a symbol, :low, :medium, :high, or nil if the magnetic data is uncalibrated
end
listener.stop

BW::Motion.magnetometer.once do |result|
  # ...
end
Device Motion

This is an amalgam of all the motion sensor data.

BW::Motion.device.available?
BW::Motion.device.data  # returns CMDeviceMotion object or nil

BW::Motion.device.every(1) do |result|
  # result contains the following data:
  # orientation data, from CMDeviceMotion#attitude
  p result[:roll]
  p result[:pitch]
  p result[:yaw]
  # rotation data, from CMDeviceMotion#rotationRate
  p result[:rotation_x]
  p result[:rotation_y]
  p result[:rotation_z]
  # gravity+acceleration vector, from CMDeviceMotion#gravity
  p result[:gravity_x]
  p result[:gravity_y]
  p result[:gravity_z]
  # just the acceleration vector, from CMDeviceMotion#userAcceleration
  p result[:acceleration_x]
  p result[:acceleration_y]
  p result[:acceleration_z]
  # the magnetic field, from CMDeviceMotion#magneticField
  p result[:magnetic_x]
  p result[:magnetic_y]
  p result[:magnetic_z]
  p result[:magnetic_accuracy]  # this will be a symbol, :low, :medium, :high, or nil if the magnetic data is uncalibrated

  # less useful data, unless you're into the whole linear algebra thing:
  p result[:matrix]  # CMAttitude#rotationMatrix
  p result[:quarternion]  # CMAttitude#quarternion

  p result[:data]  # the CMDeviceMotion object
end

# the reference frame should be one of the CMAttitudeReferenceFrame constants.
ref = CMAttitudeReferenceFrameXArbitraryZVertical
BW::Motion.device.every(1, queue: :background, reference: ref) { |result| ... }

BW::Motion.device.once do |result|
  # ...
end
markrickert commented 10 years ago

I like it! However, do you feel like this should go into BubbleWrap? What about making it its own thing?

colinta commented 10 years ago

It could, but it bears a striking resemblance to BW::Compass and BW::Location... I like that we have a collection of "Core-*" wrappers in a community project, too.

clayallsopp commented 10 years ago

Looks good - I wonder if CMDeviceMotion#attitude, CMDeviceMotion#gravity, CMAttitude#rotationMatrix and CMAttitude#quarternion should have their own convenience methods, since (I believe) the only way to get those with this API is with device.every.

or maybe allow BW::Motion.device to be scoped like BW::Motion.device(:attitude, :gravity, :matrix) etc?

colinta commented 10 years ago

I looked at the device motion stuff again, and it's not able to be scoped; the data will be there regardless, so might as well have it in the result object.

I'm not a big fan of how the result objects looks for the BW::Motion.device interface, though. It would make sense to 'scope' those results, so that they look like the other result objects.

The hash interface was meant to be similar to the other Location & Compass result objects.

colinta commented 10 years ago

See https://github.com/rubymotion/BubbleWrap/pull/366