Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
CLLocation Manager Heading information with objc_util bridge freezes??
-
Hi all,
This is my first post here, but before I go into details, a big thank you to @omz for this amazing tool and all the Pythonista community for all the help I've been silently getting from your posts. Thank you all!
I apologize in advance for this first long post, I promise future ones will be shorter.
I'm relatively confident with Python and I've been programming in different languages (namely VB, Perl and Python) for many years, both for business and mostly for pleasure. I'm quite new to Pythonista and IOS programming, though, and all in all by no means a professional coder, more of a 'quick and dirty' inefficient DIY builder, which I'm sure you'll easily spot in my poor code. I'm also stubborn, and don't like to bother people if I can learn from others independently or find a way to do the job myself, so I've been delaying the moment to come for help; but the moment has now arrived.
I'm a private pilot in my spare time, and I'm writing a prototype app for a custom flight log and associated utilities that I would like to have in my flights. At this point in time, I wanted to add heading (from magnetometer) and then course (from GPS) information in order to compute wind drift conditions, so I started writing a little separate module using the objc_util bridge.
For the time being, this simply creates a CLLocation manager object, starts the heading updates, uses the didUpdateHeading delegate to receive the updates and presents the information in a simple view with a couple of label objects. I'm also printing out to the console for debugging purposes.
button in the console is grayed out; for some reason I have the feeling that the process is still running and listening for updates but these are simply either not received or not reflected in the view label or printed out to the console. It would seem to me that the systems might be overloaded with updates? This seems odd to me, since any other 'compass' app out there is obviously reporting all updates constantly. I need best accuracy and frequency for my purposes, but still I tried to use the filter variable to reduce the frequency and report only updates greater than 3 degrees; the filter works, but the behaviour is still the same, updates stop rather quickly.If I try to use location updates (startUpdatingLocation() for GPS lat/lon position), it is even worse; I get a couple of location updates at most, maybe a couple of heading updates, sometimes none, and then it freezes. So I commented out location updates for the time being.
So all this still leads me to believe there is some kind of problem regarding competing resources, several script processes running or something similar going on, but I have no idea how to even debug the situation.
I have been testing on two different platforms with similar results:
- iPad 2nd gen, GPS and GSM capabilities on IOS 9.3.5
- iPhone 8 on IOS 12.1.2
And Here is my code:
Any help truly appreciated!
Thx!# coding: utf-8 from objc_util import * import ui def printMethods(whichObject): #print (dir(whichObject)) for method in dir(whichObject): print (method) #- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading; def locationManager_didUpdateHeading_(self, _cmd, manager, newHeading): headingObj = ObjCInstance(newHeading) print ("Magnetic:", headingObj.magneticHeading()) print ("True:",headingObj.trueHeading()) print ("ACCURACY:",headingObj.headingAccuracy()) v['m_Heading_value'].text = str(int(headingObj.magneticHeading())) def locationManager_didUpdateLocations_(_self,_cmd,manager,locations): locations=ObjCInstance(locations) print (locations[0].coordinate().a) print (locations[0].coordinate().b) def locationManager_didFailWithError_(_self,_cmd,manager,error): error=ObjCInstance(error) print (error) methods = [locationManager_didUpdateHeading_,locationManager_didUpdateLocations_,locationManager_didFailWithError_] protocols = ['CLLocationManagerDelegate'] MyLocationManagerDelegate = create_objc_class('MyLocationManagerDelegate', methods=methods, protocols=protocols) v = ui.load_view() @on_main_thread def mymain(): CLLocationManager = ObjCClass ('CLLocationManager') myloc = CLLocationManager.alloc().init().autorelease() #print (printMethods(myloc)) delegate = MyLocationManagerDelegate.alloc().init() myloc.setDelegate_(delegate) locationAvailable = CLLocationManager.headingAvailable() print ("HEADING AVAILABLE") print (locationAvailable) print ("CURRENT HEADING ORIENTATION") print (myloc.headingOrientation()) print ("NEW HEADING ORIENTATION") myloc.headingOrientation = 3 print (myloc.headingOrientation()) myloc.headingFilter = 3 print ("NEW THRESHOLD") print (myloc.headingFilter()) print ("START MONITORING ") #myloc.startUpdatingLocation() myloc.startUpdatingHeading() v.present('sheet') if __name__ == '__main__': mymain()
-
Have you tried setting the distanceFilter/headingFilter =-1, to get all data?
Also, for heading, you likely do not receive events if you have to calibrate. So perhaps implement
locationManagerShouldDisplayHeadingCalibration:
I would advise calling stopUpdatingHeading, etc from will_close in a custom class. It looks like it crashed when you ran a second time, but if you never stopped updates, the original location manager continues to live on and try to call your old functions, and update your old view. Since that all happens in the main thread, any problems could result in a stuck ui.
-
@JAM, just curious: what do you want to achieve that you cannot do with the
location
andmotion
modules? Oh, I guess you only get raw magnetometer data, and no compass heading. -
https://gist.github.com/a1b044b677178672587544522329e059
This is a simple update, that I cannot get to crash.
First, I removed the print statements-- these are getting called from the main thread, and I can get crashes with them included.Second, I created a custom ui.View class, to handle will_close. You need to stop updates there, otherwise the manager keeps updating. I also used the sharedManager, so that you only ever have one manager at a time, even when running the program multiple times. That can fix some issues withe multiple delegates being called.
I added some code to detect a closed view in the callbacks, and stop updates. This is good practice, in case you have an error somewhere, say in will_close.
What seems to be most robust, is to add a slight delay to updating of the view text. As it is, the location callbacks happen in the main thread, which is also where view updates happen. Without that delay, I would sometimes get lockup when trying to close the view (I think when I pressed close at the same time the callback was trying to update text, but I cannot prove that).
-
For what it is worth, I have noted that adding retain_global on any delegate objects greatly improves the stability and predictability of objc_util code.
-
in this case, the delegate was stored as an attribute of the View, so it hangs around as long as the view does (which is at least as long as the view is on screen).
-
@JonB, I have done that, and it has not been enough. Still, this might be a completely different case.
-
Hi all, and thank you for your help!
Let me give you an orderly update on where I am, for what it's worth:
@JonB (first post):
I followed your advice as follows;- first, I did create that custom view class you suggested in order to accomodate the 'will_close' actions (I basically copy/pasted @Phuket2 approach from another thread; I had read about 'custom classes' before and understood the overall concept, but had no idea how to implement them; in the process I finally learned where all those 'event' calls (on load, on close...) would go, which I was wondering since some time now; thx @Phuket2!); as soon as I did that, the heading updates kept up on coming regularly with no interruption; beats me why? Closing the view did not stop the updates from coming up, as I had not added the stopUpdatingHeading() command yet.
- I then added the stopUpdatingHeading() in the 'will_close' method; as expected, this would now stop the updates when the view was closed. Again, something I did not know where to fit in the script. Thx!
- I then proceeded to add the startUpdatingLocation / stopUpdatingLocation in the same fashion, and behaviour was as expected: I was getting now heading and location updates regularly.
- I also turned back the filter to 1 degree change thresholds; updates continued to be received regularly as expected in 1 degree changes now.
- I'll discuss the calibration and actual update analysis at the end of the post.
@mikael:
Well, I did check the location and motion modules some weeks ago as they seemed what I needed , but mixed results encouraged me to go another route (this might be my misunderstanding or I was not using them properly, and I may need to revisit them):- concerning the motion module (.getmagneticfield()), initially I fell in the (0,0, 0.0, 0.0, -1) trap that has been reported in old posts, and that seemed to be related to the calibration; somehow this seemed to get resolved on its own -I frankly do not remember exactly how, I think t did move the device around for calibration and then it started pumping updates; however, i than realized that I did not know how to 'convert' the x,y,z vector into a heading in degrees, and did not find any 'simple' readings that could help me there. (Any suggestions?)
- concerning that location module, I did get the lat/lon readings, but 'course' and 'speed' was always -1 (I first thought this was an issue of not moving fast enough or having enough data for the device to calculate, but I remember doing some tests in a moving car and the values were always -1; still, I have the feeling now that his is just a matter of not understanding well how this works, so open to suggestions / pointers to tutorials.
@JonB (second post):
just saw it (I'm on GMT timezone), so I will look into your kind code example for further help, and particularly for those 'best practices' and more robust foolproof checks;Now, so far so good, that's some progress there thanks to you all;
However after my success into getting those heading and location updates, I ran some tests in a moving car running the script on the iPhone8 and the iPad2 simultaneously and I'm puzzled with the results; in a nutshell,
-
location readings were accurate and consistent in both,
-
the headings they reported were highly different readings (tens, even hundreds of degrees offset),
-
to make things worse, checking occasionally with the iPhone compass app gave a third different set of readings; I moved both devices in the 'calibration' pattern several times; when I did that, it looked like the readings made a 360° circle themselves, but that did not seem to make the readings more consistent between devices/apps. Note that I have NOT yet implemented the locationManagerShouldDisplayHeadingCalibration: I'm aware of that but need to look at the documentation and figure out how to do it first!
-
I also remarked that the rate of change in headings in the script is way 'faster' (meaning much more 'sensitive' to change, if that makes sense) than in the iPhone compass app, giving me the impression that the Apple app is somehow 'smoothed' or averaged;
So I'm now wondering:
- do magnetometers behave somehow like traditional compasses in boats or planes, where each compass is different from one another and is delivered with an individual 'deviation curve' that needs to be factored-in in the reading? Basically, the deviation curve tells you how many degrees to add/subtract for every heading indication (0° to 359°) in order to get a proper reading;
- I'm no physician, but should I expect that using the two devices simultaneously (or even the script and the Apple app on the same device simultaneously) would impact each other because of the presence of different magnetometers? or is the influence of the metallic mass of the car so important as to make the readings so disparate? but if this is so, what would be the use in a plane, where you also have other compasses and a big metallic mass?
So I ended up going to sleep wondering whether external precision magnetometers exist that you can use via Bluetooth offering an API in order to get precise and reliable readings.....
As I said, open to suggestions and pointers I may have missed,
PS: I promised it would be short, but it's still a long post...
Thx, -
If you think about how the iOS compass works, it measures the direction of the local magnetic vector, relative to the device. But, to convert thar into a heading, you would project one axis of the device, and the mag vector onto a horizontal plane (using the accelerometer to find gravity) then measure the angle between those two vectors. (Think of a nautical compass which is a floating sphere that always stays level).
But, there are different options of which device axis you want to represent the"front" of the device. For compass apps, often they work like a handheld compass, with screen meant to be held up and level, so reads 0 when the top of the device in portrait points North.
For car navigation apps, the device is mounted vertically, so the device "front" is the vector pointing out the back of the screen. In other cases you might want the device to be in landscape, etc. Thus enter the headingOrientation.
You can find the enumeration values if you search for iOS runtime headers
typedef NS_ENUM(int, CLDeviceOrientation) { CLDeviceOrientationUnknown = 0, CLDeviceOrientationPortrait, CLDeviceOrientationPortraitUpsideDown, CLDeviceOrientationLandscapeLeft, CLDeviceOrientationLandscapeRight, CLDeviceOrientationFaceUp, CLDeviceOrientationFaceDown }; (Note faceup/down are not allowed)
I think your code used headingOrientation=3 which is CLDeviceOrientationLandscapeLeft. Meaning the "front" of your device is the edge with the home button.
Most compass apps probably default to portrait, so you would expect differences of 90 or 270 degrees.
Also note the implication is that apple expects the device to be mostly horizontal, or at least have enough of a tilt to be unambiguous. if you have the device truly vertical, as I'd imagine you would want in an airplane, results might be poor, (since you could go from pointing North to south by tipping a few degrees forward or back). If you plan on holding your device vertical, you might need to implement the math yourself (maybe use the provided difference between magnetic and true heading to compute the local magnetic anomaly correction).
-
Some other thoughts on accuracy:
-
no doubt the cheap magnetometers in an iPhone have limited absolute accuracy. Also, they are installed relative to the case with only so much accuracy. And if you use it in a vehicle, your mounting repeatability will only be so accurate. You would almost want to do a calibration on the ground where you taxi in a fixed heading (long enough to get a course from the location info) to determine the fixed offset between vehicle and compass.
-
I'm not that familiar with aircraft, but in my sailing days, I know a lot of effort went into making sure to only use non magnetic metals like aluminum and brass anywhere near the binnacle. 0
-
Given there are many components in an iPhone, including batteries, various metals, etc, one could imagine a rather complex relationship between applied magnetic field and measured direction. You might need to "swing" the compass to calibrate it. I think the built in calibration is comparing accelerometer to compass, which I think determines local orientation offset, or maybe computes a magnetometers correction vector, not sure if that ends up correcting an orientation dependant errors.
-