• Stars
    star
    111
  • Rank 314,510 (Top 7 %)
  • Language
    Objective-C
  • Created over 11 years ago
  • Updated about 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

iOS (iPad + iPhone) Magnet-O-Meter demo/test app to measure magnetometer data

Magnet-O-Meter

Magnetometer test app

Demonstrates the various ways of getting Magnetometer and compass data.

Compatible with iPhone, iPad, iOS 5.1+
updated for iOS7, iOS8 with changes for UIProgressBar custom image bug

updated to ios10, XCode8: important news: Apple have removed the difference between Core Motion and Core Location heading readings as tested on iPhone SE with ios 10.1. Now their output is identical. The differences are still evident on ios9 and iphone 5s. These are the only two devices and iOS versions I have been able to test on this update.

BEST on iPad as the interface is pretty crowded.

This is posted to support a lengthy Stack Overflow answer

It is hastily pulled from a larger app, hence the odd bit of code cruft around the edges...

enter image description here

interface
RED arrow
showing CLHeading.magneticHeading - the iOS 'compass' measure
GREEN arrow and progress bars
CLHeading.[x|y|z]
BLUE arrow and progress bars
CMDevice.calibratedMagneticField.field
YELLOW arrow and progress bars
CMMagnetometer.[x|y|z] raw magentometer data
MAGENTA progress bars
show difference between green and blue measures.

This is only accurate on a device positioned horizontally on a flat surface as it only uses the x and y magnetometer readings. The point of this app is to make sense of the Apple API's and to get a feel of how the magnetometer works. We could enhance it by correcting for device tilt - some kind of matrix multiplication with Core Motion's attitude rotation matrix - but we are not trying to demonstrate how to get the best reading (Apple already provides that with their CLHeading magneticHeading parameter), but rather to show how the raw figures compare.

StackOverflow notes

To unravel this I've spent a bit of time digging through the Apple docs.

There are three ways of obtaining magnetometer data

1/ Core Motion framework
CMMotionManagers's CMMagnetometer class

2/ Core Motion framework
CMDeviceMotion CMCalibratedMagneticField property

3 / Core Location framework
CLLocationManager's CLHeading

1/ supplies 'raw' data from the magnetometer.
2/ and 3/ return 'derived' data. The numbers in both cases are similar (though not exactly the same).

Difference between Core Motion's CMMagnetometer and CMCalibratedMagneticField

1/ and 2/ - both from the Core Motion framework - differ as follows:

CMDeviceMotion Class Reference

@property(readonly, nonatomic) CMCalibratedMagneticField magneticField

Discussion
The CMCalibratedMagneticField returned by this property gives you the total magnetic field in the device’s vicinity without device bias. Unlike the magneticField property of the CMMagnetometer class, these values reflect the earth’s magnetic field plus surrounding fields, minus device bias.

CMMagnetometerData class reference

  @property(readonly, nonatomic) CMMagneticField magneticField

Discussion The value of this property is the total magnetic field observed by the device which is equal to the Earth’s geomagnetic field plus bias introduced from the device itself and its surroundings.

This is the “raw” magnetic-field value, unlike the calibrated value of the magneticField property of CMDeviceMotion which filters out the bias introduced by the device and, in some cases [my italics], its surrounding fields.

CMMagnetometer gives us raw data, CMCalibratedMagneticField is adjusted data.

Difference between Core Motion's CMCalibratedMagneticField and Core Location's CLHeading

The docs are not immediately clear on the difference between 2/ and 3/, but they do generate different numbers so let's do some digging….

Core Location framework
CLHeading

From Location Awareness Programming Guide

Getting Heading-Related Events

Heading events are available to apps running on a device that contains a magnetometer. A magnetometer measures nearby magnetic fields emanating from the Earth and uses them to determine the precise orientation of the device. Although a magnetometer can be affected by local magnetic fields, such as those emanating from fixed magnets found in audio speakers, motors, and many other types of electronic devices, Core Location is smart enough to filter out fields that move with the device.

Here are the relevant CLHeading 'raw' properties

@property(readonly, nonatomic) CLHeadingComponentValue x
@property(readonly, nonatomic) CLHeadingComponentValue y
@property(readonly, nonatomic) CLHeadingComponentValue z

The geomagnetic data (measured in microteslas) for the [x|y|z]-axis. (read-only)
This value represents the [x|y|z]-axis deviation from the magnetic field lines being tracked by the device. (older versions of the docs add:) The value reported by this property is normalized to the range -128 to +128.

I am not clear how a microtesla measurement can be 'normalized' (compressed? clipped?) to a range of +/-128 and still represent the unit it claims to measure. Perhaps that's why the sentence was removed from the docs. The units on an iPad mini do seem to conform to this kind of range, but the iPhone4S gives CMMagnetometer readings in higher ranges, eg 200-500.

The API clearly expects you to use the derived properties:

@property(readonly, nonatomic) CLLocationDirection magneticHeading
@property(readonly, nonatomic) CLLocationDirection trueHeading

which give stable N/S E/W compass readings in degrees (0 = North, 180 = South etc). For the true heading, other Core Location services are required (geolocation) to obtain the deviation of magnetic from true north.

Here is a snippet from the CLHeading header file

/*
 *  CLHeading
 *  
 *  Discussion:
 *    Represents a vector pointing to magnetic North constructed from 
 *    axis component values x, y, and z. An accuracy of the heading 
 *    calculation is also provided along with timestamp information.
 *  
 *  x|y|z
 *  Discussion:
 *    Returns a raw value for the geomagnetism measured in the [x|y|z]-axis.

Core Motion framework
CMDeviceMotion CMCalibratedMagneticField

/*
 *  magneticField
 *  
 *  Discussion:
 *			Returns the magnetic field vector with respect to the device for devices with a magnetometer.
 *			Note that this is the total magnetic field in the device's vicinity without device
 *			bias (Earth's magnetic field plus surrounding fields, without device bias),
 *			unlike CMMagnetometerData magneticField.
 */
@property(readonly, nonatomic) CMCalibratedMagneticField magneticField NS_AVAILABLE(NA,5_0);

CMMagnetometer

 *  magneticField
 *  
 *  Discussion:
 *    Returns the magnetic field measured by the magnetometer. Note
 *        that this is the total magnetic field observed by the device which
 *        is equal to the Earth's geomagnetic field plus bias introduced
 *        from the device itself and its surroundings.
 */
@property(readonly, nonatomic) CMMagneticField magneticField;  

CMMagneticField
This is the struct that holds the vector.
It's the same for CMDeviceMotion's calibrated magnetic field and CMMagnetometer's uncalibrated version:

/*  CMMagneticField - used in 
 *  CMDeviceMotion.magneticField.field
 *  CMMagnetometerData.magneticField
 *  
 *  Discussion:
 *    A structure containing 3-axis magnetometer data.
 *
 *  Fields:
 *    x:
 *      X-axis magnetic field in microteslas.
 *    y:
 *      Y-axis magnetic field in microteslas.
 *    z:
 *      Z-axis magnetic field in microteslas.

The difference between 2/ and 3/ are hinted at here:

Core Location CLHeading

Represents a vector pointing to magnetic North constructed from axis component values x, y, and z

Core Location is smart enough to filter out fields that move with the device

Core Motion CMCalibratedMagneticField

[represents] Earth's magnetic field plus surrounding fields, without device bias

So - according to the docs - we have:

1/ CMMagnetometer
Raw readings from the magnetometer

2/ CMDeviceMotion (CMCalibratedMagneticField*) magneticField
Magnetometer readings corrected for device bias (onboard magnetic fields)

3/ CLHeading [x|y|z]
Magnetometer readings corrected for device bias and filtered to eliminate local external magnetic fields (as detected by device movement - if the field moves with the device, ignore it; otherwise measure it)

Testing the theory

I have put a Magnet-O-Meter demo app on gitHub which displays some of these differences. It's quite revealing to wave a magnet around your device when the app is running and watching how the various APIs react:

CMMagnetometer doesn't react much to anything unless you pull a rare earth magnet up close. The onboard magnetic fields seem far more significant than local external fields or the earth's magnetic field. On my iPhone 4S it consistently points to the bottom left of the device; on the iPad mini it points usually to the top right.

CLHeading.[x|y|z] is the most vulnerable to local external fields, whether moving or static relative to the device.

(CMDevice)CMCalibratedMagneticField is the most steady in the face of varying external fields, but otherwise tracks it's Core Location counterpart CLHeading.[x|y|z] pretty closely.

CLHeading.magneticHeading - Apple's recommendation for magnetic compass reading - is far more stable than any of these. It is using data from the other sensors to stabilise the magnetometer data. But you don't get a raw breakdown of x,y,z

             influenced by
             onboard fields    local external fields   earth's field
yellow               X                   X                 X
green                _                   X                 X
blue                 _                   _                 X
red                  _                   _                 X           

yellow CMMagnetometer
green CLHeading.[x|y|z]
blue CMCalibratedMagneticField
red CLHeading.magneticHeading

This does seem to contradict the docs, which suggest that CLHeading.[x|y|z] should be less influenced by local external fields than CMCalibratedMagneticField.

What approach should you take? Based on my limited testing, I would suggest…
If you want a compass reading
CLHeading's magneticHeading and trueHeading will give you the most accurate and most stable compass reading.
If you need to avoid Core Location
CMDeviceMotion's CMCalibratedMagneticField seems to be the next most desirable, although considerably less stable and accurate than magneticHeading.
If you are interested in local magnetic fields
CLHeading's 'raw' x y and z properties seem to be more sensitive to local magnetic fields.
If you want all of the data including onboard magnetic fields
Raw magnetometer data from CMMagnetometer. There is really not much point using this unless you are prepared to do tons of filtering, as it is hugely influenced by magnetic fields generated on the device itself.