A SimpleDataField with WebRequest

I'm a bloody beginner and I'd like to return the data from a web request:

import Toybox.Activity;

import Toybox.Lang;

import Toybox.Time;

import Toybox.WatchUi;

class AareTempView extends WatchUi.SimpleDataField {

    // Set the label of the data field here.

    function initialize() {

        SimpleDataField.initialize();

        label = "°C";

    }

    // The given info object contains all the current workout

    // information. Calculate a value and return it in this method.

    // Note that compute() and onUpdate() are asynchronous, and there is no

    // guarantee that compute() will be called before onUpdate().

    function compute(info as Activity.Info) as Numeric or Duration or String or Null {

        // See Activity.Info in the documentation for available information.

        return „water temperature from url https://aareguru.existenz.ch/v2018/current?city=thun&version=1.0.42&values=aare.temperature; 

    }

}

I've heard that it's only possible with a background service, but I don't how to combine the code above with the following samples (JSON, backgrounding):

https://developer.garmin.com/connect-iq/core-topics/https/ 

https://developer.garmin.com/connect-iq/core-topics/backgrounding/ 

Is anyone interested in helping a non information scientist?

Watch: Instinct 2 Solar Surf Edition

  • Here's some source code and instructions. I tested this in the simulator, but not on a real device.

    1) In VS Code, use the command palette to create a new monkey C project. CTRL/CMD-SHIFT-P > "Monkey C: New Project"

    - Project name: WaterTempDF

    - Type: Data Field (Simple)

    - Minimum API level 3.0.0

    (Background services require 2.3.0. but this code uses HTTP_RESPONSE_CONTENT_TYPE_TEXT_PLAIN (*) which is only present in 3.0.0 and higher)

    (*) Technically the service you're talking to is returning valid JSON (a number) but unfortunately Connect IQ requires JSON web responses to be wrapped in an object. So in this case, we have to tell Connect IQ the response is plain text.

    2) Use Command Palette > "Monkey C: Edit Products" to add the product of your choice. (I tested with fr955)

    3) Use Command Palette > "Monkey C: Edit Permissions" to add the Background and Communications permissions

    4) Replace WaterTempDFApp.mc with the following code:

    import Toybox.Application;
    import Toybox.Lang;
    import Toybox.WatchUi;
    import Toybox.Background;
    import Toybox.Communications;
    import Toybox.System;
    
    (:background) class WaterTempDFApp extends Application.AppBase {
        var view as WaterTempDFView or Null;
    
        function initialize() {
            AppBase.initialize();
        }
    
        function onStart(state as Dictionary?) as Void {
        }
    
        function onStop(state as Dictionary?) as Void {
        }
    
        // Return the initial view of your application here
        function getInitialView() as Array<Views or InputDelegates>? {
            if (Background.getTemporalEventRegisteredTime() != null) {
                Background.registerForTemporalEvent(new Time.Duration(5 * 60));
            }
            view = new WaterTempDFView();
            return [view] as Array<Views or InputDelegates>;
        }
    
        public function getServiceDelegate() as Array<System.ServiceDelegate> {
            return [new AppServiceDelegate()];
        }
    
        public function onBackgroundData(data as Application.PersistableType) as Void {
            if (data == null || data instanceof Lang.Float) {
                if (view != null) {
                    view.onTemperatureUpdated(data);
                }
            }
        }
    }
    
    function getApp() as WaterTempDFApp {
        return Application.getApp() as WaterTempDFApp;
    }
    
    (:background) class AppServiceDelegate extends System.ServiceDelegate {
        function initialize() {
            System.ServiceDelegate.initialize();
        }
        function onReceive(responseCode as Lang.Number, data as Lang.Dictioanry or Lang.String or Null) as Void {
            System.println("onReceive: response code: " + responseCode + ", data: " + data);
            if (responseCode == 200) {
                if (data instanceof Lang.String) {
                    // Convert string to float. If conversion fails, null will be returned
                    var dataAsFloat = data.toFloat();
                    Background.exit(dataAsFloat);
                }
            }
            Background.exit(null);
        }
    
        public function onTemporalEvent() as Void {
            Communications.makeWebRequest(
                "https://aareguru.existenz.ch/v2018/current",
                {
                    "city" => "thun",
                    "version" => "1.042",
                    "values" => "aare.temperature"
                },
                {
                    :method => Communications.HTTP_REQUEST_METHOD_GET,
                    :responseType  => Communications.HTTP_RESPONSE_CONTENT_TYPE_TEXT_PLAIN
                },
                method(:onReceive)
            );
        }
    }

    5) Replace WaterTempDFView.mc with the following code:

    import Toybox.Activity;
    import Toybox.Lang;
    import Toybox.Time;
    import Toybox.WatchUi;
    
    class WaterTempDFView extends WatchUi.SimpleDataField {
        var lastTemperature = null as Float or Null;
    
        function initialize() {
            SimpleDataField.initialize();
            label = "Water Temp";
        }
    
        function compute(info as Activity.Info) as Numeric or Duration or String or Null {
            return lastTemperature;
        }
    
        function onTemperatureUpdated(newTemperature as Float or Null) as Void {
            System.println("onTemperatureUpdated: " + newTemperature);
            if (newTemperature != null) {
                lastTemperature = newTemperature;
            }
        }
    }

    There is obviously a typo in onReceive()'s signature: Lang.Dictioanry should be Lang.Dictionary. For whatever reason, the code still compiles (with the default type checking level). Unfortunately the forum won't allow me to edit the code :/. For the same reason, I can't fix the handful of errors that arise when strict type checking is enabled.

    6) Build and run the app in the simulator by pressing F5. Trigger a background temporal event by opening the simulator app menu and selecting Simulation > Background Events > Event Type: Temporal Event

    This is what I see in the sim:

  • Thank you, I really appreciate that! The first mistake was that I chose the wrong API level in VS Code.

  • In the debug console I get the following message: Background: onReceive: response code: 404, data: null.

    1 problem: instinct2: No supported languages are defined...

    I checked the url in the browser, obviously the data provider isn't the problem. I triggered the background event as described.

  • I tried the code as written again, on both fr955 and instinct2. It still works for me.

    A 404 error does indicate that the URL wasn't found. Could have something to do with the way the simulator accesses the internet. Have you tried looking at the simulator HTTP traffic logs (File > View HTTP Traffic)?

    This is what I see:

    GET /v2018/current?version=1.042&values=aare.temperature&city=thun HTTP/1.1\r\nHost: aareguru.existenz.ch\r\nUser-Agent: Mozilla/5.0\r\nAccept: */*\r\n\r\n

  • There's nothing beginning with "GET":

  • I side loaded the DF on the watch: same problem. Therefore it's not the simulator. Would you mind sharing your manifest.xml code?

  • Sure np.

    <?xml version="1.0"?>
    <!-- This is a generated file. It is highly recommended that you DO NOT edit this file. -->
    <iq:manifest version="3" xmlns:iq="http://www.garmin.com/xml/connectiq">
        <!--
            Use "Monkey C: Edit Application" from the Visual Studio Code command palette
            to update the application attributes.
        -->
        <iq:application id="..." type="datafield" name="@Strings.AppName" entry="WaterTempDFApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="3.0.0">
            <!--
                Use the following from the Visual Studio Code comand palette to edit
                the build targets:
                "Monkey C: Set Products by Product Category" - Lets you add all products
                                           that belong to the same product category
                "Monkey C: Edit Products" - Lets you add or remove any product
            -->
            <iq:products>
                <iq:product id="fr955"/>
                <iq:product id="instinct2"/>
            </iq:products>
            <!--
                Use "Monkey C: Edit Permissions" from the Visual Studio Code command
                palette to update permissions.
            -->
            <iq:permissions>
                <iq:uses-permission id="Background"/>
                <iq:uses-permission id="Communications"/>
            </iq:permissions>
            <!--
                Use "Monkey C: Edit Languages" from the Visual Studio Code command
                palette to edit your compatible language list.
            -->
            <iq:languages/>
            <!--
                Use "Monkey C: Configure Monkey Barrel" from the Visual Studio Code
                command palette to edit the included barrels.
            -->
            <iq:barrels/>
        </iq:application>
    </iq:manifest>

  • Hey I took a look at my simulator settings and I found that "Use Device HTTPS requirements" was unchecked. I enabled this option and my code now fails with a 404 error (as it does for you.)

    I found a bug from over 3 years ago about this. Looks like the trusted CA list on Garmin devices is a bit lacking.

    https://forums.garmin.com/developer/connect-iq/i/bug-reports/simulator-dislikes-let-s-encrypt-certs

    > Addressing this is going to be in the realm of our WiFi team, but it looks like this won't be on their radar for quite some time. As of right now, we are assuming this will not be addressed.

    Here's another one:

    forums.garmin.com/.../tls-certificate-issue-with-fenix-7-and-epix-gen-2

    The person who reported the above bug wrote a CIQ app to test certain SSL certificates on Garmin devices:

    apps.garmin.com/.../1f8aecd2-d37f-49ec-bcdd-f19feb9e8994

    Unless Garmin fixes this on their end, I think your only option is to write your own proxy server (with a working SSL certificate) to funnel the data to your device, or to find a different service.

  • Thanks a lot! This brings light in the dark.