Integrated Agent
The Ayla Integrated Agent is essentially embedded code integrated with embedded networking and OS software to run on a Wi-Fi communications module. This allows OEMs to create their own firmware solution for secure connectivity to Ayla Services. The customer application code runs on this module with the Ayla Integrated Agent, and has access to all of the features and services of the OS and networking stack on the module. Some primary characteristics are:
- Available for embedded or LINUX solutions.
- Targeted for specific Wi-Fi manufacturers and their supported SDKs.
- The Ayla Integrated Agent is available as a library or source.
- Secure, reliable connectivity to the Ayla Cloud Service.
- LAN mode support – direct mobile-to-device communications.
- Support for file property transfer (upload and download).
- Support for OTA image management and distribution.
- On-Device schedules for automated control.
- Wi-Fi setup for specific Wi-Fi modules, and example application code.
- Well-equipped for applications with existing RTOS and networking.
- APIs and data representation are similar to the Ayla Production Agent (Black Box).
- The Ayla White-Box-based Cloud Agent’s modular design allows code for additional functions to be included as needed.
Note that there are separate documents for some optional functionalities included with the Integrated Agent on some platforms. These are:
OEM Requirements
Though the Ayla Integrated Agent (White Box) is a type of endpoint that allows for a more complex and versatile device than the Ayla Production Agent (Black Box) class of devices, the development effort is significantly longer for OEMs and therefore results in longer time to market than the Production Agent modules. OEMs are responsible for all device side functionality including:
- Host application implementation.
- Initial configuration.
- Command line features, if any.
The SDK normally provides the following features, but the OEM may be required to configure, verify, and support their correct working:
- OS features, such as tasks, timers.
- Memory management, including RAM and flash resource issues.
- Networking stack, including TLS.
Components
The Ayla Integrated Agent is divided into the components shown in the following diagram:
Note: The Ayla Integrated Agent was previously referred to as the Ayla Device Agent (ADA) or the Ayla Embedded Agent, and there are still references to ADA in our code and end-user documentation.
ADA Client
The ADA client manages the connection to the Ayla Device Service (ADS). The ADA client receives property updates and commands and then sends property updates and responses to commands, using RESTful HTTP interfaces over MQTT over TLS. In versions before ADA-3.0, MQTT is not used.
Property Subsystem
The property subsystem collects the interfaces between the application and the client to allow parts of the application to control the Ayla Device Service (ADS) properties. As the client receives properties, these properties are sent to various property managers and then the client sends them to the application. As the application receives the new property values, the application may call property manager interfaces to send the new values to the client.
Notification Subsystem
In versions before ADA-3.0, the notify subsystem manages the protocol used by the Ayla Notification Service (ANS). This maintains a UDP flow with ANS so that the ANS may send events indicating when the ADS has property updates or commands for the client. This subsystem sends a periodic UDP packet, typically every 30 to 90 seconds to keep the firewall open for notifications from the ANS. This also informs the ANS and ADS of the on-line status of the device. In ADA-3.0 and later, this functionality is performed using MQTT.
LAN Client
The LAN client operates as an HTTP service on the local network. The RESTful interface presents to mobile apps that allow them to read and write properties and send some commands to the device. The device and mobile apps use a shared secret during a key-exchange to create a session key used to encrypt subsequent traffic. The mobile client sends a keep-alive HTTP PUT command periodically.
HTTP Client
The HTTP client subsystem of the Ayla Integrated Agent provides a common interface to the HTTP client in the platform’s SDK. The client and LAN client use it for their RESTful interfaces.
MQTT Client
In ADA-3.0 and later, the MQTT client sends HTTP requests to the service and receives HTTP responses. It also receives notifications from the service when new property datapoints or commands are posted.
Schedule Subsystem
The schedule subsystem provides the ability for the device to change a property value on a specified time schedule. For example, a schedule sent to the device from the cloud could entail turning on lights from 7 PM to 11 PM every weeknight. As long as the time is known, schedules respond even when Internet connectivity is down. On the Ayla Device Service (ADS), schedules are distinct from properties, but in the Ayla Integrated Agent schedules are treated as just another type of property.
Configuration Subsystem
The configuration subsystem interfaces to the SDK’s or the application’s particular method for saving data in persistent storage between boots of the device. The configuration is organized as a set of name / value pairs. The names are hierarchically oriented and specified by ASCII tokens separated by slashes. The values can be binary data up to 1 Kilobyte or more, but are typically much shorter UTF-8 strings.
Logging
The Ayla Integrated Agent has a logging system used by each of the agent’s subsystems. Log messages can be enabled or disabled by subsystem and severity. Normally, error, warning, and informational messages are enabled on all subsystems. In addition, debug or verbose-debug messages can be enabled.
Command-line Interface
The Ayla Integrated Agent provides a small number of CLI commands that may or may not be used by the application. These interfaces allow some debugging and manufacturing setup of the device.
OTA Updates
The Ayla Integrated Agent and Ayla Device Service (ADS) provide the ability to download OTA updates to the device. The format of the OTA and method of applying the update is left up to the SDK and application implementer. The Ayla Integrated Agent has APIs, which notify the application that an OTA is available, let the agent start the download, receive the update sections, and report the status of the update back to the ADS.
Dependencies
The Ayla Integrated Agent relies on the functionality provided by the platform and supplied by the product integrator. The agent is tested with a particular SDK version but the SDK is not included with the agent, and must be obtained separately.
The platform SDK or host application must include:
- C library functions
- RTOS functions
- TCP/IP network
- Connection monitor: indicate to the Ayla Integrated Agent when the network is available and not.
- If Wi-Fi is used, but the Ayla Wi-Fi support is not used, the platform must provide for Wi-Fi setup and monitoring.
- TLS/SSL client
- HTTP client (before ADA-3.0)
- HTTP server
- Functionality to persist configuration information (e.g., in flash) over power cycles
- Console output for debug logging
- A CLI or other method to configure the device during manufacture
- Firmware update methods (OTA), if desired
- Real-time clock
Multithreading
The Ayla Integrated Agent runs the agent code in its own thread. Events and calls from other threads are queued to be performed in the agent thread. The agent thread generally does not block while waiting for network response or other threads. The host application usually has its own threads.
Memory Requirements
The memory requirements for the Ayla Integrated Agent are feature-dependent and depend on the SDK. This section estimates the memory required on the Espressif SDK using a RISC-V instruction set, including the size of the platform SDK, with a small demo host app.
For a system on another platform for which code cannot be directly executed from flash, the code requires space in both flash and in RAM, but the flash sizes below include some significant read-only and initialized-data space as well. Data space is almost entirely uninitialized data, so only requires RAM.
The flash space estimates can be used to determine the amount of flash needed for Over-the-Air (OTA) updates, so to estimate the flash size needed, the total should be doubled, for the OTA, and added to the bootloader size plus room for configuration and other flash data.
NOTE: Remember to include any additional space as needed for Wi-Fi firmware or Web server images.
The following table provides an estimate for the memory required by the Ayla Integrated Agent code and data. The sizes for OTA are only the memory required for downloading the image, not for saving or applying the update.
Memory requirements for a typical esp32-c3 module with ADA-3.0 and ayla_demo:
Subsystem | Flash (KB) | RAM (KB) |
---|---|---|
Ayla Agent | 193 | 22 |
ADA AL platform | 50 | 1 |
Ayla BLE | 6 | 0 |
Ayla Wi-Fi | 28 | 3 |
Ayla Demo Host App | 16 | 2 |
FreeRTOS | 21 | 20 |
LwIP | 132 | 4 |
MbedTLS | 123 | 0 |
SDK | 600 | 79 |
SDK BLE | 144 | 12 |
SDK Wi-Fi | 200 | 19 |
- | - | - |
Subtotal | 1314 | 143 |
Heap | 0 | 240 |
Total | 1314 | 383 |
Note that the heap shown here of 240 KB is an estimate based on the demo usage after connecting to the cloud and shutting down BLE. There was 159 KB in use and about 79 KB free.
ADA Client
The ADA client manages the connection to the Ayla Device Service (ADS). The ADA client receives property updates and commands, and then sends property updates and responses to commands, using RESTful HTTP interfaces over MQTT and TLS. The platform initialization calls ada_init()
to initialize the Ayla Integrated Agent before waiting for a Wi-Fi or network connection to be established. Once a network connection is established, the ada_client_ip_up()
call is made to start the connection to the ADS. Following are the APIs used:
ada_init
int ada_init(void)
This call initializes the Ayla Integrated Agent. The call returns 0 on success. This API should be called after the platform (including Wi-Fi) is initialized, but before waiting for a Wi-Fi or network connection to be established. The ada_conf structure should be initialized first. ada_init() initializes the Ayla Integrated Agent software real-time clock (RTC) and sets the hardware RTC if it is set to a time before a minimum value required for the TLS certificates. This also sets the Ayla Integrated Agent logging options. If necessary, ada_init() creates the client thread. Finally, ada_init()
sets up mDNS to advertise the device for the mobile application.
Starting and stopping the Agent
ada_client_up()
int ada_client_up()
The platform calls ada_client_up()
when all network interfaces are connected and the client should start connecting to the ADS and providing LAN-mode service. The mac_addr and hw_id must be set in the ada_conf first. This call returns 0 on success, or -1 if the client is not enabled or not configured. This interface is deprecated after ADA-1.9 and ada_client_ip_up()
should be used instead.
ada_client_ip_up
int ada_client_ip_up()
The platform calls ada_client_ip_up()
when the network is connected and the client should start connecting to the ADS and providing LAN-mode service. The mac_addr and hw_id must be set in the ada_conf first. This call returns 0 on success, or -1 if the client is not enabled or not configured. [ADA-1.9 and later only]
ada_client_lc_up
int ada_client_lc_up()
The platform calls ada_client_lc_up()
when the BLE interface is up for local control and the client should start providing local control service. This call returns 0 on success, or -1 if the client is not enabled or not configured. [ADA-1.9 and later only]
ada_client_down
void ada_client_down(void)
The platform calls ada_client_down()
when the connection to the local network is lost. This interface is deprecated after ADA-1.9 and ada_client_ip_down()
should be used instead.
ada_client_ip_down
void ada_client_ip_down(void)
The platform calls ada_client_ip_down()
when the connection to the IP network is lost. [ADA-1.9 and later only]
ada_client_lc_down
void ada_client_lc_down(void)
The platform calls ada_client_lc_down()
when the connection to local control is lost. [ADA-1.9 and later only].
Property Subsystem
The main purpose of the Ayla Integrated Agent is to transfer datapoint values for properties. The agent does this through handlers called property managers. An application can have several property managers, but usually just has one. Each property manager handles one or more properties. A property manager registers with the Ayla Integrated Agent, providing a structure containing function pointers to the various entry points. Afterward, the property manager may call the agent to send a new property value to the ADS. The agent may also call the property manager to get or set a property value. The agent calls the connect_status()
function of the property manager whenever reachability to the ADS or LAN clients changes.
Several property interfaces are asynchronous. For example, when a property is requested by the agent, the response may be delivered immediately or at a later time, for example, after an interaction with an external device completes. To send a new value for a property, the property manager calls the ada_prop_mgr_send()
. This call does not send the property immediately, but merely indicates that the property manager has something to send. When the Ayla Integrated Agent sends the data, the agent then calls the send_done()
function of the property manager to report success or failure. When receiving a property value from the ADS, the agent calls the prop_recv()
or prop_meta_recv()
function of the property manager.
Data Types
The ayla_tlv_type
enum indicates the type of a property value. Only some of the possible values are used for property types. Others have internal uses. The table below provides the property types and their meaning.
Type | Description |
---|---|
ATLV_INT | Integer, 32-bit |
ATLV_UINT | Unsigned integer, 32-bits |
ATLV_BIN | Binary bytes, up to 255 bytes long |
ATLV_UTF8 | UTF-8 encoded character string (see note below) |
ATLV_BOOL | Boolean value (0 or 1) |
ATLV_CENTS | A decimal number multiplied by 100 in a 32-bit integer |
ATLV_SCHED | A schedule describing actions to be taken at specified times |
ATLV_LOC | A file property location |
Note: UTF-8 string values may be up to 1024 bytes long, not counting the NUL termination. The string must still be no longer than 1024 bytes after the agent applies escapes required for JSON strings. For example, a new-line, carriage-return, tab, backslash, or double-quote takes 2 bytes to JSON-encode. A byte value between 1 and 0x1f takes 6 bytes to JSON-encode, as does any invalid UTF-8 byte.
Destination Mask
Several APIs use a parameter or variable called the destination mask. This is an unsigned 8-bit integer value in which each bit represents a destination, and may indicate that a property should be sent to that destination, or that connectivity is available to that destination:
0x01 Mask for ADS
0x02 Mask for LAN Client 1
0x04 Mask for LAN Client 2
0x08 Mask for LAN Client 3
0x10 Mask for Local Control Client 1
0x20 Mask for Local Control Client 2
0x40 Mask for Local Control Client 3
0x80 Mask for Schedule Properties
Error Codes
Several APIs return or use enum ada_err. The enum ada_err value provides the reason for failure so that an appropriate action may be taken. Error values from enum ada_err are always negative and can therefore be used with an interface that returns a zero or positive value on success. Zero generally indicates success; however, some interfaces return a Boolean result of 0 or 1, neither of which is an error. The following table provides the error codes and a short description of each.
Error Code | Value | Description |
---|---|---|
AE_OK | 0 | Success. |
AE_BUF | -1 | Network buffer shortage or temporary pause. |
AE_ALLOC | -2 | Memory allocation failed. |
AE_ERR | -3 | Non-specific error used when nothing else is appropriate. |
AE_NOT_FOUND | -4 | Property or other object not found. |
AE_INVAL_VAL | -5 | Invalid value for the property or other object. |
AE_INVAL_TYPE | -6 | Incorrect type for the property or other object. |
AE_IN_PROGRESS | -7 | Successfully started, but not finished. |
AE_BUSY | -8 | Another operation is in progress |
AE_LEN | -9 | Invalid or excessive length. |
AE_INVAL_STATE | -10 | Invalid state, the API was called without correct prerequisites. |
AE_TIMEOUT | -11 | Operation exceeded a time limit. |
AE_ABRT | -12 | Connection aborted. |
AE_RST | -13 | Connection reset. |
AE_CLSD | -14 | Connection closed. |
AE_NOTCONN | -15 | Not connected. |
AE_INVAL_NAME | -16 | Invalid property name. |
AE_RDONLY | -17 | Attempted to set a read-only value or from-device property. |
AE_CERT_EXP | -18 | TLS certificate is not yet valid or has expired. |
AE_INVAL_OFF | -19 | Invalid file offset. |
AE_FILE | -20 | Generic file error during file transfers. |
Property Interfaces
Host applications can use one of two property management interfaces offered by the agent which differ in the way they manage the asynchronous notification of the success or failure related to the action of sending a property value from the device to the cloud:
- The Simple Table Interface is the easier of the two. It is a way to handle properties in a synchronous manner in cases where the property manager can respond immediately (synchronously) to property requests.
- The Primitive Interface is a low-level interface that offers greater flexibility, but requires more work including property name validation. The Simple Table Interface is build on the primitive one.
Simple Table Interface
The Simple Table Approach to property management handles properties using a table that lists each property by name. The table is an array of ada_sprop
structure instances. Each instance has a property name and type, a pointer to the variable holding the value, and a pointer to a function that is called to handle changes to the value. The following sections describe the more important aspects of the table approach to property management.
Simple property structure
Declared in <ada/sprop.h>
, the ada_sprop
structure defines a simple property:
struct ada_sprop {
const char *name;
enum ada_tlv_type type;
void *val;
size_t val_len;
ssize_t (*get)(struct ada_sprop *, void *buf, size_t len);
enum ada_err (*set)(struct ada_sprop *, const void *buf, size_t len);
u8 send_req;
u8 source_mask;
char *ack_id;
struct prop_dp_meta *metadata;
};
Variable | Description |
---|---|
name | The name member is the name of the property. This should match a name used in the Ayla service template for the device. |
type | The "type" enum gives the type of the property as returned and expected by the get and set functions. |
val | This is the pointer to the storage for the value. Integer values are placed there as 32-bit binary values in the host byte order. |
val_len | This is the size of the value in bytes. These are used only by the get or set functions provided here, so it can actually be anything. |
get | The get member points to a function that is called whenever the property value is needed by the ADS or a mobile application. It should store the value into the provided buffer. The len argument specifies the size of the buffer, which should be large enough to receive the value. The get function should return the length that was stored or an enum ada_err if the get could not be performed for some reason. |
set | The set member points to a function to be called whenever the property value is to be changed by the ADS or a mobile application or a schedule. The function can fetch the value from the provided buffer. |
send_req | The send_req member is set internally when the property should be sent to the cloud once connectivity is (re)established. |
source_mask | The source_mask member is set internally to indicate which node performed the most recent set operation. (version 1.7 or later) |
ack_id | The acknowledgement ID is set internally when the host application may acknowledge the datapoint. (version 1.10 or later) |
metadata | On receiving metadata, this member is set to point to the metadata. (version 1.10 or later) |
Library-provided Handlers
The ADA library provides functions that can be used directly in the property table or by application handlers that are called from the property table. These functions make it easier to handle simple properties. These functions match the struct ada_sprop members for get and set and are:
ssize_t ada_sprop_get_int(struct ada_sprop *, void *, size_t)
ssize_t ada_sprop_get_uint(struct ada_sprop *, void *, size_t)
ssize_t ada_sprop_get_bool(struct ada_sprop *, void *, size_t)
ssize_t ada_sprop_get_string(struct ada_sprop *, void *, size_t)
enum ada_err ada_sprop_set_int(struct ada_sprop *, const void *, size_t)
enum ada_err ada_sprop_set_uint(struct ada_sprop *, const void *, size_t)
enum ada_err ada_sprop_set_bool(struct ada_sprop *, const void *, size_t)
enum ada_err ada_sprop_set_string(struct ada_sprop *, const void *, size_t)
Each get
or set
routine uses the val
and val_len
fields of the supplied property table entry to get or set the value and place it in the supplied buffer. If you want the application to do additional checks or operations on the value, the application may do this by supplying its own set routine in the table. It may be convenient to call the library-supplied handler from that routine in order to perform size conversions and type checks, and then to reflect the newly set value onto an output pin or LED, for example.
Simple table functions
ada_sprop_mgr_register
enum ada_err ada_sprop_mgr_register(char *name, struct ada_sprop *table, unsigned int entries)
This function registers a property table with the Ayla Integrated Agent. The name
argument gives the group of properties a name for debugging messages. The table
argument is the address of the property table. The third argument is the number of entries
in the table. This returns 0 on success. The only possible error is a memory allocation failure (AE_MEM).
ada_sprop_send
enum ada_err ada_sprop_send(struct ada_sprop *sprop
This function sends the value of the property pointed to by sprop
, which should point to the entry in the table that was used in ada_sprop_mgr_register(). The get() function in sprop is used to get the current value, and that value is saved until it is sent to the service. There is no output from the application if the value is not sent successfully.
ada_sprop_send_by_name
enum ada_err ada_sprop_send_by_name(const char *name)
This function sends the value of the property specified by name
, the get()
function in sprop is used to get the current value, and that value is saved until it is sent to the service. This function returns 0 if the request is started successfully. There is no feedback to the application if the value is not sent successfully.
ada_sprop_send_with_meta
enum ada_err ada_sprop_send_with_meta(struct ada_sprop *prop,
struct prop_dp_meta *metadata, u8 count);
Note: This function is available in version 1.10 or later.
This function sends the value of the property pointed to by sprop
, the get()
function in sprop is used to get the current value, and that value is saved until it is sent to the service. The supplied metadata
is sent with the value. count
is the number of elements in the metadata array. This function returns 0 if the request is started successfully. There is no feedback to the application if the value is not sent successfully.
ada_sprop_send_by_name_with_meta
enum ada_err ada_sprop_send_by_name_with_meta(const char *name,
struct prop_dp_meta *metadata, u8 count);
Note: This function is available in version 1.10 or later.
This function sends the value of the property specified by name
, the get()
function in sprop is used to get the current value, and that value is saved until it is sent to the service. The supplied metadata is sent with the value. This function returns 0 if the request is started successfully. There is no feedback to the application if the value is not sent successfully.
ada_sprop_send_to
enum ada_err ada_sprop_send_to(struct ada_sprop *sprop, u8 dest_mask)
Note: This function is available in version 1.7 or later.
This function sends the value of the property to the connected destinations specified by dest_mask. If the cloud is not connected, a flag is set to cause the property to be sent when the cloud is next connected.
ada_sprop_send_to_by_name
enum ada_err ada_sprop_send_to_by_name(const char *name, u8 dest_mask)
Note: This function is available in version 1.7 or later.
This function sends the value of the property name
to the connected destinations specified by dest_mask. If the cloud is not connected, a flag is set to cause the property to be sent when the cloud is next connected.
ada_sprop_send_echo
enum ada_err ada_sprop_send_echo(struct ada_sprop *sprop, u8 dest_mask)
Note: This function is available in version 1.7 or later.
This function sends the value of the property as an echo to the specified destinations. The send is an echo if for a to-device property (one with a set function).
The dest_mask parameter is a bitmask of destinations to be included, and can usually be ~0 (all bits set), which will echo the value to the cloud service (ADS) and all connected LANs. The source of the latest set is automatically excluded from the echo. If the cloud is not connected and the echo is going to the cloud, a flag is set to cause an echo to be sent when the cloud is next connected.
Note that when the auto-sync option is selected in the LAN settings portion of the device's template, echoing is handled by the agent, and the use of this API is unnecessary.
ada_sprop_send_echo_by_name
enum ada_err ada_sprop_send_echo_by_name(const char *name, u8 dests)
Note: This function is available in version 1.7 or later.
This function is similar to ada_sprop_send_echo() except that it uses the name of the property instead of its pointer.
ada_sprop_send_ack
enum ada_err ada_sprop_send_ack(struct ada_sprop *sprop, u8 status, int ack_message)
Note: This function is available in version 1.10 or later.
This function sends an acknowledgement for the last datapoint received for the property pointed to by sprop
. The status
should be 0 for success, 1 for error. ack_message
is an application-defined value that can indicate more, specifically the error or may be used for some other application-specific purpose.
Handling file and message properties
A file property or message property is used to upload or download datapoints of potentially large sizes. The library requires multiple packets to send or fetch binary data during a file transfer. In case of network connection loss, packets can be re-transmitted.
A file property is indicated by the type ATLV_LOC
.
A message property is identified by the type ATLV_MSG_UTF8
, ATLV_MSG_JSON
, or ATLV_MSG_BIN
. These designate the property as a string, JSON, or binary property, respectively.
For file and message properties, the void *val pointer must be a pointer to a struct file_dp. This is required to keep track of the current status of the ongoing transfer. There can only be one outstanding transfer active at a time. The file_dp structure is as follows:
struct file_dp {
enum file_dp_state state;
struct ada_sprop *sprop;
u32 next_off;
u32 tot_len;
size_t chunk_size;
u8 aborted;
void *val_buf;
char loc[PROP_LOC_LEN];
size_t (*file_get)(struct ada_sprop *sprop, size_t off,
void *buf, size_t len);
enum ada_err (*file_set)(struct ada_sprop *sprop, size_t off,
void *buf, size_t len, u8 eof);
void (*file_result)(struct ada_sprop *sprop, enum ada_err err);
struct prop_dp_meta *metadata;
u8 meta_count;
}
In this structure (above), file_dp_state
enumeration can have the following values:
enum file_dp_state {
FD_IDLE = 0, /* nothing to do */
FD_CREATE, /* send datapoint create */
FD_SEND, /* send datapoint value */
FD_RECV, /* request and receive datapoint value */
FD_FETCHED, /* send datapoint fetched opcode */
FD_ABORT, /* abort the datapoint operation */
}
The structure keeps a pointer to the sprop structure of the file property. The next_off bytes attribute keeps track of the progress of the file transfer. The tot_len
bytes attribute is used during file upload to know the file size to be uploaded. The chunk_size
bytes attribute records the maximum acceptable chunk sizes managed by the application. The default value is set to 512 bytes; for example, the library will send chunks of 512 bytes to the application during download, or the application will send chunks of 512 bytes to the library during upload. The aborted attribute indicates if the last file transfer for that file property was successfully aborted or not. The val_buf
pointer points to a buffer of chunk_size
bytes during file transfer. The loc attribute is used to save the current Ayla Cloud location that can be used to "GET" a remote URL where the file can be uploaded/downloaded.
The members metadata
and meta_count
are used internally for sending metadata with a file property.
The file_get
callback function should be defined for file or message uploads. The library uses this callback function to read chunks being uploaded. The file_set
callback function should be defined for file or message downloads. The library uses this callback function to send chunks of the file or message currently being downloaded to the application. The optional file_result
callback function will inform the host application when a file property has finished with or without error.
To set the appropriate file_get
and file_set
callback functions and the maximum allowed chunk_size
that can be handled by the application during file transfers, use the following API:
void ada_sprop_file_init_v2(struct file_dp *dp,
size_t (*file_get)(struct ada_sprop *sprop, size_t off,
void *buf, size_t len),
enum ada_err (*file_set)(struct ada_sprop *sprop, size_t off,
void *buf, size_t len, u8 eof),
void (*file_result)(struct ada_sprop *sprop, enum ada_err err),
size_t chunk_size
)
To allocate and initialize a struct file_dp structure for an sprop, use the following API:
enum ada_err ada_sprop_file_alloc(const char *prop_name,
size_t (*file_get)(struct ada_sprop *sprop, size_t off,
void *buf, size_t len),
enum ada_err (*file_set)(struct ada_sprop *sprop, size_t off,
void *buf, size_t len, u8 eof),
void (*file_result)(struct ada_sprop *sprop, enum ada_err err),
size_t chunk_size
)
To start a file upload, use the ada_sprop_file_start_send API. For this API, name is the name of the file property and len is the total size of the file to be transferred.
enum ada_err ada_sprop_file_start_send(const char *name, size_t len)
Internally, when this API is called, the simple property manager sends a datapoint create request (corresponds to the FD_CREATE state) for the file property. Ayla Cloud responds with a location, on which, a GET request is initiated to find the remote URL where the file can be uploaded. Once this happens, the simple property subsystem starts retrieving file chunks by calling the file property’s file_get callback and sends them to the remote URL (corresponds to the FD_SEND state). When the complete file is uploaded, a PUT is done on the Ayla Cloud location to mark the transfer complete. At this point, the library sends a PME_FILE_DONE event to indicate that the upload is complete.
To start a file download, the ada_sprop_file_start_recv API can be used. For this API, name is the name of the file property, buf is the pointer to the Ayla Cloud location that holds the remote URL for the file to be downloaded, len is the length of the buf and off is the offset from which the file needs to be downloaded.
enum ada_err ada_sprop_file_start_recv(const char *name, const void *buf, size_t len, u32 off)
Internally, when this API is called, the simple property manager first acquires the remote URL where the file is stored using Ayla Cloud location held by buf. Once this happens, the library starts downloading the file chunks (corresponds to the FD_REQ state). Simple property subsystem uses the file_set callback function to send the chunks to the application. The final chunk has the eof flag set. The library also indicates the completion of file download with a PME_FILE_DONE event. At this point of time, the file needs to be marked fetched by clearing the Ayla Cloud location. Currently, this is already taken care of by the simple property subsystem; however, the application can take charge of it by calling the ada_sprop_file_fetched API (corresponds to the FD_FETCHED state). For this API, name is the name of the file property.
enum ada_err ada_sprop_file_fetched(const char *name)
At any time, the file transfer can be aborted by using the following API. This API aborts any current ongoing file transaction.
void ada_sprop_file_abort(void)
This may be helpful in the case when other property updates from the Ayla Cloud or LAN client are received during file transfer. The library indicates this by sending a PME_NOTIFY event.
Available destinations
The simple property manager exports the variable extern u8 ada_sprop_dest_mask
indicating the currently available destinations. See Destination Mask.
Simple table example
This usage example of sprop implements one property, Blue_LED to control an LED.
#include <board.h>
#include <ada/libada.h>
#include <ada/sprop.h>
static u8 blue_led;
/*
* Demo set function for bool properties.
*/
static enum ada_err demo_led_set(struct ada_sprop *sprop, const void *buf, size_t len)
{
enum ada_err ret = ada_sprop_set_bool(sprop, buf, len);
if (!ret) {
set_led(blue_led);
}
return ret;
}
static struct ada_sprop demo_props[] = {
{"Blue_LED", ATLV_BOOL, &blue_led, sizeof(blue_led), ada_sprop_get_bool, demo_led_set},
};
/*
* Initialize property manager.
*/
void demo_init(void) {
ada_sprop_mgr_register("ledevb", demo_props, ARRAY_LEN(demo_props));
}
Note the following descriptions of the above example:
- The function demo_init() registers the demo_props table as a set of simple properties. The ARRAY_LEN() macro evaluates to 1, in this case, the number of entries in the table.
- The function demo_led_set() is called by the Ayla Integrated Agent whenever it receives a new value for the Blue_LED property. This calls ada_sprop_set_bool(), a libada function that handles any Boolean property, and puts its value in the location (blue_led) specified in the demo_props table entry. If that works, it then calls the set_led() function, which the application code provides to turn on or off the desired LED.
- To add a second LED property to this example, create another table entry naming the new property and using the same set function. To distinguish between the LEDs in demo_led_set(), compare the sprop->val pointer to & blue_led or the address of the added LED variable. Alternatively, use a string compare on sprop->name.
- The LEDs in this example are to-device (input) properties.
- For a from-device (output) property, the set() function pointer in the table can be NULL. The get() function may have to read a GPIO pin or just a variable that holds the latest state of the property to be sent.
- To send a property, the application calls ada_sprop_send_by_name(), which calls the get() function to get the value. The get() function can be called anytime that the Ayla Integrated Agent needs the property value, perhaps due to a request from a LAN client.
Primitive Interface
The Primitive Property Subsystem is the low-level interface to properties. This subsystem is very flexible, but requires the individual property manager to validate property names and determine how to handle the various events. The other property managers are built on top of this set of interfaces.
There can be several individual property managers. When performing a property-specific operation, for example, a set operation, the agent calls the appropriate handler in all registered property managers until one claims the property by returning success or an error other than AE_NOT_FOUND. This means that each property manager should check the name first and if it is not a property, the property manager handler returns AE_NOT_FOUND.
If a property manager wants to handle all properties, the property manager should be registered after any others. This might be useful when sending updates to an external processor without knowing which properties that external processor handles.
The remainder of this subsection provides descriptions of the most important aspects of the Primitive Property Subsystem.
Property structure
Declared in <ada/prop.h>
, the prop
structure defines a property:
struct prop {
const char *name;
u8 fmt_flags;
u8 echo:1;
u8 prop_mgr_echo:1;
u8 send_dest;
u8 is_dl;
enum ayla_tlv_type type;
void *val;
size_t len;
struct prop_dp_meta *dp_meta;
void (*prop_mgr_done)(struct prop *);
void *send_done_arg;
STAILQ_ENTRY(prop) list;
const struct prop_mgr *mgr;
struct prop_ack *ack;
};
Fields important to the interface include the following:
Variable | Description |
---|---|
name | This field contains the property name. |
val | This field points to the value of the property, for example, a string, 32-bit integer, or a byte, depending on the type. |
len | This field contains the maximum length of the value. For integers, this field is the actual length. For strings (UTF-8), this field contains the maximum length in bytes, including NUL termination. |
type | This field gives the type for the value. |
prop | This field provides a function to be called after the property manager is done with the property operation. |
ack | This field, if non-NULL, indicates that an acknowledgement is being sent for the property. |
Property mgr structure
Declared in <ada/prop_mgr.h>
, the prop_mgr structure defines a property manager:
struct prop_mgr {
const char *name;
enum ada_err (*prop_recv)(
const char *name,
enum ayla_tlv_type type,
const void *val,
size_t len,
size_t *offset,
u8 src,
void *cb_arg
);
enum ada_err (*prop_meta_recv)(const char *name,
enum ayla_tlv_type type,
const void *val, size_t len,
size_t *offset, u8 src, void *cb_arg,
const char *ack_id, struct prop_dp_meta *metadata);
void (*send_done)(enum prop_cb_status, u8 fail_mask, void *cb_arg);
enum ada_err (*get_val)(
const char *name,
enum ada_err (*get_cb)(
struct prop *,
void *arg,
enum ada_err
),
void *arg
);
void (*connect_status)(u8 mask);
void (*event)(enum prop_mgr_event, const void *arg);
};
Variable | Description |
---|---|
name | The member name points to the name of the property manager for use in debug messages. This member should be a short, readable string. |
prop_recv | The agent calls prop_recv in the property manager when a new value for a data point has arrived. In ada-1.10 or later, this function pointer will not be used if the property manager provides prop_meta_recv() . The name argument points to the name of the property. The type argument is the datapoint type as received from the ADS or the LAN client. The val argument points to the datapoint value in a format appropriate to the type. The len argument gives the size of the value in bytes. The offset argument is used only when handling file properties. This points to the offset of the data being received in bytes from the beginning of the value. The prop_recv handler must advance the pointed-to offset by the number of bytes actually used. Simple property managers do not need to use this at all. The source_mask is a destination mask indicating which destination originated the property update. The final argument, cb_arg , is an opaque callback argument. This is NULL except when the prop_recv call is in response to a request from the application to the cloud for a property using ada_prop_mgr_request . For now, the application can ignore this argument. In ADA-1.8 or later, if prop_recv() returns AE_BUF, further receive activity is paused until ada_prop_mgr_recv_done() is called by the property manager. |
prop_meta_recv | [ada-1.10 or later only] This is like prop_recv but has additional parameters for acknowledgements and metadata. This function pointer may be NULL to use prop_recv() for property managers that do not need to handle acknowledgements or metadata. The ack_id parameter, if non-NULL, is the acknowledgement ID to be used when acknowledging receipt of the property. The metadata parameter, if non-NULL, points to a structure containing metadata created when the datapoint was created on the cloud or mobile app. |
send_done | The agent calls send_done when a request to send a property has been completed. On normal completion, the status is PROP_CB_DONE. The argument fail_mask is set to a mask of destinations to which the datapoint could not be delivered. The argument cb_arg has the value passed as cb_arg to ada_prop_mgr_send . |
get_val | The agent calls get_val to get the value of a particular property, and send it to ADS. Upon completion, the agent calls the get_cb callback function. |
connect_status | The agent calls connect_status whenever the connection to the service or a LAN client is established or broken. The connect_status handler is optional and may be NULL. The mask argument is the new value of the available destination mask. |
event | The agent calls event to indicate the occurrence of certain events that may be of interest to property managers. The event handler is optional and may be NULL. The event argument gives the event type, which may be one of the following:
|
Property metadata structure
#define PROP_DPMETA_KEY_LEN 16 /* max len of dp metadata key */
#define PROP_DPMETA_VAL_LEN 32 /* max len of dp metadata value */
#define PROP_MAX_DPMETA 4 /* max num of dp metadata key-value pairs */
struct prop_dp_meta {
char key[PROP_DPMETA_KEY_LEN + 1];
char value[PROP_DPMETA_VAL_LEN + 1];
};
This structure usually exists in an array of up to PROP_MAX_DPMETA (4) elements. When the key is set to the empty string, the element is invalid and the array is terminated. The key and value strings are application-defined strings passed as metadata with datapoints.
LAN session structure
This structure is needed only for certain security-related host software. It is included here for completeness since it is part of the external interface provided by ADA. A pointer to this structure is passed as the argument to the event handler for the PME_LAN_STATE_CHANGE
event.
enum ada_lan_state {
ALS_INACTIVE = 0,
ALS_ACTIVE,
ALS_KEY_EXCH_FAILED,
};
struct ada_lan_session {
u8 index;
enum ada_lan_state state;
u32 remote_ipv4_addr;
u16 remote_port;
};
The index is the 1-based index of the LAN session, i.e., 1 or 2. The state enum indicates whether the LAN session is now inactive, active, or has become inactive due to a key-exchange failure. The fields remote_ipv4_addr
and remote_port
give the LAN session remote IP address and port.
Primitive functions
ada_prop_mgr_register
void ada_prop_mgr_register(const struct ada_prop_mgr *);
This function adds the given property manager to an internal list of property managers. The call also declares that the property manager may handle some set of properties and should be called for property operations as defined in the struct ada_prop_mgr. The application should eventually call ada_prop_mgr_ready()
as well.
ada_prop_mgr_ready
void ada_prop_mgr_ready(const struct ada_prop_mgr *);
This function indicates to Ayla Integrated Agent that the property manager is ready to receive property values. This must be called after ada_prop_mgr_register() and after each time the ADS destination becomes available. ada_prop_mgr_ready() can be called by the connect_status() handler and may be safely called more often than necessary.
The Ayla Integrated Agent does not fetch commands or property values from the ADS until all property managers have indicated their readiness by calling ada_prop_mgr_ready(). If properties have changed while connectivity to the ADS was lost, it is desirable to send the new values to the service before calling ada_prop_mgr_ready() so that the datapoint values from the device have precedence over any datapoints that may have been created on the ADS during the connectivity loss.
ada_prop_mgr_recv_done
void ada_prop_mgr_recv_done(u8 src_mask);
This function indicates to the agent that a property receive is complete. The src_mask
argument, present in ADA-1.9 or later only, indicates which source (e.g., ADS or LAN) was being used. When the property manager's prop_recv()
or prop_meta_recv()
function is called and returns AE_BUF, the agent stops receiving additional properties until this function is called, as a way of flow-controlling the receive path. [ADA-1.8 or later only]
ada_prop_mgr_request
enum ada_err ada_prop_mgr_request(const char *name);
This function requests that the Ayla Integrated Agent fetch the value of a property from the ADS. The name argument specifies the property name. If name is NULL, a request for all to-device (input) properties is made.
Upon success (which is indicated by 0 return value), this starts the request which completes, if possible, sometime later. Failures are not reflected back to the application, and may include loss of connectivity or a property name that is not in the device template of the ADS.
ada_prop_mgr_request_get
enum ada_err ada_prop_mgr_request_get(const char *name, void (*callback)(struct prop *prop));
This function operates just like ada_prop_mgr_request() with the addition that the caller provides a callback function which will be called on completion of the request. If the name is NULL, the callback function will be called for each to-device property in the response. [ADA-1.8 or later only]
In the callback, the provided property structure can be used to access the value, after which the callback must free the property structure.
Upon success (which is indicated by 0 return value), ada_prop_mgr_request_get() starts the request which completes, if possible, sometime later. Failures are not reflected back to the application, and may include loss of connectivity or a property name that is not in the device template of the ADS.
ada_prop_mgr_send
enum ada_err ada_prop_mgr_send(const struct prop_mgr *mgr, struct prop *prop, u8 dests, void *cb_arg);
This call initiates a request to send a property to the specified destinations. The first argument is the property manager that handles the property. The second arg is the prop structure, which must be available until (*send_cb)() is called, indicating that the value has been completely sent.
The dests argument indicates which destinations should receive the property. The opaque cb_arg pointer is provided to the send_cb() callback. Upon success, this function returns AE_IN_PROGRESS, indicating the operation was started successfully. If the dests mask is 0, it returns AE_INVAL_STATE.
ada_prop_mgr_get
enum ada_err ada_prop_mgr_get(
const char name,
enum ada_err (*get_cb)(struct prop *prop,
void *arg, enum ada_err), void *cb_arg
);
This function requests that a property is fetched from the ADS. The supplied get_cb() function is called on completion. The name argument gives the property name. The cb_arg argument is passed as arg to the get_cb() function.
ada_prop_mgr_set
enum ada_err ada_prop_mgr_set(
const char *name,
enum ayla_tlv_type type,
const void *val,
size_t val_len,
size_t *offset,
u8 src,
void *set_arg
);
This function requests that a property on the device is set to the value specified. This is usually used only internally in Ayla Integrated Agent, but may be useful for a property manager to set a property handled by another property manager. This function calls the prop_recv()
or prop_meta_recv()
handler in the appropriate property manager. See the prop_mgr prop_recv()
function for a description of the arguments.
ada_prop_mgr_meta_set
enum ada_err ada_prop_mgr_set(
const char *name,
enum ayla_tlv_type type,
const void *val,
size_t val_len,
size_t *offset,
u8 src,
void *set_arg,
const char *ack_id,
struct prop_dp_meta *metadata
);
This function requests that a property on the device is set to the value specified. This is usually used only internally in Ayla Integrated Agent, but may be useful for a property manager to set a property handled by another property manager. This function calls the prop_recv()
or prop_meta_recv()
handler in the appropriate property manager. See the prop_mgr prop_recv_meta()
function for a description of the arguments. This is for ada-1.10 or later.
ada_prop_mgr_dp_put
enum ada_err ada_prop_mgr_dp_put(
const struct prop_mgr *pm,
struct prop *prop,
u32 off,
size_t tot_len,
u8 eof,
void *cb_arg
);
This function is used to queue a file chunk to be uploaded to the remote URL received from the Ayla Cloud location. tot_len is used when off is 0. The last chunk should have eof set to 1. cb_arg is sent back to the prop_send_done callback.
ada_prop_mgr_dp_get
enum ada_err ada_prop_mgr_dp_get(
const struct prop_mgr *pm,
struct prop *prop,
const char *location,
u32 off,
size_t max_chunk_size,
enum ada_err (*prop_mgr_dp_process_cb)(
const char *location,
u32 off,
void *buf,
size_t len,
u8 eof),
void *cb_arg
);
This function is used to queue the initiation of a download of a file property. The function retrieves the remote URL where the file resides, using the Ayla Cloud location. off to indicate the offset from which the file is to be downloaded. max_chunk_size indicates the maximum allowed chunk size that can be processed by the application at a time. Invoking the prop_mgr_dp_process_cb callback function processes these chunks. cb_arg is sent back to the prop_send_done callback function.
ada_prop_mgr_dp_fetched
enum ada_err ada_prop_mgr_dp_fetched(
const struct prop_mgr *pm,
struct prop *prop,
const char *location,
void *cb_arg
);
This function is used to mark the downloaded file property fetched, and clearing the Ayla Cloud location. cb_arg is sent back to the prop_send_done callback function.
ada_prop_mgr_dp_abort
void ada_prop_mgr_dp_abort(const char *location);
This function is used to abort the ongoing file upload/download at the Ayla Cloud location.
Batch Datapoints
A batch is a buffer for accumulating datapoints. Batch datapoints are used to reduce the network flow and traffic. Once the batch is created, the datapoints can be put in a batch-buffer. When the batch-buffer is full, the device application can send the batch datapoints to the Ayla Cloud in a single transmission. Following are some important points about this batch datapoint feature:
When creating a batch, the maximum datapoints allowed and maximum data size in memory are specified by the device application.
A batch can be multi-instance. For example, a device application can create two or more batches, add data properties to them independently, and send the batches at different times.
No auto-sending is provided for a batch. Even when the batch-buffer is full or its age is expired, the device application is responsible for sending a batch. A prop_mgr can send both individual properties and batch properties.
Batch functions
ada_batch_create
struct batch_ctx *ada_batch_create(u16 max_dps, u16 max_size);
This function is used to create a batch buffer. The maximum datapoints and maximum total data size should be specified. Upon failure, the function returns NULL. Upon success, the function returns a handle. With this handle, the device app can add datapoints to the batch and send all datapoints in the batch to the cloud.
ada_batch_destroy
void ada_batch_destroy(struct batch_ctx *ctx);
This function is used to free memory of all datapoints in the specified batch, as well as the batch-buffer. After this call, the batch handle becomes invalid.
ada_batch_add_prop_by_name
int ada_batch_add_prop_by_name(struct batch_ctx *ctx, const char *prop_name, s64 time_stamp);
This function is used to add a property to the batch by the property name. The batch handle is specified by ctx, the property name is specified by prop_name. If the argument time_stamp is not zero, the time stamp (in UTC milliseconds since 1970) is specified by the app for the datapoint. If time_stamp is zero, the batch-manager will use the current time (UTC) for the data point’s time stamp. On success, the function’s return value is a positive batch-ID. On error, the return value is negative or 0. A special return value of AE_BUF means that the datapoints in the batch reached the maximum datapoints limit, or the data size of all datapoints in the batch reached the maximum data-size limit.
ada_batch_add_prop
int ada_batch_add_prop(struct batch_ctx *ctx, struct ada_sprop *sprop, s64 t_stamp);
This function is almost as same as ada_batch_add_prop_by_name(). The function can be used to add a property to a batch-buffer. But the property is specified by a pointer to struct ada_sprop, not by property name.
ada_batch_send
enum ada_err ada_batch_send(struct batch_ctx *ctx, void (*done_cb)(size_t size_sent));
This function is used to send a specified batch to the cloud. The batch is queued by prop_mgr, and will be sent later. After the batch is sent, the memory of all associated datapoints is freed. The batch handle ctx remains valid after the call. If the parameter done_cb is not null, it will be invoked after the batch is sent. The argument size_sent of the callback provides the sent payload size. On success, the function’s return value is zero.
ada_batch_set_err_cb
void ada_batch_set_err_cb(void (*err_cb)(int batch_id, int err));
The batch-manager has a default error callback, which simply prints the status for each datapoint sent. This function can be used to override the default error-callback. The first argument of the err_cb() is the datapoint’s batch-ID. The second argument is the error code (positive is HTTP status code, and negative is enum ada_err).
ada_batch_discard
void ada_batch_discard(struct batch_ctx *ctx);
This function is used to discard all datapoints in the batch. The memory of discarded datapoints is freed. After the call, the batch’s handle ctx is still valid.
Batch best practices
To use a batch in the device app, you need to determine the maximum batch datapoints and the maximum total batch data size in bytes for the batch according to your platform. First, call ada_batch_create() to create a batch buffer. With the returned handle, you can add properties to the batch-buffer by calling ada_batch_add_prop_by_name() or ada_batch_add_prop(). In the process of adding datapoints to batch, when an error of AE_BUF (buffer buff) occurs, you may call ada_batch_send() to send the batch to the cloud.
All batch operations should be performed after your device is connected to Ayla Device Service (ADS) server. Perform the batch-related initialization after the flag (ada_sprop_dest_mask and NODES_ADS) is changed from 0 to non-zero. The batch operation should be stopped when the flag is changed from non-zero to 0.
The device application may need to trace the datapoint sending status. In this case, the app should call ada_batch_set_err_cb() to set an error callback. When adding each datapoint to the batch-buffer, save the returned batch-id. When the error callback is invoked, check each data-point’s status.
Following is an example of a batch test program. The test app runs in a loop:
add data-point to batch;
send the batch when it is full;
In some cases, the memory in the device system will be totally consumed (fortunately in PDA’s prop_mgr, only 20 properties can be stored in the sending queue). To solve this problem, call ada_batch_send() with a parameter done_cb in which you may count all batches having been actually sent and put datapoints into the batch buffer again after a specified number of batches has been sent.
Schedules
Each schedule is like a property and its value is a list of time-based conditions and actions to take when those conditions are met. These conditions and actions are expressed as binary TLV items.
The names of the schedules have the same restrictions as property names and must not be the same as any other property name. The schedule feature is implemented on top of the primitive property manager subsystem.
In order for schedules to operate, the platform must have the current UTC time and, for schedules using local time, the local time offset and daylight savings information. These are provided by the Ayla Device Service, but must be maintained by the platform during restarts or power-downs using a hardware RTC or non-volatile memory. Also kept in NVRAM is the time that the schedules were last evaluated to save some work at startup. APIs for these purposes must be provided by the platform.
Schedule functions
ada_sched_init
enum ada_err ada_sched_init(unsigned int count)
This interface initializes the schedule system. The return is 0 on success. This interface initializes the schedule system and sets the number of schedules to count and allocates resources accordingly. The application is responsible for persisting schedules in the configuration and restoring the names and values at startup.
enum ada_err ada_sched_dynamic_init(unsigned int count)
This interface initializes the schedule system to use dynamically-named schedules. The return is 0 on success. The interface sets the number of schedules to count and allocates resources accordingly. With this initialization, the schedule names are provided by the cloud and the agent keeps only the active schedules. The application is responsible for persisting schedules in the configuration and restoring the names and values at startup. [ADA-1.9 and later only]
ada_sched_set_name
enum ada_err ada_sched_set_name(unsigned int index, const char *name)
This interface sets the name of the schedule specified by index, which must be less than the count supplied to ada_sched_init()
or ada_sched_dynamic_init()
. The supplied name must be a valid property name.
ada_sched_get_index
enum ada_err ada_sched_get_index(unsigned int index, char **name, const void *buf, size_t *lenp)
This interface gets the name and the binary value of the schedule specified by index in the supplied buffer. This may be used when persisting the schedule value to persistent storage. The name argument points to a string pointer that is set by the function. The buf argument points to the buffer to receive the value. The lenp argument points to a length variable that is initially set to the size of the buffer. On success, this function sets the length variable to the length stored and returns 0.
ada_sched_set
enum ada_err ada_sched_set(const char *name, const void *buf, size_t len)
This interface sets the value of the schedule specified by name to the contents of the supplied buffer.
ada_sched_set_index
enum ada_err ada_sched_set_index(unsigned int index, const void *buf, size_t len)
This interface sets the value of the schedule specified by index from the contents of the supplied buffer. This is used when loading the schedule values from persistent storage shortly after booting.
ada_sched_enable
enum ada_err ada_sched_enable(void)
This interface enables the scheduling system, and should be called after all schedules have had their names set and saved values restored.
Logging
Provided that the targeted SDK has a console logging subsystem (a serial console), the Ayla Integrated Agent messages are sent to that log. The Ayla Integrated Agent log messages are classified by subsystem and severity. These are defined in <ayla/log.h>
and <ayla/mod_log.h>
.
Subsystems of interest include mod, client, server, log-client, ssl, mqtt, test, and sched. Others are defined, but not used by the Ayla Integrated Agent. The subsystem mod is the default for module logging and is sometimes used by the host application or platform code; almost all agent log messages are classified as belonging to one of the other subsystems.
The severities used are info, warn, err, debug, and debug2. The info, warn, and err messages are enabled by default. Debug gives more details about activity; much of it is of interest only to developers and is cryptic. Debug2 gives even more information, including network I/O packets in some cases. Other severities are defined, but not used by the Ayla Integrated Agent.
Following is an example of log messages:
[ada] 2015-08-19T17:32:08.500 i m client: ADA-DEMO 1.0-eng
[ada] 17:32:15.739 d m client: http_client_idle_close: session 0
[ada] 17:32:15.744 i m client: ada_client_up: IP 172.16.11.79
[ada] 17:32:15.753 i c client: get DSN AC000W000432408
The fields prior to the log message are as follows:
- [ada] is the prefix for all Ayla Integrated Agent log messages and may be present or omitted depending on the platform. It is omitted in ADA-3.0 and later.
- Following the [ada] is the UTC time in seconds and milliseconds. When the hour is different from the previous message, or on the first log message, the date is shown as well.
- The UTC time is followed by a single character that indicates the severity level of the message, i for info, d for debug or debug2, W for warning, E for error. On multi-threaded platforms this is followed by a short, usually single-letter, indication of the thread that is issuing the log message, that is, m for main thread, c for client.
- The severity level is followed by the subsystem name (for example, client) and then the text of the message. Often, but not always, the text begins with the function name or a portion of it.
Most messages from CLI commands are not considered log messages and do not have this format. The application may want to generate log messages in the Ayla Integrated Agent format. These should be made under the mod subsystem.
Logging functions
log_info
void log_info(const char *format, …)
This is a printf-like interface that logs an info message under the default (mod) subsystem. This is currently implemented as a macro.
log_put
void log_put(const char *format, …)
This interface can be used to log a message of any severity on the default (mod) subsystem. The beginning of the format string is a severity designation. These strings are defined as LOG_INFO, LOG_WARN, LOG_ERR, LOG_DEBUG, or LOG_DEBUG2, and one of these is usually prepended to the format string. For example:
log_put(LOG_WARN "%s: error code %d", __func__, err);
Notice the use of string concatenation (no comma after LOG_WARN) to prepend the severity designator.
ada_cons_log_redir
void (*ada_cons_log_redir)(const char *str)
Note: This interface is available on version 1.7 or later.
This function pointer may be set by the host application to handle printing of logs.
The function used is usually log_print()
. The str
argument points to the entire log message, without the "[ada]" prefix and without a newline. This allows the host application to leave off the "[ada]" prefix or store log messages, or to discard them if desired.
OTA Updates
The OTA feature lets the module accept firmware changes from the Ayla Device Service (ADS). The Ayla Integrated Agent uses the "host" OTA type, in which the OEM provides the firmware image. The use of this feature is entirely optional; it is extremely useful, however, and highly recommended. Also, the platform may or may not provide this OTA capability.
Best Practices
- Ensure that all Devices configured with a specific Device Model run compatible firmware so that there's backward compatibility.
- Deploying an incompatible image to a device may render it inoperable.
- Follow best practices to implement OTA feature so that in case of a failure, it never damages the devices permanently.
The platform or application code can provide a different OTA handler for each of the two types of OTAs. The OTA handler provides a set of functions that handle the download and installation of the new firmware.
The ADS and the Ayla Integrated Agent do not place any special requirements on the contents of the OTA firmware image. The OTA image is entirely up to the developer of the platform.
The OTA feature may be used to update separate components within a system using the optional "label" field to identify the subsystem, such as a separate MCU, the OTA image applies to. In this way, the subsystem component's image can be managed and updated separate from the main application image.
You can start an OTA operation by deploying the image from the Ayla Developers Portal, the Ayla Customer Dashboard, or the Ayla IoT Command Center. Once the agent is notified that an OTA image is going to be deployed, the agent indicates this by calling the OTA handler’s notify() function. If the image is desired, the handler calls ada_ota_start() to prompt the agent to download the image. The agent provides the image to the handler by calling the save() function for each part of the image as it arrives. After all of the parts have been given to the handler in sequence, the agent calls the handler’s save_done() function.
Once the handler has applied the OTA and rebooted to the new firmware image, the handler reports the completion of the operation by calling ada_ota_report().
Only one OTA can be in process at a time. Therefore, for multi-component OTAs the device must fully process each component OTA before it will receive notification for subsequent pending OTAs.
patch_state
This enum provides status codes for OTA updates and is the return value for several operations. The ADS understands the codes, and existing assignments must not change. The codes are defined in <ayla/patch_state.h>
. Only some of the values apply to the OTA update platform code. Others are used internally. The following table describes the codes that may be used by the platform.
Code | Description |
---|---|
PB_ERR_NEW_CRC | The modified code results in a CRC error. |
PB_ERR_STALL | This is not an error. It is an indication that the download should pause until ada_ota_continue() is called. This code may be used for temporary resource shortages or a delay imposed by an external system. |
PB_ERR_DECOMP | The update had failed to decompress. |
PB_ERR_OP_LEN | A patch had a length error. |
PB_ERR_FATAL | An unspecified error occurred while applying the update. |
PB_ERR_OP | A segment of the update had an invalid opcode. |
PB_ERR_STATE | The patch program was in an invalid state. |
PB_ERR_CRC | A section of the image being patched had a bad CRC before patching. |
PB_ERR_COPIES | More than one block is in the copied state. |
PB_ERR_HEAD | The patch file area in flash had a bad structure or length. |
PB_ERR_FILE_CRC | The update file had a CRC error. |
PB_ERR_ERASE | Persistent storage erasure failed. |
PB_ERR_WRITE | Persistent storage write failed. |
PB_ERR_SCRATCH_SIZE | The scratch area was too short. |
PB_ERR_DIFF_BLKS | The old and new data were in different storage blocks. |
PB_ERR_OLD_BLKS | The old data for a section of the patch spanned two blocks. |
PB_ERR_NEW_BLKS | The new data for a section of the patch spanned two blocks. |
PB_ERR_SCR_ERASE | The scratch block erasure failed. |
PB_ERR_SCR_WRITE | The scratch block write failed. |
PB_ERR_PROG | An error occurred reading or writing a progress byte. |
PB_ERR_PROT | A block to be patched was not in the correct progress state. |
PB_ERR_NOFILE | The patch file was not found in persistent storage. |
PB_ERR_HEAD | The patch file could not be read. |
PB_ERR_NO_PROG | The update area did not contain a progress area. |
PB_ERR_INV_PROG | The progress area was invalid. |
PB_ERR_READ | The patch file could not be read. |
PB_ERR_DECOMP_INIT | A failure occurred in initializing the decompressor. |
PB_ERR_PREV | A previous patch attempt failed. |
PB_ERR_OPEN | An error occurred opening the persistent storage device. |
PB_ERR_BOOT | The update program did not boot. |
ada_ota_ops
Declared in <ada/client_ota.h>
, this structure contains pointers to the functions implementing the OTA handler. The structure is filled in by the handler code and passed to ada_ota_register
. This structure must remain valid for the duration of the client operations.
struct ada_ota_ops {
enum patch_state (*notify)(const struct ada_ota_info *ota_info);
enum patch_state (*save)(unsigned int offset, const void *, size_t);
void (*save_done)(void);
void (*status_clear)(void);
};
Variable | Description |
---|---|
notify | This function indicates that an OTA is available. |
save | This function gives the handler a section of the OTA update to save in persistent storage. |
save_done | This function indicates completion of the OTA download such that subsequent processing should be completed, such as activating the image and rebooting. |
status_clear | This optional function handles the completion of the OTA update. |
ada_ota_info
Declared in <ada/client_ota.h>
, this structure provides information about the OTA image when the notify function is called.
struct ada_ota_info {
enum ada_ota_type type;
u32 length;
const char *label;
const char *version;
};
Variable | Description |
---|---|
type | The type of OTA. For integrated agent OEM applications, the value will always be OTA_HOST. OTA_MODULE is only used for Ayla production agents and applications and is not applicable to OEM applications. |
length | The length of the OTA image file in bytes. |
label | An optional character string label that may be used to identify a subsystem or component the OTA image applies to. The value will be NULL if the label is not provided. |
version | The version character string for this OTA image. |
OTA functions
ada_ota_register
void ada_ota_register(enum ada_ota_type type, const struct ada_ota_ops *ops)
This function registers a set of handlers for the specified type of OTA (either OTA_MODULE or OTA_HOST). To unregister a handler, the second argument may be NULL. This function always succeeds or results in an assertion failure if an unrecognized type is given. The ops structure is described next, struct ada_ota_ops.
ada_ota_start
ada_ota_start(void)
The handler calls the ada_ota_start function to start an OTA download. This function can be called from the handler’s notify() function or later.
ada_ota_continue
void ada_ota_continue(void)
The handler calls this function to continue a download after a call to save() returned PB_ERR_STALL.
ada_ota_report
void ada_ota_report(enum patch_state status)
The handler calls this report to the status of an OTA image after it has been completely downloaded. This may be called during the save_done() function or at a later time, even in the new image after it has booted.
ada_ota_fetch_len_set
void ada_ota_fetch_len_set(size_t len)
This function can be called at any time to set the number of bytes to be fetched from the cloud in each API call. The default is 4096 bytes. Larger values may be more efficient and provide a faster OTA, but at the risk of reducing responsiveness to property updates that might occur during the download. The fetch length used will be between 2040 bytes and 256 KB (262,144 bytes). If len
is less than 2040, then 2040 is used. If greater than 256 KB, then 256 KB is used. Note that the number of bytes supplied to the save handler will generally be much lower.
OTA best practices
The primary concern around OTA updates is to make sure that the module continues to run either the old image or the new image after the update, even if power is lost at some point during the OTA update.
The SDK may have an OTA mechanism that can be used, including a boot loader that allows installing a new image or two images in flash that can be alternately booted.
Be sure to preserve the configuration during an OTA, and allow for compatibility between old configurations and new firmware images. Some configuration items expected by new images may not be present in the old configuration. These should have reasonable default values.
Often Wi-Fi firmware has to be updated at the same time as an SDK change. Therefore, you may want to combine the Wi-Fi firmware with the rest of the firmware so that they can be updated together.
Agent Auto Recovery / Health Check
As a way of making sure the device is always recoverable from unforeseen conditions, the agent tries to get commands from the cloud service at least every twelve hours or so. If it cannot get commands after a day, the client is deemed unhealthy and the system is reset by a watchdog timer on most platforms. The platform must be configured to do this and the client health functionality is disabled by default but normally would be enabled by the host application as soon as the Wi-Fi interface is started or provisioned.
The host app only needs to enable this, and may need to configure the watchdog system in the build, but the rest of the behavior is taken care of by the agent.
These interfaces control the client health check. This facility is available only in ADA-1.7 or later.
Health Check Functions
ada_client_health_check
int ada_client_health_check(void)
Returns non-zero if the agent has been unable to contact the service for one day (or shorter in case of the test mode) or if it has detected any other problems that might warrant a reset. When the client health check is disabled, this always returns 0.
ada_client_health_check_en
void ada_client_health_check_en(void)
Enable the client health check. This is normally called when the network comes up.
ada_client_health_check_dis
void ada_client_health_check_dis(void)
Disable the client health check.
ada_client_health_test_mode
void ada_client_health_test_mode(u8 enable)
This function enables or disables a test mode for the health test. When this is called with a non-zero argument, the test mode is enabled and the time interval for get commands is set to 2 minutes, with some randomness applied, and agent is considered unhealthy after 4 minutes of being unable to get commands. When this has not been called, or is called with a zero argument, the GET commands interval is 12 hours, with some randomness applied, and the agent is considered unhealthy after 24 hours of being unable to get commadns.
Command Line Interfaces
The Command Line Interface (CLI) is optional and is under the full control of the host application. The host application may implement console input and parse arguments and pass them to some of the CLI functions provided. The platform's SDK may provide a console, some functionality, and its own CLI as well.
All of the integrated agent CLI functions take as an argument the number of command line arguments and a pointer to an array of the command line argument pointers. They return void. The first argument is always the command name.
CLI functions
ada_client_cli
void ada_client_cli(int argc, char **argv)
This CLI can be used for debugging, to show some internal state of the agent. Available in ADA-1.9 or later only.
conf_cli
void conf_cli(int argc, char **argv)
This CLI allows access to the agent's configuration. Without arguments, or invoked as "conf show", it shows the factory, startup, and running configuration. To show a subset of the configuration, use "conf show ". The usage "conf set " allows setting factory configuration items, but only in setup_mode.
This API is available only in ADA-3.0 or later.
ada_conf_oem_cli
void ada_conf_oem_cli(int argc, char **argv)
This CLI can be used to show the OEM settings and to set the OEM ID, OEM model, and OEM key. The OEM ID and OEM model values entered by the CLI override the compiled-in defaults. Following are a few other important factors:
- The ada_conf_oem_cli interface should be used for manufacturing setup only and may be left out of end-user applications.
- Without arguments, the current settings are shown.
- With one argument, the OEM ID is set.
- If the command is given as oem model , the OEM model is set.
- With the command syntax oem key [], the OEM key is set.
- The optional OEM-model entered with the key restricts the device to just that OEM model; unless, the key is re-entered. If omitted, the current OEM model setting is used. The oem_model can be entered as * (a single asterisk) to allow the OEM model to be set to anything by the application later.
ada_log_cli
void ada_log_cli(int argc, char **argv)
This CLI can be used to show and modify logging settings. The help string extern char ada_log_cli_help[];
may be used if desired to show the usage of the command.
The CLI syntax is log [--mod <subsystem>] [<level>…]
.
The available subsystems are client, conf, dnss, mod, notify, server, wifi, ssl, log-client, io, sched, eth, and test. If no subsystem is specified, the command affects all subsystems (modules). Without a level argument, the current logging levels are shown. The available logging levels are info, warning, error, debug, debug2, metric, pass, fail, none, and all. If a logging level is preceded with a hyphen, for example -debug, then that level is turned off. Some levels (such as warning and error) cannot be turned off. Some information messages may occur even when info messages are disabled.
ayla_time_cli
void ada_time_cli(int argc, char **argv)
This CLI can show or set the time. This interface is available in ADA-3.0 or later
ada_client_command_func_register
void ada_client_command_func_register(void (*func)(const char *command))
Note: This interface is available on version 1.7 and later.
This function registers a handler function func
which will be called whenever the agent receives a CLI command from the cloud. This may be useful in debugging issues that occur in the field or in modifying the configuration of devices. The operation is used sparingly and only in exceptional circumstances.
After the handler is registered, if a CLI command is received from the cloud, the func
function is called with a string argument. The string argument contains the command and all of its arguments. The command results should be output via the printcli() function so that they can be redirected to the device logs rather than being sent to the serial console.
Required App Interfaces
The host application must implement the following subsystems for use by the Ayla Integrated Agent.
- Configuration subsystem (for versions before ADA-3.0)
- Wi-Fi subsystem
These interfaces provide for configuration and logging, among other platform-specific tasks.
Configuration subsystem
Note that this is implemented by the agent and its platform code in ADA-3.0 and later versions. In those versions the host application must have a way to initialize the configuration, e.g., using the (conf_cli) API. Some configuration items can be compiled in or set using (struct-ada_conf) the below given configuration items.
The platform must provide a method for persistently saving certain configuration items. The device must maintain a unique ID and key that are used to authenticate the device in the Ayla Device Service (ADS). In addition, the ADS sends configuration items, such as the time zone, daylight-savings information, the LAN key, and various modes. These must be persisted and kept across reboot and power-cycles in order for the device to work without internet connectivity. This section provides the required and persisting configuration items.
id/dev_id
This string is the Ayla Device Serial Number (DSN). It is usually a 15-character value, like AC000W123456789. The device OEM must set this. The DSN can be stored in a one-time-programming area if practical. This is the primary identifier for the device within the ADS.
id/pub_key
This is the 2048-bit RSA public key corresponding to the DSN. The device OEM must set this. This value can be saved in binary or as a base-64 text string.
struct ada_conf
Declared in <ada/ada_conf.h>
, the ada_conf structure is a global structure that collects several configuration items which control the Ayla Integrated Agent and in particular the client portion of the agent:
struct ada_conf {
u8 enable:1;
u8 get_all:1;
u8 lan_disable:1;
u8 test_connect:1;
u8 conf_serv_override;
const char *region;
char conf_server[CONF_ADS_HOST_MAX];
u16 conf_port;
u16 poll_interval;
const u8 *mac_addr;
const char *hw_id;
char host_symname[CLIENT_CONF_SYMNAME_LEN];
char reg_token[CLIENT_CONF_REG_TOK_LEN];
u8 reg_user;
enum client_event event_mask;
};
There are two sections of fields in the ada_conf structure. The first part is composed of fields that are set by the platform application before starting the client. The second part contains items that may be of interest to the platform application. Following are descriptions of the fields in the ada_conf structure:
Variable | Description |
---|---|
enable | This bit is normally set to 1 during initialization by the application startup. In some situations, you may want to control this bit with a CLI. |
get_all | This bit, if set, indicates that the client should fetch all input (to-device) properties upon the first connection to the ADS. This is often desirable, but some applications may want the chance to send properties that the application altered due to LAN mode devices or persisted due to scheduled changes before fetching the latest ADS. |
test_connect | This bit, if set, indicates to the ADS that the connection is for testing at the factory and doesn’t indicate the first end-user connection by the device. Test software may want a way to set this temporarily, but it shouldn’t be persisted. |
conf_serv_override | This byte, if non-zero, indicates that the client should use the default ADS server name rather than the name that would be implied by the combination of the oem_model and OEM. This does not override the region and can be generally ignored by the application. This byte can also be set through the cloud and persisted in the configuration. |
mac_addr | The platform must set this field to a unique MAC address for the device before starting the client. |
hw_id | The platform should set this field to a unique hardware ID string of ASCII characters (usually hex digits and dashes). The string should remain valid while the Ayla Integrated Agent is active. This field is sent to the ADS and available as information on the Ayla OEM Dashboard. The field may be used to verify the uniqueness of the DSN assignments. |
region | The two-letter region string indicates the part of the world where the device will operate. This determines the DNS domain that is used for the ADS server. Use CN for People’s Republic of China, or US (or NULL) for the rest of the world. |
conf_server | This field can be used to configure the entire ADS server host name, for example, to connect to a test server. Generally it is left NULL. |
conf_port | This field can be used to set the port used by the server, for example, to connect to a test server. Generally, the field is set to 0. |
poll_interval | This field is the number of seconds between polls of the ADS by the ADA client at times when ANS is not reachable. The field is normally is set to 30 seconds by the application. If set to 0, the client currently uses 5 minutes as the default. NOTE: This field is not used in ADA-3.0 or later. |
host_symname | This string is received from the cloud and filled in by the Ayla Integrated Agent. The string holds the symbolic name that comes from the template originally and may be set by the user. |
reg_token | This string is set by the Ayla Integrated Agent and holds the registration token that must be entered by the user to register the device. In the Display Method of registration, the application would need a way to display this string. |
reg_user | This bit is set by the Ayla Integrated Agent when the device is registered to a user. |
u8 oem[]
This is usually set by the "oem" CLI (ada_conf_oem_cli), but may be compiled in as well. It can be set by the host application to the OEM ID assigned by Ayla. This is typically an 8-character ASCII string composed of hex digits.
u8 oem_model[]
This is usually set by the "oem" CLI (ada_conf_oem_cli), but may be compiled in as well. It indicates the template to be used as well as part of the host name for the service. The OEM key is used to authenticate the OEM and OEM model.
oem_set_key
enum conf_error oem_set_key(const char *key, size_t key_len, const char *model)
This function sets the encrypted OEM secret that is used to authenticate the device made by the OEM. The key
argument is the OEM secret string and the length
is the string length of the key. The model
argument is the OEM model that is encrypted as part of the authentication string. The resulting encrypted value is saved in configuration and used by the agent when connecting. This would normally be used during manufacturing as an alternative to using the "oem" CLI (ada_conf_oem_cli).
adap_conf_sw_build()
const char *adap_conf_sw_build(void)
This function should return the version string to report to the ADS as the module software version. Among other things, this is used to as information to filter the available module OTA images. The function can return ada_version_build, the supplied build info for the Ayla Integrated Agent.
adap_conf_sw_version()
const char *adap_conf_sw_version(void)
This function should return the module version string to report to LAN clients. The function can return ada_version, the supplied version of the Ayla Integrated Agent.
adap_conf_reg_changed()
void adap_conf_reg_changed(void)
This function must be provided by the application. The function is called when ada_conf.reg_user
changes. This function can be used to indicate that progress to the user with a display or LED, or may do nothing.
adap_conf_get()
int adap_conf_get(const char *name, void *buf, size_t len)
NOTE: This is not used in ADA-3.0 or later.
This function fetches the specified configuration item if found in persistent storage. The name argument is the name of the configuration item. The buf argument specifies the buffer address, and the len specifies the length in bytes. The function should return the number of bytes read, which is generally a string length, but in some cases is the length of binary data read. On error, the function should return -1 or a negative error number.
adap_conf_set()
int adap_conf_set_bin(const char *name, const void *buf, size_t len)
NOTE: This is not used in ADA-3.0 or later.
This function saves a configuration item, specified by the name. The source of the data is the buffer specified by the buf and the len in bytes. Although in many cases the buffer is a string, the buffer can be binary data and no assumptions about the content should be made. The function returns 0 on success, and -1 or a negative error number on failure.
adap_conf_pub_key_get()
int ada_conf_pub_key_read(void *buf, size_t len)
NOTE: This is not used in ADA-3.0 or later.
This function reads the configured device public key as an ASN-1-encoded binary 2048-bit RSA key. The key must be configured in the platform during manufacturing and may be saved in binary or base-64 or some other format, but delivered in binary. If a name/value configuration system is used, the name id/pub_key is recommended.
adap_conf_oem_key_get()
int ada_conf_oem_key_read(void *buf, size_t len)
NOTE: This is not used in ADA-3.0 or later.
This function reads the encrypted OEM key in binary. This may have been saved by a CLI command or another method during manufacturing. The arguments buf and len specify the area to receive the key. The return value is the length of the key read, or a negative value on error. If a name/value configuration system is used, the name oem/key is recommended.
Wi-Fi subsystem
The Ayla Integrated Agent, Ayla Device Wi-Fi (ADW), and alternative Wi-Fi subsystems require Wi-Fi interfaces. If there is no Wi-Fi interface, these may be stubbed out, but must still be supplied.
adap_wifi_features_get
enum ada_wifi_features adap_wifi_features_get(void);
This function returns a mask of features provided by this version of the ADW. The function can depend on compile options and on the version of the ADW being used. The features may include:
Feature | Description |
---|---|
AWF_SIMUL_AP_STA | Indicates that simultaneous AP and Station mode can be used. |
AWF_WPS | Indicates that Wi-Fi Protected Setup (WPS) is supported. |
AWF_WPS_APREG | Indicates that AP-Mode registration of the device to a user during WPS setup is supported. |
All the values that are supported are ORed together. The caller can test for the presence of a given feature using code that tests the associated bit; for example:
enum ada_wifi_features features;
features = adap_wifi_features_get();
if (features & AWF_WPS) {
printf("WPS supported\n");
}
adap_wifi_in_ap_mode
int adap_wifi_in_ap_mode();
This returns a non-zero value if the AP interface is active, and zero otherwise.
adap_wifi_get_ssid
int adap_wifi_get_ssid(void *buf, size_t len);
This function fills in the supplied buffer with the (Service Set Identifier) SSID of the connected network. The len argument gives the total length of the buffer available, which must be at least 33 bytes long. The returned value is the length of the SSID in bytes. It will be 0 or a negative number if the station interface is not connected to a network.
adap_wifi_stayup
void adap_wifi_stayup(void);
This function instructs the ADW that the interface is in use and connectivity should be maintained. The Ayla Integrated Agent uses this to inform ADW when the internal web server is in use so that AP mode remains active for some unspecified time.
adap_wifi_show_hist
void adap_wifi_show_hist(void);
This should cause the history of that last several Wi-Fi connection attempts to be logged to the console as Wi-Fi info messages. This function is called before logs are sent to the logging service the first time. This gives the Wi-Fi subsystem a chance to include its status and history in the first connection to the log server.
adap_net_get_signal
int adap_net_get_signal(int *signalp);
The Ayla Integrated Agent uses this function to get the current signal strength from the network subsystem. If a wireless network is connected, it should set the integer pointed to by signalp to the signal strength in dBm and return 0. If no wireless network is connected or this functionality is not provided, the return is -1.
Updated 12 months ago