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.