HomePort
|
The internal class diagram for reference, most but not all types are available to the outside through references (direct access to members are generally not possible):
All functions in the API returns hpd_err_t, which is:
For the request-response relationships HomePort continues to use HTTP status codes to describe errors, as many modules relies on HTTP connections. From the HTTP protocol, these are:
The entities in the data model are described using attributes. These are flexible key-value pairs, where the keys can be chosen freely. However, we do define the following default keys for consistencies between modules:
Services have a set of actions, currently for get and put only, but these are define in a enumeration, thus can easily be expanded later:
HomePort's data model allow module developers to store custom pointers to user data of their own choice. HomePort will not touch this data. To allow memory management of custom data pointers, HomePort generally take a on_free callback function with the data pointer. This function will be called when HomePort is free'ing the object and can be used by the module developer to ensure that the custom data is free'd as well. The function pointer can be NULL, if it is not necessary to free the data.
First, the main program allocates memory for HomePort (hpd_t) with hpd_alloc(). Then it tells HomePort of all modules that it wishes to include with hpd_module(). We shall use the term module for both adapters and applications. All modules are assigned a unique id by the main program, which allows running multiple instances of the same module. module_def is a public structure, of which each module should provide an instance (explained below).
The main program will then start HomePort with hpd_start(). Note that this function does not return, but will keep running until hpd_stop() is called. hpd_stop() will be called automatically on sigint and sigterm. Finally, the main program will deallocate the memory with hpd_free().
Any pointer to hpd_t is valid between calls to hpd_alloc() and hpd_free(), and thus can generally be stored by modules in their entire lifetime.
A module will provide an instance of the module_def structure along with implementations of the five included functions. module_def is a structure where the members are publicly available.
The five functions:
In here, on_create and on_destroy are used to allocate/deallocate globally available memory in data, which can be used freely. on_start and on_stop are used to initialise/stop the underlying connection and add watchers to the event loop – whatever is required for the module to be considered running. To handle program argument given to the executable, a module can also define on_parse_opt, which may be called between on_create and on_start due to arguments given to the executable.
The following three functions are shared between adapters and applications:
For arguments to work, the module will have to add any options using hpd_module_add_option() during on_create. Options will be named –id-name, where id is the module identifier, as given by the main program. The module can call hpd_module_get_id() to obtain this id. During on_start and afterwards, the event loop will be accessible through hpd_get_loop(). On here, the module can put io-watchers, timer-watchers, and more to trigger on events.
A module can rely on the pointer to its hpd_module_t (as given in on_create()) will be valid for its entire lifetime, that is between calls to on_create and on_destroy. The pointer to its id (as obtained with hpd_module_get_id()) is valid in the same interval. The pointer to the event loop (as obtained with hpd_get_loop()) is valid between calls to on_start and on_stop.
Through the following sections we shall utilise indirect references to objects in the data mode (adapters, devices, services, parameters). These prevent module developers from keeping pointers to objects that eventually will be free'd, thus making these pointers invalid. To allocate and free these references:
If a module developers explicitly call *_id_alloc(), they are also responsible for calling *_id_free().
The functions in the Adapter API are only available for adapters.
To maintain adapter structures in the data model:
alloc/free are used to manage memory. attach/detach are used to hand the object to HomePort. If an object is attached, it will be visibly through the navigation functions (shown later) and HomePort will ensure that the memory is managed. For example this means that if an adapter is freed, all devices, services, etc. below it will automatically be freed also. Note that the reverse applies too; if an object is detached, HomePort do not know about it and it is up to the module to manage the memory. set_data/get_data functions can be used, by the adapter, to store custom data within HomePort. If on_free is set, HomePort will call this function to free this custom data, when the object is freed.
As to when to use a direct pointer to the adapter and when to use an indirect reference, see the following diagram. hpd_adapter_alloc() allocates the instance of hpd_adapter_t, which is referred to using a pointer - the module developer is in control of the memory. The object is changed (set functions), and finally hpd_adapter_attach() is called. After this call the pointer will be invalid and we can only refer to the object using instances of hpd_adapter_id_t (indirect references) - HomePort manages the memory.
After a call to hpd_adapter_detach(), the indirect reference will still be allocated, but will now result in HPD_E_NOT_FOUND, if we try to use it. hpd_adapter_detach() also returns a pointer to hpd_adapter_t – it is now the responsibility of the module to free the object.
Devices are managed in a similar manner:
Services furthermore allow setting an action, which is a method and a function pointer. Currently only get and put is support, but it could theoretically be expanded to others:
Finally, functions to manage parameters:
To get members from the objects, created by an adapter, applications have the following functions:
Again, we have defines to ease foreach loops:
The HomePort data model can be navigated by both adapters and applications, thus only the later will find use for browsing through models created by another module. To do so, the following functions are available:
In brief: Get functions will navigate upwards in the hierarchy, first functions will get the first object in the list under the given object, next will give the next one.
Because first-next function are a bit complicated to use, the following defines are also available to create simple foreach loops (based on the same principle from libev):
The value structure is used in both get and put requests and thus is available for both adapters and applications:
The body of a value is the actual value, where the headers are additional attributes, which are currently unused but will be used to indicate time-to-live for caching, but are defined generic so they can easily be expanded without breaking the API. Note it is also possible for adapters and applications to store their own custom headers, similar to the principle in HTTP headers.
The len in hpd_value_alloc can be set to HPD_NULL_TERMINATED if body is a \0 terminated string:
Action functions will need functions related to requests/responses to manage these (functions to manage values are shared with applications and will be shown later):
And when, we are considering communication, an adapter should call the following function, whenever a service has changed value:
The functions in the Application API are only available for applications.
For hpd_request_t, hpd_response_t and hpd_listener_t functions we need the following callbacks:
The functions for requests and response are the reverse of an adapter (an application can create requests and read the values of a response):
In addition, an application can also create listeners. Listeners can be created on any object (HomePort, adapter, device, and service) and will be called for that object and everything below if the conditions are met. E.g., a listener with a value callback on an adapter will be called if any service under that adapter changes value. Function to manage listeners:
The foreach function will cause the given listener to be called for each device that is already attached.