HomePort
Tutorial 02: Demo Example

Table of Contents

Introduction

In this tutorial, we will cover the basic functions in hpd, which are needed in the main program, adapters, and applications.

The full files can be found in the example/demo folder, and can be compiled with make example_demo.

Main Program

The main program (demo_main.c) is pretty similar to that of the template example. Note, that we included the hpd_rest webservice module, which are included and installed alongside hpd itself.

#include "demo_adapter.h"
int main(int argc, char *argv[])
{
// Allocate hpd memory
if ((rc = hpd_alloc(&hpd))) goto error_return;
// Add modules
if ((rc = hpd_module(hpd, "rest", &hpd_rest))) goto error_free;
if ((rc = hpd_module(hpd, "demo_adapter", &hpd_demo_adapter_def))) goto error_free;
if ((rc = hpd_module(hpd, "demo_application", &hpd_demo_app_def))) goto error_free;
// Start hpd
if ((rc = hpd_start(hpd, argc, argv))) goto error_free;
// Clean up
if ((rc = hpd_free(hpd))) goto error_return;
return rc;
error_free:
hpd_free(hpd);
error_return:
return rc;
}

The hpd functionality used in this file:

Demo Adapter

Here, we will create a simple adapter, that creates N virtual lamps, where N is a number given as command-line argument. If N is not given we will create just one lamp as default.

Header file

As usual, the header file (demo_adapter.h) with only include a declaration of single hpd_module_def. Which describes our module, and makes it easy to include in a main program when calling hpd_module().

#ifndef HOMEPORT_DEMO_ADAPTER_H
#define HOMEPORT_DEMO_ADAPTER_H
#include <hpd/hpd_types.h>
#endif //HOMEPORT_DEMO_ADAPTER_H

Source file

Our source file (demo_adapter.c) will use two header files.

We will need two struct of our own for this adapter:

As in the template we provide the five functions and an instance of the module definition struct, as declared in the header file:

Function Implementations

The order of the functions here differs from the actual file (demo_adapter.c), so refer to the file if you need to compile this example.

on_create() and on_destroy()

demo_adapter_on_create() first allocation the global memory and initialises it, then it add the support command-line arguments to hpd:

static hpd_error_t demo_adapter_on_create(void **data, const hpd_module_t *context)
{
// Create struct with custom data
HPD_CALLOC(demo_adapter, 1, demo_adapter_t);
demo_adapter->num_lamps = 1;
demo_adapter->context = context;
if ((rc = hpd_module_get_id(context, &demo_adapter->module_id)))
goto error_free;
// Add supported options
if ((rc = hpd_module_add_option(context, "num-lamps", "count", 0, "Number of lamps to create. Default 1.")))
goto error_free;
*data = demo_adapter;
return HPD_E_SUCCESS;
alloc_error:
error_free:
free(demo_adapter);
return rc;
}

New functionality introduced:

demo_adapter_on_destroy() frees this memory:

{
demo_adapter_t *demo_adapter = data;
free(demo_adapter);
return HPD_E_SUCCESS;
}

on_parse_opt()

demo_adapter_on_parse_opt() checks for the argument we defined with hpd_module_add_option() earlier and sets num_lamps accordingly. Recall that this function should return HPD_E_ARGUMENT, when the argument is not recognised.

static hpd_error_t demo_adapter_on_parse_opt(void *data, const char *name, const char *arg)
{
demo_adapter_t *demo_adapter = data;
if (strcmp(name, "num-lamps") == 0) {
demo_adapter->num_lamps = atoi(arg);
return HPD_E_SUCCESS;
}
}

on_start() and on_stop()

demo_adapter_on_start() creates the adapter object and the required number of lamps (we will make demo_adapter_create_adapter() and demo_adapter_create_lamp() in a bit). On errors it stops everything again:

static hpd_error_t demo_adapter_on_start(void *data, hpd_t *hpd)
{
demo_adapter_t *demo_adapter = data;
hpd_error_t rc, rc2;
HPD_LOG_INFO(demo_adapter->context, "Starting with %i lamps...", demo_adapter->num_lamps);
if ((rc = demo_adapter_create_adapter(hpd, demo_adapter))) goto error_return;
// Create device structures
char *id = NULL;
for (int i = 0; i < demo_adapter->num_lamps; i++) {
HPD_SPRINTF_ALLOC(id, "dev%i", i);
if ((rc = demo_adapter_create_lamp(demo_adapter, id))) goto error_free_id;
free(id);
}
return HPD_E_SUCCESS;
alloc_error:
free(id);
if ((rc2 = demo_adapter_on_stop(demo_adapter, hpd)))
HPD_LOG_ERROR(demo_adapter->context, "on_stop() failed [code: %i].", rc2);
snprintf_error:
free(id);
if ((rc2 = demo_adapter_on_stop(demo_adapter, hpd)))
HPD_LOG_ERROR(demo_adapter->context, "on_stop() failed [code: %i].", rc2);
error_free_id:
free(id);
if ((rc2 = demo_adapter_on_stop(demo_adapter, hpd)))
HPD_LOG_ERROR(demo_adapter->context, "on_stop() failed [code: %i].", rc2);
error_return:
return rc;
}

New functionality introduced:

demo_adapter_on_stop() stops anything that was started during runtime. As we only created one adapter, we only have to detach and free this - everything underneath it (devices, services, and parameters) will automatically be freed by hpd:

static hpd_error_t demo_adapter_on_stop(void *data, hpd_t *hpd)
{
hpd_error_t rc, rc2;
demo_adapter_t *demo_adapter = data;
HPD_LOG_INFO(demo_adapter->context, "Stopping...");
// Detach adapter from hpd
hpd_adapter_t *adapter;
if ((rc = hpd_adapter_detach(demo_adapter->adapter_id, &adapter))) goto error_free_id;
// Clean up nicely
if ((rc = hpd_adapter_free(adapter))) goto error_free_id;
if ((rc = hpd_adapter_id_free(demo_adapter->adapter_id))) goto error_return;
return HPD_E_SUCCESS;
error_free_id:
if ((rc2 = hpd_adapter_id_free(demo_adapter->adapter_id)))
HPD_LOG_ERROR(demo_adapter->context, "Free function failed [code: %i].", rc2);
error_return:
return rc;
}

New functionality introduced:

Object creation

In demo_adapter_on_start() we called two functions; demo_adapter_create_adapter() and demo_adapter_create_lamp() to create our object models, which we will implement now.

First demo_adapter_create_adapter() allocates an adapter object, attaches it and creates an id reference to it. As unique id we will use the module id, because we do not have better options and we are only making one adapter per module.

{
hpd_error_t rc, rc2;
// Create id structure to reference our adapter
if ((rc = hpd_adapter_id_alloc(&demo_adapter->adapter_id, hpd, demo_adapter->module_id))) goto error_return;
// Create adapter structure (using module_id as id)
hpd_adapter_t *adapter;
if ((rc = hpd_adapter_alloc(&adapter, demo_adapter->module_id))) goto error_free_id;
if ((rc = hpd_adapter_set_attr(adapter, HPD_ATTR_TYPE, "demo_adapter"))) goto error_free_adapter;
if ((rc = hpd_adapter_attach(hpd, adapter))) goto error_free_adapter;
return HPD_E_SUCCESS;
error_free_adapter:
if ((rc2 = hpd_adapter_free(adapter)))
HPD_LOG_ERROR(demo_adapter->context, "Free function failed [code: %i].", rc2);
error_free_id:
if ((rc2 = hpd_adapter_id_free(demo_adapter->adapter_id)))
HPD_LOG_ERROR(demo_adapter->context, "Free function failed [code: %i].", rc2);
demo_adapter->adapter_id = NULL;
error_return:
return rc;
}

New functionality introduced:

demo_adapter_create_lamp() create the device object and calls demo_adapter_create_service() to create the service and parameter. Note that we are creating the service before attaching the device - unlike adapters, devices needs to be attached with all their services and parameters already attached.

static hpd_error_t demo_adapter_create_lamp(demo_adapter_t *demo_adapter, const char *id)
{
hpd_error_t rc, rc2;
hpd_device_t *device;
if ((rc = hpd_device_alloc(&device, id))) goto error_return;
if ((rc = hpd_device_set_attr(device, HPD_ATTR_TYPE, "demo_lamp"))) goto error_free;
if ((rc = demo_adapter_create_service(demo_adapter, device))) goto error_free;
if ((rc = hpd_device_attach(demo_adapter->adapter_id, device))) goto error_free;
return HPD_E_SUCCESS;
error_free:
if ((rc2 = hpd_device_free(device)))
HPD_LOG_ERROR(demo_adapter->context, "Free function failed [code: %i].", rc2);
error_return:
return rc;
}

New functionality introduced:

demo_adapter_create_service() creates the service, the struct for our custom data for it, and calls demo_adapter_create_parameter() to create the parameter. We will create two callbacks demo_adapter_on_get() and demo_adapter_on_put() later.

{
hpd_error_t rc, rc2;
hpd_service_t *service;
if ((rc = hpd_service_alloc(&service, "srv0"))) goto error_return;
if ((rc = hpd_service_set_actions(service,
HPD_M_NONE))) goto error_free_service;
demo_adapter_srv_t *srv_data;
srv_data->state = 0;
srv_data->demo_adapter = demo_adapter;
if ((rc = hpd_service_set_data(service, srv_data, free))) goto error_free_data;
if ((rc = demo_adapter_create_parameter(demo_adapter, service))) goto error_free_service;
if ((rc = hpd_service_attach(device, service))) goto error_free_service;
return HPD_E_SUCCESS;
alloc_error:
if ((rc2 = hpd_service_free(service)))
HPD_LOG_ERROR(demo_adapter->context, "Free function failed [code: %i].", rc2);
error_free_data:
free(srv_data);
error_free_service:
if ((rc2 = hpd_service_free(service)))
HPD_LOG_ERROR(demo_adapter->context, "Free function failed [code: %i].", rc2);
error_return:
return rc;
}

New functionality introduced:

demo_adapter_create_parameter() creates a new parameter object for our service:

{
hpd_error_t rc, rc2;
hpd_parameter_t *parameter;
if ((rc = hpd_parameter_alloc(&parameter, "param0"))) goto error_return;
if ((rc = hpd_parameter_attach(service, parameter))) goto error_free;
return HPD_E_SUCCESS;
error_free:
if ((rc2 = hpd_parameter_free(parameter)))
HPD_LOG_ERROR(demo_adapter->context, "Free function failed [code: %i].", rc2);
error_return:
return rc;
}

New functionality introduced:

Action Callbacks

demo_adapter_on_get() will send the current value of the lamp:

{
return demo_adapter_send_value(req, data);
}

demo_adapter_on_put() will set the value of the lamp and then send the new current value:

{
hpd_status_t status;
if ((status = demo_adapter_set_value(req, data)) != HPD_S_NONE) return status;
return demo_adapter_send_value(req, data);
}

New functionality introduced:

Sending and Setting Values

demo_adapter_send_value() sends a response to the requester with the current value:

{
hpd_error_t rc, rc2;
// Allocate response
if ((rc = hpd_response_alloc(&res, req, HPD_S_200))) goto error_return;
// Create and set value
if ((rc = hpd_value_allocf(&val, "%i", srv_data->state))) goto error_free_res;
if ((rc = hpd_response_set_value(res, val))) goto error_free_val;
// Send response
if ((rc = hpd_respond(res))) goto error_free_res;
return HPD_S_NONE;
error_free_val:
if ((rc2 = hpd_value_free(val)))
HPD_LOG_ERROR(srv_data->demo_adapter->context, "Free function failed [code: %i].", rc2);
error_free_res:
if ((rc2 = hpd_response_free(res)))
HPD_LOG_ERROR(srv_data->demo_adapter->context, "Free function failed [code: %i].", rc2);
error_return:
HPD_LOG_ERROR(srv_data->demo_adapter->context, "%s() failed [code: %i].", __FUNCTION__, rc);
return HPD_S_500;
}

New functionality introduced:

demo_adapter_set_value() set the value of a lamp, which was store in the state field of demo_adapter_srv_t. To do so we first have to retrieve the new value from the request and parse it from a string into an integer. If the value is changed be call demo_adapter_send_changed() to send out an event.

{
// Get value
const hpd_value_t *val;
if ((rc = hpd_request_get_value(req, &val))) goto error_return;
// Get body from value
const char *body;
size_t len;
if ((rc = hpd_value_get_body(val, &body, &len))) goto error_return;
// Get service id
const hpd_service_id_t *service_id;
if ((rc = hpd_request_get_service(req, &service_id))) goto error_return;
// Get new state
char *nul_term = NULL;
HPD_STR_N_CPY(nul_term, body, len);
int state = atoi(nul_term);
free(nul_term);
if (state != srv_data->state) {
srv_data->state = state;
if ((rc = demo_adapter_send_changed(service_id, srv_data)))
HPD_LOG_ERROR(srv_data->demo_adapter->context, "Failed to send changed value [code: %i].", rc);
}
return HPD_S_NONE;
alloc_error:
HPD_LOG_ERROR(srv_data->demo_adapter->context, "%s() failed.", __FUNCTION__);
return HPD_S_500;
error_return:
HPD_LOG_ERROR(srv_data->demo_adapter->context, "%s() failed [code: %i].", __FUNCTION__, rc);
return HPD_S_500;
}

New functionality introduced:

demo_adapter_send_changed() sends out events that the value of changed to any possible listeners.

{
if ((rc = hpd_value_allocf(&value, "%i", srv_data->state))) return rc;
if ((rc = hpd_changed(service_id, value))) hpd_value_free(value);
return rc;
}

New functionality introduced: