Handling Ack-enabled Properties

Why property acks?

The Ayla Cloud represents a device as a Digital Twin which includes a set of properties and values. For example, the cloud might use three property-value pairs (on=true, set_pt=72, and temp=75) to represent a thermostat:

When a mobile app sets a property value (e.g. `set_pt=72`), the cloud attempts to update two values: (1) the digital-twin value, and, if the device is online, and if the value is acceptable (e.g. in range), (2) the edge-device value:

<img src="https://files.readme.io/8e25eb9-two-step-no-ack.png" width="450" height="133" />

    However, by default, as indicated by the checkboxes in the diagram above, the cloud reports (to the mobile app) the status of the digital-twin update only. It does not report the status of the edge-device update which may have failed. Ack-enabled properties solve this problem.

    ## Requirements

    There are four requirements to ensuring that mobile or web applications can receive acknowledgments about edge-device property-value updates:

    1. The `ack_enabled` attribute of the property must be set to `true`.
    2. The host application must be ack-aware.
    3. The Ayla agent must be ack-aware. Currently, the [Production Agent](https://ayla.readme.io/docs/ayla-host-library-and-reference-application-v21) and the [Linux agent](https://ayla.readme.io/docs/ayla-linux-device-solution) can handle these types of acknowledgments, and they use different implementations.
    4. The mobile or web application must ask the cloud for the acknowledgment. (The [Ayla Mobile SDK](https://content.aylanetworks.com/SdkJavaDoc/SdkJavaDoc/index.html) does this automatically for the mobile app.)

    When `ack_enabled=true`, the host application provides `status` and `message` information to the agent; the agent returns the information to the cloud; and the mobile or web app retrieves the information from the cloud. `ack_enabled` is a property attribute. The Ayla Cloud represents a property as a set of attributes and values. Here is an example:

    ```json
    {
        "type": "Property",
        "name": "set_pt",
        "base_type": "integer",
        "read_only": false,
        "direction": "input",
        "scope": "user",
        "data_updated_at": "2020-04-19T18:59:15Z",
        "key": 33334444,
        "device_key": 11112222,
        "product_name": "Thermostat",
        "track_only_changes": false,
        "display_name": "set_pt",
        "host_sw_version": false,
        "time_series": false,
        "derived": false,
        "app_type": null,
        "recipe": null,
        "value": null,
        "denied_roles": [],
        "ack_message": "Out of range.",
        "ack_status": 1,
        "acked_at": "2020-04-21T13:17:48Z",
        "ack_enabled": true,
        "retention_days": 30
    }
    ```


    Setting the `ack_enabled` attribute to `true` is essential for ensuring that mobile and web apps receive positive or negative acknowledgments about whether property-value updates reach edge devices. The following diagram represents a positive acknowledgment:

    <img src="https://files.readme.io/15c6a65-two-step-ack-success.png" width="450" height="133" />

        And, the one below, because the host application, after determining that the value is out of range, has rejected the update, represents a negative acknowledgment:

        <img src="https://files.readme.io/6dbf509-two-step-ack-error.png" width="450" height="133" />

            ## Asynchronous updates

            Updating the digital-twin and edge-device copies of a property value is asynchonous. Consider the following diagram which depicts an iOS/Android mobile app, the Ayla Cloud, and an edge device with an Ayla Agent, Ayla Host Library, and Host Application:

            <img src="https://files.readme.io/8d0d4a7-mobile-device-cloud-edge-device.png" width="720" height="136" />

                When `ack_enabled=false`, from the perspective of a mobile app, setting a property value means setting the value in the digital twin:

                <img src="https://files.readme.io/a7a2ec9-mobile-device-to-cloud.png" width="720" height="136" />

                    Aynchronously, the cloud sends `ANS Check` to the agent indicating that updates are available:

                    <img src="https://files.readme.io/d2daf7b-cloud-to-agent-check.png" width="720" height="136" />

                        The agent retrieves the update information:

                        <img src="https://files.readme.io/24ac7e0-agent-to-cloud-get.png" width="720" height="136" />

                            The agent passes the information to the host application which completes the edge-device update, and returns **control** (but not **status**) to the agent.

                            <img src="https://files.readme.io/9157097-agent-to-host-lib-to-app.png" width="720" height="136" />

                                In the scenario above, no status information about the edge-device update flows back to the cloud, so the mobile app cannot ask the cloud whether the property value was actually updated on the edge device. But, when `ack_enabled=true`, and when the mobile app, agent, and host app are *ack-aware*, the edge-device update status flows back to the mobile app. The following shows a positive acknowledgment where `ack_status=0` means `success`, and the `ack_message` integer `123` is user-defined:

                                <img src="https://files.readme.io/5d882d1-ack-enabled-success.png" width="720" height="270" />

                                    Below, the host app rejects the update, and returns a negative acknowledgment where `ack_status=1` means `error`, and the `ack_message` integer `456` is user-defined as `out of range`:

                                    <img src="https://files.readme.io/39b2b66-ack-enabled-error.png" width="720" height="270" />

                                        # Implementation

                                        ## Setting ack\_enabled

                                        You can set the `ack_enabled` attribute of a property by several means.

                                        ### Modify an existing template

                                        1. Browse to the [Ayla Developer Portal](https://ayla.readme.io/docs/ayla-developer-portal).
                                        2. Click *Design a Device*.
                                        3. Click the template name of a *Private* template.
                                        4. Click *Properties*.
                                        5. Click a particular property.
                                        6. Check *Ack Enabled*.
                                        7. Click *OK*.

                                        If necessary, reassocate the modified template to your device:

                                        1. Click *Devices*.
                                        2. Click the *Serial Number* of your device.
                                        3. Click *Template*.
                                        4. In the dropdown, re-select the template.
                                        5. Click Reassociate.

                                        ### Create a new template

                                        1. Browse to the [Ayla Developer Portal](https://ayla.readme.io/docs/ayla-developer-portal).
                                        2. Click *Design a Device*.
                                        3. Click *Add*, and fill out the form, and click OK.
                                        4. Find the new template on the list, and click.
                                        5. Click *Properties*.
                                        6. Click *Import*, and important a csv file that specifies `ack_enabled=true`. Example:
                                        ```
                                        ack_enabled,base_type,direction,name,scope
                                        false,boolean,output,Blue_button,user
                                        true,boolean,input,Blue_LED,user
                                        true,string,input,cmd,user
                                        true,decimal,input,decimal_in,user
                                        false,decimal,output,decimal_out,user
                                        true,boolean,input,Green_LED,user
                                        true,integer,input,input,user
                                        false,string,output,log,user
                                        false,integer,output,output,user
                                        ```

                                        ## Modifying a host app

                                        This example handles acknowledgments for a property named `set_pt` which specifies the *set point* for a thermostat:

                                        ```
                                        enum demo_val_err {
                                        VAL_NO_ERR = 0,
                                        VAL_BAD_LEN,
                                        VAL_OUT_OF_RNG
                                    };

                                        static s32 set_pt;

                                        static struct prop prop_table[] = {
                                        ...
                                        { "set_pt", ATLV_INT, set_set_pt, prop_send_generic, &set_pt, sizeof(set_pt)},
                                        ...
                                    };

                                        static void set_set_pt(struct prop *prop, void *arg, void *valp, size_t len)
                                        {
                                            s32 i = *(s32 *)valp;

                                            if (len != sizeof(s32)) {
                                            prop->ack.ack_status = 1;
                                            prop->ack.ack_message = VAL_BAD_LEN;
                                            return;
                                        }

                                            if (i > 120 || i < 35) {
                                            prop->ack.ack_status = 1;
                                            prop->ack.ack_message = VAL_OUT_OF_RNG;
                                            return;
                                        }

                                            prop->ack.ack_status = 0;
                                            prop->ack.ack_message = VAL_NO_ERR;
                                            set_pt = i;
                                        }
                                        ```

                                        ## Modifying a mobile app

                                        The [Ayla Mobile SDK](https://content.aylanetworks.com/SdkJavaDoc/SdkJavaDoc/index.html) device template automatically handles `acknowledge` properties without any changes to developer code. Typically the default timeout is extended to handle events with longer durations.

                                        ```
                                        private void profileCloudCommand(TestSuite suite, AylaProperty prop, Object value) {
                                        RequestFuture<AylaDatapoint<Integer>> future = RequestFuture.newFuture();
                                        AylaAPIRequest<AylaDatapoint> request = prop.createDatapointCloud(value, null, future, future);
                                        AylaDatapoint dp;
                                        _cloudRequestCount++;
                                        long startTime = System.currentTimeMillis();
                                        try {
                                        dp = future.get(prop.isAckEnabled() ? 10 * 1000 : 3000, TimeUnit.MILLISECONDS);
                                    } catch (InterruptedException e) {
                                        fail();
                                        suite.logMessage(LogEntry.LogType.Error, "Interrupted");
                                        return;
                                    } catch (ExecutionException e) {
                                        fail();
                                        suite.logMessage(LogEntry.LogType.Error, "Error " + e.getMessage());
                                        return;
                                    } catch (TimeoutException e) {
                                        fail();
                                        suite.logMessage(LogEntry.LogType.Error, "Timed out waiting for response");
                                        return;
                                    }
                                        long duration = System.currentTimeMillis() - startTime;
                                        suite.logMessage(LogEntry.LogType.Info, "Changing "+ prop.getName() + " to " +
                                        ""+value + " via Cloud" );
                                        if (!dp.getValue().equals(value)) {
                                        suite.logMessage(LogEntry.LogType.Warning, "Returned datapoint value is " + dp.getValue());
                                    }
                                        if (prop.isAckEnabled()) {
                                        Date ackedAt = prop.getAckedAt();
                                        if (ackedAt != null) {
                                        long diff = ackedAt.getTime() - request.getNetworkResponseTimestamp();
                                        suite.logMessage(LogEntry.LogType.Info, "Property ack'd by device " +
                                        Math.abs(diff) + "ms " +
                                        (diff <= 0 ? "before" : "after") + " the server response");
                                    }
                                    }
                                        long networkTime = request.getNetworkTimeMs();;
                                        _cloudNetworkTime += networkTime;
                                        if(_cloudMinTime > networkTime){
                                        _cloudMinTime = networkTime;
                                    }
                                        if(_cloudMaxTime < networkTime){
                                        _cloudMaxTime = networkTime;
                                    }
                                        _cloudRequestPassCount++;
                                        _cloudTotalTime += duration;
                                        suite.logMessage(LogEntry.LogType.Info, "Cloud Operation Time: " + duration + "ms; Network: " +
                                        networkTime+ "ms");
                                    }
                                        ```

                                        ## Modifying a Web app

                                        Like the [Ayla Mobile SDK](https://content.aylanetworks.com/SdkJavaDoc/SdkJavaDoc/index.html), the [Ayla Javascript Client](https://github.com/AylaNetworks/JS_AylaSDK/blob/develop/src/device/AylaProperty.ts) (To view this page, contact your Ayla Representative to set up an Ayla GitHub account) handles `acknowledge` properties without requiring changes to developer code.