plus_plugins
plus_plugins copied to clipboard
Magnetometer values are off
System info
Platform the Issue occurs on: iOS Plugin name: sensors_plus Plugin version: 1.3.0
Steps to Reproduce
It seems that Flutter's implementation of the Magnetometer is off when compared to iOS devices. I don't know yet if it's due to some additional flags that are set on the iOS side to calibrate the magnetometer ( https://stackoverflow.com/questions/28365121/how-to-get-magnetometer-data-using-swift ), but results for Flutter are way higher than for iOS devices. And actually sometimes when the Flutter app detects metals the values go down instead of going up.
You can easily test it out by downloading those 3 apps below and seeing the results by yourself.
iOS implementation https://apps.apple.com/us/app/stud-finder-wall-detector/id1530653768 https://apps.apple.com/us/app/teslameter/id976926346
Flutter implementation https://apps.apple.com/us/app/stud-finder/id1613688190
iOS
Flutter
You can see on the screenshots that iOS values are lower than those on Flutter. All of those screenshots were made in the same phone position, with the phone lying flat.
One note here: Look at the X, Y, Z values here, because the Flutter app has the main displayed wrong.
cc: @Zabadam because I've seen that you were implementing this feature
I have an extremely limited knowledge of iOS and as such the testing for that platform was less than thorough.
If I take another look, it may be as simple as calling values from sensor manager incorrectly.
It seems Android has moved things around over the years, and iOS may be similar; whereby I could have been referencing irrelevant docs.
Edit: Thank you for the bug report!
Possibly, I'll also take a look at what could be wrong here
I have finally sat down to look into this.
It seems like it may be a simple fix. We are after this value, the calibrated magneticField
property of a CMDeviceMotion
object that "unlike the magneticField
property of the CMMagnetometerData
class . . . reflect the earth’s magnetic field plus surrounding fields, minus device bias."
EDIT:
After analyzing the usage of the DeviceMotion
sensor for the implementation of UserAcceleration--opposed to Acceleration implementation by raw accelerometer samples from startAccelerometerUpdatesToQueue
or Gyroscope implementation via samples from startGyroUpdatesToQueue
--I determined the solution is to utilize this DeviceMotion
"sensor" for its calibrated magnetic field as well.
This oughta do it.
FLTMagnetometerStreamHandlerPlus
implementation inFLTSensorsPlusPlugin.m
@ L124 becomes@implementation FLTMagnetometerStreamHandlerPlus - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { _initMotionManager(); [_motionManager // startMagnetometerUpdatesToQueue:[[NSOperationQueue alloc] init] // withHandler:^(CMMagnetometerData* magData, NSError* error) { // CMMagneticField magneticField = magData.magneticField; startDeviceMotionUpdatesToQueue:[[NSOperationQueue alloc] init] withHandler:^(CMDeviceMotion* motionData, NSError* error) { CMCalibratedMagneticField field = motionData.magneticField; sendTriplet(field.x, field.y, field.z, eventSink); }]; return nil; } - (FlutterError*)onCancelWithArguments:(id)arguments { // [_motionManager stopMagnetometerUpdates]; [_motionManager stopDeviceMotionUpdates]; return nil; } @end
Perhaps before I submit a PR you could do me a massive favor @filippkowalski and test this locally on your end. You would open your work environment with your app, then make these line changes locally in sensors_plus
pubdev cache.
Awesome! I'll check it out later. @Zabadam
Just FYI, in the meantime, I've used this library: https://pub.dev/packages/motion_sensors and it seems to have a proper implementation.
Just FYI, in the meantime, I've used this library: https://pub.dev/packages/motion_sensors and it seems to have a proper implementation.
I remember when I was building a sensors based package I had to make the decision between that package and sensors_plus
.
You can see now the decision that I ultimately made was to add the magnetometer support I needed to this official package instead of using motion_sensors
as per its lack of web support.
EDIT: I am glad you brought this proper implementation to light, though, as it helped me realize three things in my proposed solution:
-
usingReferenceFrame
call to provide frame of reference -
CMMotionManager.showsDeviceMovementDisplay = true
- The calibrated field object
CMCalibratedMagneticField
does not have its ownx
,y
andz
values, but instead has anaccuracy
property and a standardCMMagneticField
property
- Access to these measurements then is through the seemingly superfluous
CMDeviceMotion.magneticField.field
We'll get this done! Bear in mind I have never programmed in Objective-C nor owned an Apple product 😇
Barring a check for availability of sensors and providing a means to alter temporal accuracy, this implementation should accomplish the same expected functionality as from motion_sensors
:
@implementation FLTMagnetometerStreamHandlerPlus
- (FlutterError *)onListenWithArguments:(id)arguments
eventSink:(FlutterEventSink)eventSink {
_initMotionManager();
// Allow iOS to present calibration interaction if necessary
_motionManager.showsDeviceMovementDisplay = YES; // not `true`, correct?
[_motionManager
startDeviceMotionUpdatesUsingReferenceFrame: CMAttitudeReferenceFrameXArbitraryCorrectedZVertical
ToQueue:[[NSOperationQueue alloc] init]
withHandler:^(CMDeviceMotion *motionData, NSError *error) {
// The `magneticField` from CMDeviceMotion is of type
// CMCalibratedMagneticField which has an `accuracy`
// and a standard CMMagneticField `field`.
CMMagneticField field = motionData.magneticField.field;
sendTriplet(field.x, field.y, field.z, eventSink);
}];
return nil;
}
- (FlutterError *)onCancelWithArguments:(id)arguments {
[_motionManager stopDeviceMotionUpdates];
return nil;
}
@end
Beyond my lack of expertise, motion_sensor
's implementation is with Swift meanwhile this is Obj-C, no? Took some adjusting. The Apple docs have a dropdown toggle, but lack code examples for Obj-C.
I wish I had an iOS device to verify this fix. Could be checked by someone else if they have the time:
dependencies
# Testing calibrated magnetometer on iOS
sensors_plus:
git:
url: https://github.com/Zabadam/plus_plugins.git
path: packages/sensors_plus/sensors_plus
@Zabadam Thanks for your effort!
I just used this dependency and it seems that it's still showing higher values than the other apps or the motion_plus
package.
@Zabadam Thanks for your effort!
I just used this dependency and it seems that it's still showing higher values than the other apps or the
motion_plus
package.
In doing so, between removing sensors_plus: 1.3.0
and adding sensors_plus: 1.4.0
via git you ran a flutter clean
just to be sure the cache was cleared?
Because the shared section of code is nearly identical (albeit Swift instead of ObjC), it seems the magnetometer implementation here may not be the source of issue after all.
private let motionManager = CMMotionManager()
private let queue = OperationQueue()
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
if motionManager.isDeviceMotionAvailable {
motionManager.showsDeviceMovementDisplay = true
motionManager.startDeviceMotionUpdates(using: CMAttitudeReferenceFrame.xArbitraryCorrectedZVertical, to: queue) { (data, error) in
if data != nil {
events([data!.magneticField.field.x, data!.magneticField.field.y, data!.magneticField.field.z])
}
}
}
return nil
}
motion_sensors
versus
- (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
_initMotionManager();
_motionManager.showsDeviceMovementDisplay = YES;
[_motionManager
startDeviceMotionUpdatesUsingReferenceFrame: CMAttitudeReferenceFrameXArbitraryCorrectedZVertical
ToQueue:[[NSOperationQueue alloc] init]
withHandler:^(CMDeviceMotion *motionData, NSError *error) {
CMMagneticField field = motionData.magneticField.field;
sendTriplet(field.x, field.y, field.z, eventSink);
}];
return nil;
}
suggestion for
sensors_plus
The differences I can spot are checking for sensor availability and not sending data if it is nil
.
Aside from a more generous overhaul of both the Android and iOS implementation to enable interval management and sensor availability checking--a separate project--my work seems complete. Someone else may have to pick it up here. I suggest you try it out, Filip, if you find the time. I was still new to Dart and OSS when I first made my contribution to this package, and I have never even programmed for Android or iOS native! I truly wish I could test changes and this could take a day instead of weeks.
Good luck and cheers 🍻
The PR that implements this fix will likely soon be merged. As the code is technically correct, especially when compared to your other linked package that implements iOS magnetometer support, I'm unsure the status on this Issue.
I would love an update on the status of this fix.
Sorry, didn't notice your previous message. It's hard to test those things since we don't really know what's the correct result. The implementation looks good to me, I say: merge it.
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days
Wow, I learned a lot just by reading this excellent comment on StackOverflow.
Snipped below:
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:
my original solution
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.
my proposed solution and the route taken by other Flutter plugins
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.
...but it seems that
CLHeading.[x|y|z]
is the most vulnerable (responsive) to local external fields, whether moving or static relative to the device.
And this is the surprising bit. It would seem, according to these tests, that the Core Location CLHeading
provides geomagnetic data that both filters out the device hard iron and also is more responsive to external elements (such as is desired for a stud finder) than is the Core Motion CMDeviceMotion
's CMCalibratedMagneticField
.
The person who posed the question suggests the official Apple example teslameter employs Core Location's CLHeading
.
Their final note:
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
Still, how these values compare to Android implementation is mystery to me. It is almost funny to me that by now I have learned far more about how iOS and Apple handle sensors and this sort of data than the platform I actually use. That just means I have more to learn!
The PR with the magnet sensor changes has been merged and release. Let me know if this ticket can be closed.
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days
@Zabadam have you checked further to see if it gives the correct values? i checked today and magnetometerEventStream still returns incorrect values that are much higher