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:

  1. Parse the JSON string

    The thing program parses action parameters passed with action_params for each action_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 for turnPower and the integer value of the JSON path /brightness for setBrightness. These values are set in an array of the kii_json_field_t structure per action and then prv_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 the path member indicates the end of a parameter definition.

    prv_json_read_object() returns KII_JSON_PARSE_SUCCESS when it parsed the action successfully.

  2. Get the prv_smartlight_t structure

    The 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.

  3. 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 the type member when the parsing started. A boolean value is copied to field_copy.boolean_value and an integer value is copied to field_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.

  4. Store the state information in the prv_smartlight_t structure

    Lastly, 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.