0

I'm trying to get the device's declination from the magnetic North in degrees, by relying solely on the device's magnetometer. This is the code I've written but I just get 0 degrees.. What am I doing wrong ?

CMMotionManager *motionManager;

motionManager = [[CMMotionManager alloc] init];

[motionManager startDeviceMotionUpdates];

CMDeviceMotion *deviceMotion;

deviceMotion = [[CMDeviceMotion alloc] init];

while(!self.stopButtonPressed)
{
    double x = motionManager.deviceMotion.magneticField.field.x;

    double y = motionManager.deviceMotion.magneticField.field.y;

    double degrees = asin(y/sqrt(pow(x, 2.0) + pow(y, 2.0))) * 180.0 / M_PI ;

    int degreesRounded = (int)degrees;

    NSLog(@"Degrees : %i", degreesRounded);
}
  • Are you getting real values for `x` and `y`? What device are you using - not all iOS devices have a magnetometer in them. – Jim Jul 29 '12 at 18:06
  • `asin(y/sqrt(pow(x, 2.0) + pow(y, 2.0)))` is equivalent to `atan(y/fabs(x))`... – kennytm Jul 29 '12 at 18:06

3 Answers3

0

It is likely that the simulator doesn't return normal values for these methods, so you will need to test on a real device.

The CLLocationManager's method, didUpdateHeading: doesn't work on the simulator, so you are probably experiencing something similar here.

Edit:

From the docs:

"The latest sample of device-motion data. (read-only)

@property(readonly) CMDeviceMotion *deviceMotion

Discussion If no device-motion data is available, the value of this property is nil. An application that is receiving device-motion data after calling startDeviceMotionUpdates periodically checks the value of this property and processes the device-motion data."

Check to see if that property of your motion manager is nil. If it is, then you would get 0 for the magnetic field property.

Edit 2:

Instead of using startDeviceMotionUpdates, you should be using startMagnetometerUpdatesToQueue:. The docs say this:

"Magnetometer. Set the magnetometerUpdateInterval property to specify an update interval. Call the startMagnetometerUpdatesToQueue:withHandler: method, passing a block of type CMMagnetometerHandler. Magnetic-field data is passed into the block as CMMagnetometerData objects."

Docs are here.

eric.mitchell
  • 8,817
  • 12
  • 54
  • 92
  • I'm testing it on a real device –  Jul 29 '12 at 18:06
  • Why would device-motion data be unavailable since I'm using an iPhone 4S that has a working magnetometer? It doesn't make sense.. –  Jul 29 '12 at 18:12
  • I don't want to use a Queue. I want to have direct real-time access to the values magneticField.field.x and magneticField.field.y the moment I ask for them. And get the new values the same way each time round the loop. Documentation : "Your application can obtain samples of magnetometer measurements, as represented by instances of this class, from the block handler of the startMagnetometerUpdatesToQueue:withHandler: method or from the magnetometerData property of the CMMotionManager class" –  Jul 29 '12 at 18:19
  • 1
    Is `motionManager.deviceMotion` nil? – eric.mitchell Jul 29 '12 at 18:41
  • looks like it's nil . I wrote while (motionManager.deviceMotion != nil) {...the loop...} and now the app will stop after I hit play –  Jul 29 '12 at 18:51
  • so it looks like the problem might be that the motionManager.deviceMotion is nil for some reason... –  Jul 29 '12 at 19:00
  • Hm. Best bet is the docs, since I have no experience with CMMotionManager. Maybe `startDeviceMotionUpdates` isn't the right method to prep the motion manager? – eric.mitchell Jul 29 '12 at 19:58
  • maybe, but I have to work through the DeviceMotion methods if I want to get unbiased calibrated magnetic field values, it's all very confusing, there must be 3-4 different ways to have access to magnetic field values which only makes things more complicated... I'll come back with an answer if I ever find one... (unless smn else beats me to it that is... :) ) –  Jul 29 '12 at 20:18
  • What are you trying to do with the magnetic field data? – eric.mitchell Jul 29 '12 at 20:58
  • I want to convert them into degrees and know the device's declination from the magneticNorth at anytime. I only want to rely on the Magnetometer (not on Location Services) and I want to get the unbiased data from the magnetometer and not the completely raw data returned by the Magnetometer class.. Basically I'm trying to create a compass, but turns out it's harder than it sounds :) –  Jul 29 '12 at 21:08
0

What thread is the above code running on? If you're running it on the main thread, you probably won't get see updates to the device motion data over time. Use an NSTimer or similar mechanism to sample the motion over time, so the main thread is free to do other things (like service requests from the core motion sub-system).

Jesse Rusak
  • 56,530
  • 12
  • 101
  • 102
  • this is for testing purposes only. I've created a class called Game that only has a -(void) play method and all that's included in that method is the code I've presented above. When the app runs the play method of a Game instance is called and that's it.. –  Jul 29 '12 at 18:14
  • I've tried to clarify my answer; if you are running an infinite loop on your main thread like that, you might not actually get core motion data, since core motion probably uses the main thread's runloop. – Jesse Rusak Jul 29 '12 at 18:17
  • No I've actually tried the exact above form but instead of asking for magnetic field values in my while loop I was asking for yaw values from the gyroscope and it worked fine, using CoreMotion classes.. –  Jul 29 '12 at 18:22
  • It's still possible that you need the main thread for this kind of data. In either case, is it possible that your magnetic field values need to be calibrated? Try setting `showsDeviceMovementDisplay` to YES or listening for the `CMErrorDeviceRequiresMovement` error. – Jesse Rusak Jul 29 '12 at 18:26
  • I set it to YES but I still get nothing. Being fairly new to programming, I don't know much about main and non-main threads. How do I put all of this code in a separate thread that would run in parallel with the main one? But I still believe there's something else I'm doing wrong.. –  Jul 29 '12 at 18:34
  • You can put this on another thread using `performSelectorInBackground`, for example, as suggested in the first answer in your [other question](http://stackoverflow.com/questions/11362918/where-does-the-main-loop-go-when-creating-an-ios-app) – Jesse Rusak Jul 29 '12 at 18:39
  • given that my -(void) play method is part of a Game class, in my IBAction playPressed I'simply calling '[game play]' where game is an instance of my Game class. So should I now replace that with smith like : "[self performSelectorInBackground:@selector(game.play) withObject:nil];" in the IBAction playPressed? –  Jul 29 '12 at 18:47
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/14593/discussion-between-jesse-rusak-and-sprout-coder) – Jesse Rusak Jul 29 '12 at 18:47
0

Here is what I tested on a real device:

   CMMotionManager *myMotionManager= [[CMMotionManager alloc] init];
    myMotionManager.deviceMotionUpdateInterval = 1;
    [myMotionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical  toQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
        double x = myMotionManager.deviceMotion.magneticField.field.x;
        double y = myMotionManager.deviceMotion.magneticField.field.y;
        double z = myMotionManager.deviceMotion.magneticField.field.z;
        NSLog(@"Field.x= %f; Field.y = %f; Field.z= %f",x,y,z);
    }];