GPS position functionality in the SDK and/or VMs

My METAR/TAF app uses the [FONT=Courier New]LOCATION_ONE_SHOT[/FONT] type when calling [FONT=Courier New]Position.enableLocationEvents()[/FONT] method, and attach a "system activity" progress bar to the process. On my D2 Bravo this tends to take 10-15 seconds while it actually retrieves an actual fix, which it then displays as a lat/lon position at the bottom of the watch. On my fēnix 5, this same process take less than a second and then instead of returning a new position it seems to return the same lat/lon as last time, which seems to be based on the last GPS fix from an activity. Over the weekend I moved a complete degree of latitude and the only way I could get the fix returned to my app through Connect IQ on my fēnix 5 was to start an activity. My D2 Bravo, on the other hand, retrieved an actual fix each time it was requested. In all attempts to retrieve an updated position I was outdoors and should have been able to get a new fix, both devices are synced to the same phone (not that BLE should affect the GPS fix) and both apps are running the same version of the app (other than some build exclusion stuff which doesn't affect the request for a position).

So, that all said, I am wondering which of the following scenarios is the case:

1. The way it is on the fēnix 5 is the way CIQ should function (thus it's a bug on D2 Bravo VM); or
2. The way it is on the D2 Bravo is the way CIQ should function (thus it's a bug on the fēnix 5 VM); or
3. The way it works changed between CIQ SDK 1.x and SDK 2.x (since D2B is on 1.4 and F5 is on 2.2); or
4. Something else I'm not thinking of right now because I'm in need of caffeine.

Regardless of the outcome here, it actually might be a good feature to have in CIQ where I can request the last position be returned along with the age of the position. That would allow a quick position to be returned without needing to wait for an actual fix. Of course, it would also be good to be able to request that the last position fix be ignored, thus forcing a refresh (though, that I guess can be accomplished using continuous position).

Cheers,
Douglas
  • Regardless of the outcome here, it actually might be a good feature to have in CIQ where I can request the last position be returned along with the age of the position.

    That information is already available. The Position.Info object has an accuracy member that tells you the quality of the fix (one of the legal values is Position.QUALITY_LAST_KNOWN) and a when member that gives you the time of the position fix.

    If you don't really care about the quality of the data, you just want some position information, you should use Position.LOCATION_ONE_SHOT. If you want some control over the quality, you are expected to retrieve continuous updates until the position data is sufficient for your needs. You can write a little helper to do this pretty easily.
  • I'm posting code separately to avoid errors from the forum software...

    using Toybox.Position;

    class PositionDelegate
    {
    hidden var quality;
    hidden var when;
    hidden var callback;

    // empty init method commented out to placate forum software.

    function enableLocationEvents(min_quality, min_moment, callback) {
    self.quality = min_quality;
    self.when = min_moment;
    self.callback = callback;

    Position.enableLocationEvents(Position.LOCATION_CONTINUOUS, self.method(:onPosition));
    }

    function onPosition(info) {
    if (info.quality >= self.quality) {
    if (self.when == null || !info.when.lessThan(self.when)) {
    Position.enableLocationEvents(Position.LOCATION_DISABLE, null);

    self.callback.invoke(info);
    }
    }
    }
    }


    You should be able to use this to do everything you want flexibly. If you want to simulate Position.LOCATION_ONE_SHOT...

    var delegate = new PositionDelegate();
    delegate.enableLocationEvents(Position.QUALITY_LAST_KNOWN, null, self.method(:onPosition));


    If you want a usable position fix that is less than five minutes old...

    var five_minutes_ago = Time.now().subtract(new Time.Duration(300));

    var delegate = new PositionDelegate();
    delegate.enableLocationEvents(Position.QUALITY_USABLE, five_minutes_ago, self.method(:onPosition));


    Travis
  • That information is already available. The Position.Info object has an accuracy member that tells you the quality of the fix (one of the legal values is Position.QUALITY_LAST_KNOWN) and a when member that gives you the time of the position fix.


    So as not to get caught uninformed, I read the API docs on the drive back so I did see the mention of the "last known" quality [FONT=Courier New]when[/FONT] member. But the thing is, on the D2 Bravo I am pretty sure that I have *never* been returned a position of quality [FONT=Courier New]Position.QUALITY_LAST_KNOWN[/FONT]. And since it is returning an actual fix each time, this is likely to be the case for every request on the D2 Bravo (and other devices also?). And this is why I suggested that.

    If you don't really care about the quality of the data, you just want some position information, you should use Position.LOCATION_ONE_SHOT. If you want some control over the quality, you are expected to retrieve continuous updates until the position data is sufficient for your needs. You can write a little helper to do this pretty easily.


    Is this information something you learned from Garmin in the forums? Because I didn't see anything in the API docs that suggested that type [FONT=Courier New]ONE_SHOT[/FONT] will usually (or always) return the last position from some other app that used [FONT=Courier New]LOCATION_CONTINUOUS[/FONT] continuous.

    Perhaps this is a case of variation between the VM implementation on devices? Or something in the way the D2 Bravo aviation functionality is setup requires that it be implemented this way?

    Regardless, my original question of which scenario is correct still stands because the D2 Bravo does not, as I mentioned, seem to do anything other than return the most current position, as a result of an actual GPS fix.

    And if ONE_SHOT is indeed meant to potentially return a last known position (which could be days or weeks or months old) then I still think that I should be able to pass in an option to limit the minimum level of quality I'm willing to accept. But since the feature request list is rather long, I will have to code up a helper as you suggest. Once I know how things are supposed to work. ;)

    Cheers,
    Douglas
  • But the thing is, on the D2 Bravo I am pretty sure that I have *never* been returned a position of quality [FONT=Courier New]Position.QUALITY_LAST_KNOWN[/FONT]. And since it is returning an actual fix each time, this is likely to be the case for every request on the D2 Bravo (and other devices also?). And this is why I suggested that.

    I don't have a d2bravo device to test with, so I can't verify, and I definitely can't say if it is a device-specific behavior or a bug... The documentation for each of the related parts does allow for the callback registered to be called with data from a QUALITY_LAST_KNOWN fix, so my assumption is that anything is allowed and as a developer we're expected to handle that.

    I would expect that the QUALITY_LAST_KNOWN fix would only be considered valid for some predefined duration. i.e., If you use the built-in run app to get a GPS fix on a fenix5, wait 4 hours, and then run a ConnectIQ app that gets position data with LOCATION_ONE_SHOT, it would do a full GPS fix again before reporting a response (I do have a fenix5 to test with, but haven't bothered to write any code yet to verify this). That said, I'd also expect that this duration would differ between devices (the d2bravo is intended to be used in aviation settings where it would not be uncommon to travel 100mi in an hour, but a fenix5 which is intended to be used for outdoor recreation would not). Of course this is only something that the Garmin folks can answer; I'm guessing that the device teams will know what the limits are and why they are that way.

    Is this information something you learned from Garmin in the forums?

    No, not that I recall.

    Because I didn't see anything in the API docs that suggested that type [FONT=Courier New]ONE_SHOT[/FONT] will usually (or always) return the last position from some other app that used [FONT=Courier New]LOCATION_CONTINUOUS[/FONT] continuous.

    The documentation doesn't explicitly disallow it, so I'm making the assumption that it is allowed. This makes the most sense from the perspective of someone providing an API.. Don't say exactly what will happen as that might disallow differences in behavior across devices.

    Perhaps this is a case of variation between the VM implementation on devices? Or something in the way the D2 Bravo aviation functionality is setup requires that it be implemented this way?

    This is quite possible. It aligns most closely with my understanding of how the system is working (again note that I've not written tests to confirm behaviors across devices).

    ..my original question of which scenario is correct still stands because the D2 Bravo does not, as I mentioned, seem to do anything other than return the most current position, as a result of an actual GPS fix.

    Yes. As I mentioned, all of the documentation we have allows this behavior. It also allows for a device to return a LOCATION_LAST_KNOWN fix, and it would make sense for devices to do so (to optimize battery life) for some reasonable amount of time.

    I still think that I should be able to pass in an option to limit the minimum level of quality I'm willing to accept. But since the feature request list is rather long, I will have to code up a helper as you suggest.

    I'd expect that we'll see some feedback from the Garmin guys tomorrow. I'm interested to know what the result is... If my assumptions are right, then I think the snippet above might be something to throw into a library of useful code for sharing.

    Travis
  • I just noticed that my code snippet may have gotten lost while I was fighting with the forum software... Here is a rewrite of what I'd posted.

    class PositionQuery
    {
    hidden var quality;
    hidden var moment;
    hidden var callback;

    // omitting the empty initializer, it prevents me from posting

    function requestPosition(quality, moment, callback) {
    self.quality = quality;
    self.moment = moment;
    self.callback = callback;

    Position.enableLocationEvents(Position.LOCATION_CONTINUOUS, self.method(:onPosition));
    }

    function onPosition(info) {
    if (info.quality != null && info.quality >= self.quality) {

    if (self.moment == null || !self.moment.greaterThan(info.when)) {
    Position.enableLocationEvents(Position.LOCATION_DISABLED, null);

    self.callback.invoke(info);
    }
    }
    }
    }


    If you want to ensure you get a usable position on any device...

    var query = new PositionQuery();
    query.requestPosition(Position.QUALITY_USABLE, null, self.method(:onPosition));


    If you want to ensure that you get you get a last known position that is no more than 5 minutes old...

    var five_minutes_ago = Time.now().subtract(new Time.Duration(300));

    var query = new PositionQuery();
    query.requestPosition(Position.QUALITY_LAST_KNOWN, five_minutes_ago, self.method(:onPosition));
  • I know on the va-hr and one_shot there was (and maybe still is) a problem, where you often got "last known" back, and I did something like Travis, where I do a continuous, and wait for one of a useable quality and then disable position. (turn off GPS). The same code is used in all my apps for all devices that do a one shot as a result.

    Another thing to check is that "when" may be in "garmin time" (Jan 1,1990 based) and not "unix time"(Jan 1,1970 based). I don't do that kind of check with position, but the "garmin time" does occur in things like getXYZHistory() for "when".
  • I would expect that the QUALITY_LAST_KNOWN fix would only be considered valid for some predefined duration. i.e., If you use the built-in run app to get a GPS fix on a fenix5, wait 4 hours, and then run a ConnectIQ app that gets position data with LOCATION_ONE_SHOT, it would do a full GPS fix again before reporting a response (I do have a fenix5 to test with, but haven't bothered to write any code yet to verify this). That said, I'd also expect that this duration would differ between devices (the d2bravo is intended to be used in aviation settings where it would not be uncommon to travel 100mi in an hour, but a fenix5 which is intended to be used for outdoor recreation would not). Of course this is only something that the Garmin folks can answer; I'm guessing that the device teams will know what the limits are and why they are that way.


    It's now been just over 24 hours since I started my last activity (23 hrs, 15 minutes since the activity ended) and I tried to get a position updated again within my widget and this time the activity indicator was shown for the usual 10-15 seconds and the position updated. Armed with this information I would agree with you that the last known position is being cached on the fēnix 5 and is being considered valid for roughly 24 hours.

    Cheers,
    Douglas
  • I'll get in touch with the device teams and see if I can find out the particulars about position caching, because it does sound like this is controlled at the firmware level. The 2.3.0 API docs state for Position.Info.position:

    The latitude and longitude of the Location.


    If no GPS is available or is between GPS fix intervals (typically 1 second), the position is propagated (i.e. dead-reckoned) using the last known heading and last known speed. After a short period of time, the position will cease to be propagated to avoid excessive accumulation of position errors.


    It the "After a short period of time..." that's in question here. CIQ typically just asks the system for information and returns whatever the system provides. I don't know whether we have any control over what the position expiration time is.