Receiving Commands
Thing Interaction Framework delivers the command sent from the mobile app to the target thing. The Thing-IF SDK calls the action handler specified in initialization when the thing receives the command.
Action Handler
The action handler is a function as below. It is called as many times as the number of actions included in a command.
static kii_bool_t action_handler(
const char* schema,
int schema_version,
const char* action_name,
const char* action_params,
char error[EMESSAGE_SIZE + 1])
{
return KII_TRUE;
}
schema
and shema_version
store the schema name and the schema version specified in the command from the mobile app. See the next section, Checking the Schema, to learn how to use these values.
action_name
and action_params
store the detail of the received action.
error
is the output parameter of this function. It returns KII_TRUE
and error
with no change when the action is successfully executed. It returns KII_FALSE
and error
containing an error message when the action execution fails. Bulb is overheating
was displayed when you walked through Running Sample Programs because the action handler put the string in error
and returned KII_FALSE
.
For example, when the thing receives the turnPower action designed in Designing Commands and State Information, the action handler will receive these values as below.
Argument | Description | Sample Value |
---|---|---|
schema |
Schema name | HelloThingIF-Schema |
schema_version |
Schema version | 1 |
action_name |
Action name | turnPower |
action_params |
Action parameters in JSON format | {"power":true} |
If the same command includes the setBrightness
action as well, the action handler is called again with the arguments for the action (Example: action_name
= setBrightness
and action_params
= {"brightness":100}
) just after the turnPower
action is processed.
Checking the Schema
When the action handler receives an action, it first checks the schema name and the schema version to determine if the thing program can process the action. The handler responds with an error message if the schema is not compatible with the thing program.
For example, suppose the following situation.
At first, all the components use Version 1 of the schema. Each component receives any commands as it expects in this circumstance.
Later, the mobile app and the thing firmware are updated. However, those updates might not be installed to all the components. As seen in the second example in the below figure, things with the old firmware might receive unrecognizable commands.
In this case, things can return error against the unknown schema and/or schema version. You can take measures such as having the mobile app prompt the user to update the thing firmware when the app receives a certain error message.
The check process implemented in Hello Thing-IF checks if the command has the specific schema name and version. You can implement a check process which allows multiple versions, for example, Version 2 or later.
static kii_bool_t action_handler(
const char* schema,
int schema_version,
const char* action_name,
const char* action_params,
char error[EMESSAGE_SIZE + 1])
{
......
printf("Schema=%s, schema version=%d, action name=%s, action params=%s\n",
schema, schema_version, action_name, action_params);
if (strcmp(schema, SCHEMA_NAME) != 0 || SCHEMA_VERSION != 1) {
prv_action_error(error, "Invalid schema.");
return KII_FALSE;
}
......
return KII_TRUE;
}
prv_action_error
is a helper method as below. It outputs a message to error
and the console.
static void prv_action_error(char error[EMESSAGE_SIZE + 1], const char *message)
{
memset(error, 0x00, EMESSAGE_SIZE + 1);
strncpy(error, message, EMESSAGE_SIZE);
printf("Error: %s\n", message);
}
An area of EMESSAGE_SIZE + 1
bytes is reserved for error
. Simple transfer of arguments of an action to the error message can be a source of buffer overflow vulnerabilities. Check the length of the error message with strncpy()
or anything as in the above sample code.
Parsing the Action
After the compatibility of the received action is verified, the action name and parameters are parsed to process the action.
The process to parse an action is implemented as below in Hello Thing-IF.
static kii_bool_t action_handler(
const char* schema,
int schema_version,
const char* action_name,
const char* action_params,
char error[EMESSAGE_SIZE + 1])
{
prv_smartlight_t smartlight;
......
memset(&smartlight, 0x00, sizeof(smartlight));
if (strcmp(action_name, "turnPower") == 0) {
kii_json_field_t fields[2];
memset(fields, 0x00, sizeof(fields));
fields[0].path = "/power";
fields[0].type = KII_JSON_FIELD_TYPE_BOOLEAN;
fields[1].path = NULL;
if (prv_json_read_object(action_params, strlen(action_params),
fields, error) != KII_JSON_PARSE_SUCCESS) {
prv_action_error(error, "Invalid turnPower in JSON.");
return KII_FALSE;
}
if (prv_get_smartlight_info(&smartlight) == KII_FALSE) {
prv_action_error(error, "Failed to get smartlight info.");
return KII_FALSE;
}
smartlight.power = fields[0].field_copy.boolean_value;
if (prv_set_smartlight_info(&smartlight) == KII_FALSE) {
prv_action_error(error, "Failed to set smartlight info.");
return KII_FALSE;
}
} else if (strcmp(action_name, "setBrightness") == 0) {
kii_json_field_t fields[2];
memset(fields, 0x00, sizeof(fields));
fields[0].path = "/brightness";
fields[0].type = KII_JSON_FIELD_TYPE_INTEGER;
fields[1].path = NULL;
if(prv_json_read_object(action_params, strlen(action_params),
fields, error) != KII_JSON_PARSE_SUCCESS) {
prv_action_error(error, "Invalid brightness in JSON.");
return KII_FALSE;
}
if (prv_get_smartlight_info(&smartlight) == KII_FALSE) {
prv_action_error(error, "Failed to get smartlight info.");
return KII_FALSE;
}
if (smartlight.brightness == 100 && fields[0].field_copy.int_value == 100) {
prv_action_error(error, "Bulb is overheating");
return KII_FALSE;
}
smartlight.brightness = fields[0].field_copy.int_value;
if (prv_set_smartlight_info(&smartlight) == KII_FALSE) {
prv_action_error(error, "Failed to set smartlight info.");
return KII_FALSE;
}
} else {
prv_action_error(error, "Invalid action.");
return KII_FALSE;
}
if (smartlight.power) {
printf("Light: Power on, Brightness: %d\n", smartlight.brightness);
} else {
printf("Light: Power off\n");
}
return KII_TRUE;
}
Basically, the action handler processes action_name
by applying an if...else if...else statement. It parses action_params
of each action to execute the action.
The thing program parses actions as designed in Designing Commands and State Information. The first if
parses an action whose name is turnPower
and the second if
parses an action whose name is setBrightness
.
The thing program parses actions in the following steps:
Parse the JSON string
The thing program parses action parameters passed with
action_params
for eachaction_name
. The parsed string corresponds to the parameter defined in the schema.The kii_json library provides a functionality to parse the JSON string. The thing program calls it via a helper function in
prv_json_read_object()
.The thing program reads the boolean value of the JSON path
/power
forturnPower
and the integer value of the JSON path/brightness
forsetBrightness
. These values are set in an array of thekii_json_field_t
structure per action and thenprv_json_read_object
is called.The kii_json library allows you to get values of multiple fields from a single JSON string. Therefore, the
kii_json_field_t
structure which stores such fields should be declared in a continuous area as an array. NULL in thepath
member indicates the end of a parameter definition.prv_json_read_object()
returnsKII_JSON_PARSE_SUCCESS
when it parsed the action successfully.Get the
prv_smartlight_t
structureThe latest state of the LED light is stored in the global variable to send as state information. The
prv_get_smartlight_info()
function gets values in the variable.Update the values
The thing program updates the values in the
prv_smartlight_t
structure read in step 2.The thing program reads the parsing result of the JSON string. The result is copied to a union member of the
field_copy
member if the field type has been instructed with thetype
member when the parsing started. A boolean value is copied tofield_copy.boolean_value
and an integer value is copied tofield_copy.int_value
.Note that an error check runs for testing purposes when the brightness level is updated. It issues the
Bulb is overheating
error if the user attempts to update the brightness level to 100 when the level is already 100. You can return errors in a similar way in your actual program.Store the state information in the
prv_smartlight_t
structureLastly, the thing program calls the
prv_set_smartlight_info()
function and stores the state of the LED light updated by the action in the global variable.
Finally, the read values are output with printf()
and KII_TRUE
is returned. The Thing-IF SDK takes KII_TRUE
for success and responds to Thing Interfaction Framework that the action was successful. The mobile app can get the action result.
Hello Thing-IF just displays values changed by actions. Your own thing program will control thing hardware, for example, by relaying values from the JSON string to tasks which operate the actuator.
Data Sharing between the Handers
Data should be protected while it is being updated because the action and state handlers run asynchronously. prv_get_smartlight_info()
and prv_set_smartlight_info()
use the mutex of Pthreads to prevent concurrent updates to the global variable.
The prv_smartlight_t
structure and the get and set functions for it are implemented as below.
typedef struct prv_smartlight_t {
kii_json_boolean_t power;
int brightness;
} prv_smartlight_t;
static prv_smartlight_t m_smartlight;
static pthread_mutex_t m_mutex;
static kii_bool_t prv_get_smartlight_info(prv_smartlight_t* smartlight)
{
if (pthread_mutex_lock(&m_mutex) != 0) {
return KII_FALSE;
}
smartlight->power = m_smartlight.power;
smartlight->brightness = m_smartlight.brightness;
if (pthread_mutex_unlock(&m_mutex) != 0) {
return KII_FALSE;
}
return KII_TRUE;
}
static kii_bool_t prv_set_smartlight_info(const prv_smartlight_t* smartlight)
{
if (pthread_mutex_lock(&m_mutex) != 0) {
return KII_FALSE;
}
m_smartlight.power = smartlight->power;
m_smartlight.brightness = smartlight->brightness;
if (pthread_mutex_unlock(&m_mutex) != 0) {
return KII_FALSE;
}
return KII_TRUE;
}
Both the functions lock the mutex, access the global variable m_smartlight
, manipulate the argument values, then unlock the mutex.
If the action and state handlers access the same data in your thing program, you should implement your program to expect those handlers may run at the same time. As with this sample program, synchronization with the mutex or anything may be one of implementation methods.
What's Next?
Let us walk through the process of registering state information.
Go to Registering State Information.
If you want to learn more...
- See Executing Commands for more information about sending commands with the Thing-IF SDK.
- In the real world, you would need to parse more complicated JSON strings. See Parsing JSON for more information about JSON parsing with the Thing-IF SDK.