le_dcs Interface

API Reference
Sample code

When applications need to exchange data with other devices on the packet switched data network, a data channel needs to be first established. After such channel is successfully established, then applications can open up IP sockets on this data channel as necessary as a data pipe for sending and receiving data.

Channel Operations

The Data Channel Service (DCS) provides an API interface for a client application to learn about all the data channels available on a device, and administratively start or stop a selected channel. Data channels that are managed by DCS can be shared by multiple applications.

Note
In the context of this documentation the acronym DCS refers to the le_dcs interface, unless specified otherwise.

When one application asks DCS to start a specified channel, DCS associates it with this channel; similarly when it asks DCS to stop this channel, DCS disassociates it from its use. Therefore, in this way what a client application sees and gets as a channel's state is its administrative state, not its operational state. A channel is operationally brought up when its administratively state goes up to indicate that it is needed by at least one application. Similarly, it is operationally brought down when its administrative state goes down to indicate that it is needed by no application anymore. Thus, it is possible for a chanel to be administratively up, meaning that it is associated with some applications, but operationally down due to certain reasons, including network errors, weak signal, temporary downtime, etc.

Channel Reference

Since DCS provides channel choices, a channel's object reference is used in most of its APIs as the input identifier to uniquely identify the channel of choice. This channel reference is unique across the device over all supported technologies, and is managed and provided by DCS.

Before a channel can be specified as an input argument of choice, an application needs to call le_dcs_GetChannels() to get the list of all channels and from those results learn about each one's channel reference for use in any other le_dcs and le_net API calls.

le_net Interface

Associated with the le_dcs interface, the le_net interface provides network services to DCS-managed channels. le_net provides APIs for setting the device's default gateway addresses, DNS server addresses, adding routes, etc.

Technical Configurations

With the use of le_data, there used to be technology-specific configurations saved in Legato's configuration tree under DCS's paths. le_dcs expanded DCS's capabilities to modularize technology-specific implementations and support multiple active channels over different technologies simultaneously, le_dcs no longer keeps technology-specific configurations under its tree paths.

For WiFi-specific configurations, the le_dcs interface no longer saves them on its common path dataConnectionService:/wifi/SSID. Per-SSID configurations are saved now on the wifiService path under the SSID name itself, e.g. wifiService:/wifi/channel/MY-MOBILE/setProtocol and wifiService:/wifi/channel/MY-WLAN/hidden where MY-MOBILE and MY-WLAN are both SSIDs. Moreover, confidential user credentials, like passphrase, PSK, user name with password, are now kept in secure storage. In the case of WiFi, these are kept and managed there by the wifiClient, only through which reading and writing of such info can be done. Note that the WiFi security protocol used for each SSID remains on the config tree mentioned above for easy reference and debugging. In this way, le_dcs keeps technology-specific configurations on the path of the technology itself.

Example of WiFi Config:

 wifiService:/
   wifi/
     channel/
       MY-MOBILE/
         secProtocol<string> == 3
       MY-WLAN/
         secProtocol<string> == 3
         hidden<bool> == true

The above illustration shows that under this new path structure multiple configurations for multiple channels can be stored and easily expanded for future needs. For more details about WiFi configurations, please refer to le_wifiClient_interface.h.

For cellular-specific configurations, each time a cellular channel needs to be specified as an input argument in calling a le_dcs API.le_dcs does not use nor refer to the default cellular profile index saved under the path dataConnectionService:/cellular/profileIndex.

API Overview

The le_dcs APIs provide applications the interfaces to:

Function Description
le_dcs_GetChannels() Retrieves the list of all available channels across all supported technologies
le_dcs_GetTechnology() Queries for a channel reference's technology type
le_dcs_GetReference() Queries for a channel's reference by its name and technology
le_dcs_GetState() Queries for a channel's administrative state and network interface name by its reference
le_dcs_AddEventHandler() Adds a channel event notification handler for a specific channel
le_dcs_RemoveEventHandler() Removes a channel event notification handler for a specific channel
le_dcs_Start() Starts a channel
le_dcs_Stop() Stops a channel

Get Channel

For an application that seeks to use the le_dcs APIs, the first step is to call le_dcs_GetChannels() to obtain an up-to-date list of all channels available over all supported technologies on which are the channel reference and technology type of each available channel. The channel that the app wishes to communicate with can then be chosen from this list.

Note
To call le_dcs_GetChannels() the application needs to provide a handler function to handle the results to be returned, and wait until such results come back before calling subsequent le_dcs APIs. le_dcs_GetChannels() is asynchronous, due to need of some technologies to actively re-scan for their latest lists of available channels, i.e., WiFi.
Moreover, on the returned channel list the channel state of each channel is provided. However, it is a dynamic parameter which value might keep changing and is, thus, included as a result only for reference. Client apps need to know that it was a value retrieved at the moment when DCS performed the channel query, but may not necessarily stay the same when they get it as part of the channel list results.

Example of le_dcs_GetChannels():

// This is the callback function for handling the results of a channel list query
static void ClientChannelQueryHandler
(
le_result_t result, ///< [IN] Result of the query
const le_dcs_ChannelInfo_t *channelList, ///< [IN] Channel list returned
size_t channelListSize, ///< [IN] Channel list's size
void *contextPtr ///< [IN] Associated user context pointer
)
{
uint16_t i;
 
LE_INFO("Received channel query result %d, channel list size %d", result, channelListSize);
 
if (channelListSize == 0)
{
return;
}
 
for (i = 0; i < channelListSize; i++)
{
LE_INFO("Available channel #%d: name %s from technology %d, state %d, reference %p",
i + 1, channelList[i].name, channelList[i].technology, channelList[i].state,
channelList[i].ref);
}
}
 
// This is the function initiating a channel list query
void ClientGetChannels
(
void
)
{
le_dcs_GetChannels(ClientChannelQueryHandler, NULL);
}

Get Channel Reference

If an application wants to user le_dcs over a channel that is already known and won't come and go frequently, i.e., a cellular channel, then the app can learn about its channel reference via either le_dcs_GetChannels() or le_dcs_GetReference(). le_dcs_GetChannels() provides better reliability, le_dcs_GetReference() is a synchronous transaction that provides the technology and channel type faster but without the reliability of le_dcs_GetChannels().

Example of le_dcs_GetChannels() used to retrieve a specific channel's channel reference:

void ClientGetReference
(
char channelName[LE_DCS_CHANNEL_NAME_MAX_LEN],
le_dcs_Technology_t channelTech
)
{
le_dcs_ChannelRef_t ref = le_dcs_GetReference(channelName, channelTech);
if (ref == 0)
{
LE_ERROR("Failed to retrieve channel reference for channel %s of tech %d",
channelName, channelTech);
return;
}
LE_INFO("Retrieved channel reference %p for channel %s of tech %d", channelName,
channelTech, ref);
}

Channel Technology

DCS provides le_dcs_GetTechnology() to applications for querying for the technology type of a known channel reference. This should be used when those applications are seeking to remember all channel references returned via le_dcs_GetChannels() but not all the details about each channel.

Example over how to use le_dcs_GetTechnology():

void ClientGetTechnology
(
)
{
le_dcs_Technology_t channelTech = le_dcs_GetTechnology(channelRef);
LE_INFO("Retrieved tech type %d for channel reference %p ", channelTech, channelRef);
}

Channel State

As a channel's administrative state might change over time, le_dcs_GetState() queries for a given channel's up-to-date state.

Example of checking for State:

void ClientGetAdminState
(
)
{
char interfaceName[LE_DCS_INTERFACE_NAME_MAX_LEN+1];
 
ret = le_dcs_GetState(channelRef, &state, interfaceName, LE_DCS_INTERFACE_NAME_MAX_LEN);
if (LE_OK != ret)
{
LE_ERROR("Failed to get admin state of channel reference %p; return code %d",
channelRef, ret);
return;
}
LE_INFO("Retrieved for channel reference %s network interface %s, admin state %s",
channelRef, interfaceName, (state==LE_DCS_STATE_UP) ? "up" : "down");
}

Channel Event Handler

Applications that want to be kept posted about any state changes of the channel can fulfill this need by providing DCS a channel event handler via le_dcs_AddEventHandler() per channel. For each channel managed by le_dcs, there could be a chain of event handlers provided by multiple applications interested in it. After an application is disassociated from a channel or uninterested in it, it can call le_dcs_RemoveEventHandler() to remove its event handler.

Example of checking for state within an event handler:

static void clientEventHandler
(
le_dcs_ChannelRef_t channelRef, ///< [IN] The channel for which the event is
le_dcs_Event_t event, ///< [IN] Event up, down or temp-down
int32_t code, ///< [IN] Additional event code, like error code
void *contextPtr ///< [IN] Associated user context pointer
)
{
char *eventString;
switch (event)
{
case LE_DCS_EVENT_UP:
eventString = "Up";
break;
case LE_DCS_EVENT_DOWN:
eventString = "Down";
break;
case LE_DCS_EVENT_TEMP_DOWN:
eventString = "Temporary Down";
break;
default:
eventString = "Unknown";
break;
}
LE_INFO("Received for channel reference %p event %s", channelRef,
eventString, channelDb->channelName, appSessionRef);
}
 
void ClientAddEventHandler
(
le_dcs_ChannelRef_t channelRef,
le_dcs_EventHandlerRef_t *eventHandlerRef
)
{
if ((channelRef == 0) || !eventHandlerRef)
{
LE_ERROR("Invalid inputs for adding event handler for channel reference %p",
channelRef);
return;
}
*eventHandlerRef = le_dcs_AddEventHandler(channelRef, clientEventHandler, NULL);
LE_INFO("Added event handler for channel reference %p; event handler reference %p",
channelRef, *eventHandlerRef);
}
 
void ClientRemoveEventHandler
(
le_dcs_EventHandlerRef_t eventHandlerRef
)
{
if (!eventHandlerRef)
{
LE_ERROR("Got null channel event handler to remove");
return;
}
le_dcs_RemoveEventHandler(eventHandlerRef);
}

Starting and Stopping Channel

The function for an application to start a selected data channel and associate itself with it is le_dcs_Start(), while the opposite to stop and disassociate with it is le_dcs_Stop().

Example of starting and stoping a channel:

void ClientStartChannel
(
le_dcs_ChannelRef_t channelRef,
)
{
if ((channelRef == 0) || !reqObj)
{
LE_ERROR("Invalid inputs for starting channel with reference %p", channelRef);
return;
}
*reqObj = le_dcs_Start(channelRef);
LE_INFO("Started channel with reference %p and request reference %p", channelRef, *reqObj);
}
 
void ClientStopChannel
(
)
{
if (!reqObj)
{
LE_ERROR("Invalid input for stopping channel with request reference %p", reqObj);
return;
}
 
ret = le_dcs_Stop(reqObj);
LE_INFO("Stopped channel with request reference %p, release status %d", reqObj, ret);
}

Handling of Connection Failures

With the le_dcs interface, connection failures over a channel are handled within the channel itself. Connection retries over another channel of the same or different technology will not be internally attempted like with the le_data interface.

If the channel fails the default behavior is for DCS to try to reconnect over the same channel. If a different channel is wanted to be tried as a backup that logic and direction needs to be added to the application logic. If there is an issue with a channel, the application associated with the channel needs to disassociate from it and re-associate with another channel.

Technology-specific conditions and custom-made handlings in le_dcs are now implemented and attempted within the technology itself, i.e. the default behavior for cellular channels will have a different behavior than WiFi's.

Best practices are to retry a reconnect over a certain limited duration with a back-off duration. After all allowed attempts have exhausted with no success, then DCS will send a channel down event notification to the interested applications and stop the retries. In situations involving a system-wide blackout, one example is in the cellular packet switched state's becoming false, a retry won't be attempted until its recovery, i.e. in the example the cellular packet switched state's going back to true, where DCS will resume retrying to reconnect back all the channels of the corresponding technology that are administratively up.

It is very necessary to note that for some technologies after a channel has failed and then reconnected back, the network interface and IP address assigned to its use might have changed (depending on settings like DHCP). The default gateway and DNS addresses for this channel might have changed as well and applications must take this into account and might need to re-adjust the routes and/or gateway address settings after a channel state flapped.