GPS ready

Former Member
Former Member
I have written an app with Connect IQ that gets the current GPS location and makes JSON call to get the sunrise / sunset data.
What I want to do is inform the user when the GPS is ready so they aren't guessing when to press the button to initiate the call.
I know when I use the built in running app on my Forerunner 920xt, it says "waiting for GPS" and then "GPS Ready"
How can i do this in Connect IQ?
Any thoughts?
  • There are several ways to handle this. The easiest way is to display a drawable or a progress bar when you're waiting for the GPS to be acquired. Here is an example of that...

    using Toybox.Application as App;
    using Toybox.Graphics as Gfx;
    using Toybox.WatchUi as Ui;

    class MyAcquirePositionDelegate extends Ui.BehaviorDelegate
    {
    hidden var _view;
    hidden var _callback;

    function initialize(view, callback) {
    BehaviorDelegate.initialize();
    _view = view;
    _callback = callback;

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

    function onPosition(info) {
    if (info == null || info.accuracy == null) {
    return;
    }

    if (info.accuracy != Position.QUALITY_GOOD) {
    return;
    }

    Position.enableLocationEvents(
    Position.LOCATION_DISABLE, null);

    _callback.invoke(info);
    }

    function onBack() {
    Position.enableLocationEvents(
    Position.LOCATION_DISABLE, null);

    return false;
    }
    }

    class MyViewDelegate extends Ui.BehaviorDelegate
    {
    function initialize() {
    BehaviorDelegate.initialize();
    }

    function onPosition(info) {
    Ui.popView(Ui.SLIDE_IMMEDIATE);

    if (info != null) {
    // you might also display a view to show the status of the fetch...
    System.println("submit request");

    // var view = new Ui.ProgressBar("Fetching Data", null);
    // var delegate = new MyRequestDelegate(view, info, self.method(:onJsonResponse));

    // Ui.pushView(view, delegate, Ui.SLIDE_IMMEDIATE);
    }
    }

    function onJsonResponse(code, data) {
    Ui.popView(Ui.SLIDE_IMMEDIATE);

    if (code == 200) {
    // everything went well
    }
    }

    function onSelect() {

    var view = new Ui.ProgressBar("Waiting for GPS", null);
    var delegate = new MyAcquirePositionDelegate(view, self.method(:onPosition));

    Ui.pushView(view, delegate, Ui.SLIDE_IMMEDIATE);

    return true;
    }
    }

    class MyView extends Ui.View
    {
    function initialize() {
    View.initialize();
    }
    }

    class MyApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    var delegate = new MyViewDelegate();
    var view = new MyView();

    return [ view, delegate ];
    }
    }


    The nice thing about doing that is you don't have to write a bunch of code to display the status to match how the native apps work, you can just rely on the built-in ProgressBar and be done.

    If you really must start fetching the gps data immediately, and then show status updates as they come in, you can do this...

    using Toybox.Application as App;
    using Toybox.Graphics as Gfx;
    using Toybox.WatchUi as Ui;
    using Toybox.System as Sys;

    class MyViewDelegate extends Ui.BehaviorDelegate
    {
    function initialize() {
    BehaviorDelegate.initialize();
    }

    hidden var _view;

    function onShow(view) {
    _view = view;

    // set the initial quality
    _view.onQualityChanged(Position.QUALITY_NOT_AVAILABLE);

    // you'll probably want some logic here to avoid enabling position
    // events if you already have a valid position. that is up to you.
    Position.enableLocationEvents(
    Position.LOCATION_CONTINUOUS,
    self.method(:onPosition));
    }

    function onHide(view) {
    Position.enableLocationEvents(
    Position.LOCATION_DISABLE,
    null);

    _view = null;
    }

    hidden var _info;

    function onPosition(info) {
    if (info == null || info.accuracy == null) {
    return;
    }

    // notify the view to update the gps quality indicator
    _view.onQualityChanged(info.accuracy);

    if (info.accuracy < Position.QUALITY_USABLE) {

    // position fix is not good enough
    _info = null;
    }
    else {
    // save the position info for later
    _info = info;
    }
    }

    function onSelect() {

    if (_info != null) {
    // submit the request
    }

    return true;
    }
    }

    class MyView extends Ui.View
    {
    //
    // the view gets a pointer to the delegate/controller so that it
    // may notify the delegate when the view is shown/hidden and so
    // the delegate has a chance to notify the view to update if the
    // state of the application changes.
    //
    hidden var _delegate;

    function initialize(delegate) {
    View.initialize();
    _delegate = delegate;
    }

    function onShow() {
    _delegate.onShow(self);
    }

    function onHide() {
    _delegate.onHide(self);
    }

    hidden var _quality;

    function onQualityChanged(quality) {
    _quality = quality;
    Ui.requestUpdate();
    }

    hidden static const _states = [
    "QUALITY_NOT_AVAILABLE",
    "QUALITY_LAST_KNOWN",
    "QUALITY_POOR",
    "QUALITY_USABLE",
    "QUALITY_GOOD"
    ];

    function onUpdate(dc) {
    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_BLACK);
    dc.clear();

    var cx = dc.getWidth() / 2;
    var cy = dc.getHeight() / 2;

    // instead of just displaying a string, you can show a
    // bitmap, or use color, or whatever
    dc.drawText(cx, cy, Gfx.FONT_LARGE, _states[_quality], Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER);
    }
    }

    class MyApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    var delegate = new MyViewDelegate();
    var view = new MyView(delegate);

    return [ view, delegate ];
    }
    }
  • Couple of things to consider. If you just need a single lat/lon and not a stream, you could use LOCATION_ONE_SHOT and not LOCATION_CONTINUOUS.

    Set flags at startup that you don't have a location or data, and also make the Position.enableLocationEvents() call.

    Then in onUpdate() check those flags. If there's no location, display that how you wish. If you have the location data, but don't have the data from the makeJsonRequest, display that instead. (maybe with progress bars as Travis suggested) If you have the data, display the data or maybe an error message.

    As soon as you get a location, you'll see that in onPosition (or what you have set as the callback in Position.enableLocationEvents() ), and for your use, the accuracy may not be that important, as being off by a few hundred feet might not matter much and other than "not available" could be used, but I don't think I've ever seen "not available" with a one_shot. Save the lat/lon, and change the flag as you now have the location, and right there, you can do the makeJsonRequest() with no user interaction. Then call Ui.requestUpdate(), so the screen updates with the new state.

    In onReceive(), set the flag that you have data, handle the case where there's an error (maybe another flag used by onUpdate) and process the data if there's no error. Then again, call Ui.requestUpdate() so the state is updated on the screen.

    Couple of side points. When you test in the sim (using "Simulation>fit data"), the accuracy will be what ever you have set in "Settings>GPS Quality" in the sim ("good" by default), and when you do the makeJsonRequest you'll get data faster than on a real device - the makeJsonRequest could take longer on a real device than getting the lat/lon. Also, last I checked (a week or so back), makeJsonReqest() still doesn't work with GCM/WP, only with GCM/Android and GCM/iOS. (it's a GCM/WP issue and not Connect IQ)
  • Former Member
    Former Member over 8 years ago
    Thanks

    Thanks for the help. I have got both of the example programs running, now I need to incorporate it into my program.
    One more question. Currently my program is getting a snapshot cause that all i need.
    In order to do the Progress bar, do I HAVE to do continuous? Or will a snapshot work there too?
    thanks again.
  • Yes, a snapshot will work just fine.
  • I use a red bar on a start screen .. and turn it to green when the GPS is at a 'good' level. Until then .. I just disallow them going forward.

    You could argue that you do not want to stop people from doing it .. but if the GPS is ****, what is the point of allowing it and their data will be ****. I have never had any issues getting a good lock .. even inside my residence.
  • I think it really depends on the app. If your app cannot function at all without GPS, then it may be best to just make users look at a loading screen or prevent them from advancing through the user interface until there is a good GPS fix.

    If you're implementing a widget, it may not make sense to immediately enable GpS and show a loading screen. If the user is flipping past your widget, and it throws up the GPS status indicator and turns on GPS, they'll get a beep and a toast that indicates that GPS is being acquired even though they didn't want to look at your widget in the first place. Or, if your app has a menu, they might want to get into the menu to configure an option. GPS isn't necessary for that, so why make them wait?

    Again, I'm not saying you're wrong. It just depends on what you're doing with the user interface.
  • Former Member
    Former Member over 8 years ago
    Thanks

    I got it working in my program!
    thanks for all your help!
  • Former Member
    Former Member over 8 years ago
    Hmmm

    Something dumb is happening.
    I had the app on my Forerunner 920xt before I added the progress bar.
    The addition of the progress bar worked in the simulator. but when I tried to transfer it to the watch, it doesn't appear.
    I tried adding it to the watch with "build for device" in eclipse. and just copying the file from windows to the watch.

    if I look on Garmin Express, it doesn't show up, but my other App ( battery %) is still there.
    If I press the up arrow on the watch * which use to she my sun rise / set app) does nothing. if I press it again, it shows my battery % app. Th Sun Rise /Set app use to sit in between the time and the BAt % app.

    What could be stopping the app from working on the watch? Could it be an incompatibility between the compiler version ante h OS version?
    Compiler: SDK 1.2.6
    Watch : Software version 8.20
    GPS version 3.00

    my code is at
    https://github.com/tsehnoutka/Garmin_SetRise/tree/develop/source

    if you are interested.
  • I'm not up on the CIQ versions of 920 FW, but I suspect it has a 1.3.x version, and if you look in \garmin\apps\logs\ciq_log.txt you'll see you got a signing error. (you can probably check the CIQ version by looking at "settings>system>about" on the watch)

    See https://forums.garmin.com/showthread.php?354407-App-Signing-and-Security

    You will need the 2.1.x SDK (and a 2.x plugin - current is 2.2.0) to do app signing (The 2.2.0 SDK is available but it's in beta and with it you can't build .iq files for the app store)
  • Former Member
    Former Member over 8 years ago
    hopefull only one more ;)

    I have the app loaded on my watch.
    When I press the up arrow on my watch, the app starts and I get the progress bar.
    It just loops there in the progress bar until the app times out and goes back to the main screen.

    If I go to an activity first, wait for GPS to be ready, THEN go to my app, immediately I get the "wait for GPS" at the top of the screen from the system. Then the "GPS is Ready" system message and my app continues to run correctly.

    What's gong on here?

    do I need to "prime" it ?