Академический Документы
Профессиональный Документы
Культура Документы
IDENTIFICATION --------------------------------
<PARTNERSHIP> ---------------------
DOCUMENT HISTORY
VERSION 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.0 1.0 COMMENTS Initial draft putting all sections together Review Review Final version Dependencies and Embedded Linux sections have been added Added comments in Embedded Linux sections Added linking section Review of linking section Updated device measurement D-Bus interface Updated to Signoves template Review Library name REALIZED BY Elvis Pfutzenreuter Mateus Lima Elvis Pfutzenreuter Raul Herbster Marcos Pereira Elvis Pfutzenreuter Elvis Pfutzenreuter Marcos Pereira Elvis Pfutzenreuter Marcos Pereira Aldenor Martins Elvis Pfutzenreuter DATE 22/11/2010 24/11/2010 26/11/2010 29/11/2010 29/11/2010 10/12/2010 13/12/2010 14/12/2010 14/12/2010 17/12/2010 23/03/2011 20/04/2011
Page 1 of 23
Summary
This document describes Antidote, the IEEE 11073-20601 implementation, which is an API defined to help developers to create applications for medical devices. The information in this document is aimed mainly for software developers of project.
Page 2 of 23
1.
Document Control .............................................................................. 4 1.1 1.2 1.3 References .................................................................................. 4 Documentation Conventions ............................................................. 4 Abbreviations ............................................................................... 4
2. 3.
4.
Components ..................................................................................... 6 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 4.10 4.11 MDER encoding/decoding and utility functions ....................................... 6 APDU parser ................................................................................. 7 DIM Domain Information model ........................................................ 7 Specializations.............................................................................. 8 Data codecs ................................................................................. 9 Communication and state machine ..................................................... 9 Transport plug-ins ......................................................................... 9 IEEE manager ............................................................................. 10 Applications ............................................................................... 11 IEEE manager ............................................................................ 11 D-Bus service ............................................................................ 11
5. 6.
Anatomy of an IEEE application ........................................................... 13 Enabling IEEE 11073-20601 based applications on embedded linux platform ..... 19 6.1 Platforms without D-Bus or BlueZ ..................................................... 19
7. 8.
Appendix A: partial D-Bus health service API description ............................ 20 Linking against THE IEEE static library ................................................... 22
Page 3 of 23
1. Document Control
1.1 References
Ref id Document / Source State Author
Code snippets use Courier New font and they are surrounded by a box.
1.3 Abbreviations
Abbreviation API Description Application Programming Interface.
Page 4 of 23
2. Introduction
This is a developer's guide for Antidote, the Signove IEEE 11073-20601 stack library. This library should be used by applications that want to communicate with health and fitness devices. The document aims to introduce the developer to the software, including architecture and internal building blocks, and ease the learning curve of APIs. A real, working application is included in library itself, and it is dissected in this document, which yields a prompt reference to the developer that is writing his first health application with this stack. It is assumed that the reader knows the IEEE 11073 standard and is familiarized with health devices in general. The text contains opportune clarifications to aid readers that are beginning to work with these matters, but it is not an introductory text to them.
3. High-level architecture
Antidote IEEE 11073 stack (therefore called stack) was developed under key architectural decisions: Avoid dependencies from external libraries. This means easy porting to different Linux distributions, or even different operating systems. Not tied to any particular event loop framework e.g. GLib or Qt or threads. Again, this aims to easy porting and easy adaptation to external software parts (having in mind that every Linux/Unix UI framework implements its event loop in a particular way). Transport plug-in architecture, which allows connection with virtually any transport protocol, be it Bluetooth, pipes, sockets etc. Generation of static libraries, which are embedded into application binary. Applications may link to the main library (which includes the full stack) or any number of components that it actually employs (each component compiles into a separate static library).
3.1 Dependencies
Antidote depends on certain applications and libraries to work: BlueZ (With HDP support)
Page 5 of 23
Kernel 2.6.36 (Some ERTM bugs were fixed in this version). There are community-maintained packages of 2.6.36 for Ubuntu and possibly for other distributions as well. Otherwise, compiling the stock 2.6.36 source is also ok. D-Bus 1.4 (This version adds file descriptor passing support) D-Bus Glib Glib 2.0
4. Components
This section describes each building block within the stack.
Platform dependencies
Application
Data codecs
Device specializations
APDU parser
DIM
MDER
PHD ASN.1
Page 6 of 23
MDER is the common-denominator rule, that every health agent and manager is expected to implement. MDER is optimized in some ways, in order to accommodate low-end devices MDER supports a subset of ASN.1 primitive types. Medical messages are ultimately limited to those primitive types. Of course, compound types like PHD types may use any composition of the primitive types supported by MDER. Apart from MDER, the IEEE stack needs a number of utility functions like date manipulation, safe string buffer handling and so on. These things were implemented here in order to avoid depending on external libraries for such tasks (and thus avoiding gratuitous portability problems). Source code of this block can be found at src/util, and its static library is src/util/libutil.a. Another folder of interest is src/asn1/, which contains include files for the PHD types. PHD types are compound types of primitive ASN.1 types (int, float string etc.), and are used throughout the stack.
PM-Store, which is a method of agent off-line data collection and eventual retrieving by the manager.
Source code of this block can be found at src/dim, and its static library is src/dim/libdim
4.4 Specializations
When an IEEE agent device associates with the manager, it sends its configuration. It is an integer value that defines how measured data events will be formatted. For example, the configuration ID of 0x0190 identifies an oximeter. It is a standard configuration (that a manager should already know) and implies that measurement events will always send two values:| oxygen saturation and pulse rate. If the configuration ID is unknown by the manager, or it is extended (meaning: not standard), the agent will send a configuration APDU after association step is complete. This allows the manager to discover the configuration dynamically, and interpret measurement event data. An IEEE specialization defines standard configurations. For example, src/specializations/pulse_oximeter.h defines configurations 0x0190 and 0x0191. If some oximeter presents an unknown configuration like 0x0192, the IEEE stack will still be able to interpret the measurement data, but an additional configuration APDU exchange is required. Standard configurations exist to reduce the APDU exchange during session establishment, which also helps to save energy. A specialization may also extend the collection of DIM types.. For example, the same source adds a type for beats-per-minute. It does not add a type for oxygen saturation because the saturation unit (percent) is already present on base DIM. Sources for this block are at src/specializations, and its library is src/specializations/libspecializations.a. Specializations are added dynamically by src/manager.c. This means that, in theory, more specializations could be dynamically loaded and added afterwards, even though the current version of stack uses static linking. The IEEE manager caches configurations in the local filesystem. Even if there is no specialization implemented, the device needs to send configuration only once, at the very first association. Future associations from any device that implements the same configuration will skip this phase and save energy. Extended configurations are cached as well. The only difference is that extended configuration namespace is device-specific.
Page 8 of 23
Notifying applications about data of interest (e.g. agent connected, measurement received). This component is the nearest to the application's event loop. Since it is event loopagnostic, the timeout primitives must be supplied by application via external functions, and transport protocol is supplied via plug-ins, which will be shown in next topic. Communication source codes may be found at src/communication, and its library is src/communication/libcomm.a.
Page 9 of 23
When the communication is started, a structure comprised of a few function pointers must be supplied by application. Those functions implement initiation, transport primitives (creation, sending, receiving and closing). The application may implement those functions in any way it wants. Requirements are very few: send and receive functions should not block, and it must support multiple initiation/shutdown, in case IEEE stack is shutdown and restarted. Each transport session is identified by a handle, which is a simple 64-bit integer. It is plug-in's (and therefore application's) responsibility to attribute an unique ID to each transport connection. If the transport supports reconnection (like HDP does), the ID should be re-used upon reconnection, which allows the IEEE state machine to resume communication instead of beginning from scratch. When the communication layer needs to send data, it just calls the plug-in write function, passing the handle and the APDU buffer. The application just needs to relate the handle ID with actual transport connection and send the data (or schedule for later dispatch). The application may use threads to deal with transport connections. A common technique is to dedicate one thread per connection; in that case, IEEE stack would run in multiple, possibly simultaneous, contexts. The stack is thread-safe and supports this case. The only caveat is callback to application whilst in communication thread's context; an UI application would not be allowed to update the UI immediately upon callback, handling it in main thread instead. All thread-aware frameworks (like Qt, Glib) have techniques to solve this issue. In the other hand, no application is forced to use threads. The IEEE stack does not create threads internally. For convenience and auto-testing, some plug-ins are already implemented. They can be found at src/communications/plugin folder. Most plug-ins are in src/communications/plugin/libcommplugin.a library. The src/communications/plugin/bluez/libbluezplugin.a is a BlueZ HDP plug-in. It is specifically used by the included D-Bus health service. IMPORTANT: while the rest of IEEE stack is platform-agnostic, each one of the supplied transport plug-ins is platform-dependent. For example, the BlueZ HDP plugin is written under the assumption that a Glib event loop will exist at runtime.
Page 10 of 23
The src/manager.c source code implements the IEEE manager. It depends (directly or indirectly) on all other components, save by data codecs. The whole stack, including the manager, is contained in a single static library: src/libantidote.a.
4.9 Applications
Antidote source tree contains not only the stack itself, but also two complete and functional applications that make use of stack and accept connections from actual IEEE agents. These applications serve specific purposes, as will be explained below, but they are also very valuable examples of how to actually use the stack. A developer wanting to link the stack with its own application just needs to follow the footsteps.
Page 11 of 23
D-Bus Glib bindings BlueZ HDP transport plug-in IEEE 11073 library
BlueZ dependency upon recent versions of kernel and D-Bus are due to the HDP profile
This service is offered because writing transport plug-ins for IEEE stack is not a trivial task, not because of IEEE stack itself, but because asynchronous communication is itself difficult. An application may use this D-Bus service instead of linking with IEEE stack directly, not having to deal with transport layer at all. This service was written with Linux in mind, with presence of D-Bus service; but it is perfectly possible to port the service to another architecture and to use another IPC mechanism to exchange data. Having the transport handling outside UI application would still be very convenient. More elaborate D-Bus types were carefully avoided. While it is very easy to send and receive e.g. arrays and dictionaries over D-Bus in Python language, it is very inconvenient to do the same things in statically-typed languages. With this in mind, structured messages like DIM objects, which seem to lend themselves to recursive D-Bus arrays and dictionaries, are encoded as XML and sent over plain D-Bus strings instead. D-Bus was chosen because it is well-supported in intended targets (Linux, Meego or Android) and most programming languages and UI frameworks have good D-Bus
Page 12 of 23
support, which means immediate access to the API regardless of how the application is developed. Details of D-Bus API are laid out at Appendix A.
#include manager.h #include "src/api/xml_encoder.h" #include "src/communication/plugin/bluez/plugin_bluez.h" We will be an IEEE manager; we will use XML codec to translate data from DIM objects, and we will employ the BlueZ HDP plug-in.
static void timer_reset_timeout(Context *ctx) { if (ctx->timeout_action.id) { g_source_remove(ctx->timeout_action.id); } } static gboolean timer_alarm(gpointer data) { void (*f)() = data; f(); return FALSE; } static int timer_count_timeout(Context *ctx) { ctx->timeout_action.id = g_timeout_add(ctx->timeout_action.timeout * 1000, timer_alarm, ctx->timeout_action.func); return ctx->timeout_action.id; Page 13 of 23
These functions implement timeout scheduling and triggering, in terms of Glib framework. Note that timeout_action is expressed in full seconds (as are all IEEE protocol timeouts) and it is multiplied by one thousand to meet Glib API that uses miliseconds. We also employ user_data pointer, pervasive to Glib, to call back IEEE stack when timeout is triggered. Note that functions are static; they do not need to be public. Their pointers will be passed to IEEE stack at main(). void new_data_received(Context *ctx, DataList *list) { DEBUG("Medical Device System Data"); char *data = xml_encode_data_list(list); if (data) { call_agent_measurementdata(ctx->id, data); free(data); } } void device_associated(Context *ctx, DataList *list) { DEBUG("Device associated"); char *data = xml_encode_data_list(list); if (data) { call_agent_associated(ctx->id, data); free(data); } } void device_disassociated(Context *ctx) { DEBUG("Device unassociated"); call_agent_disassociated(ctx->id); }
These functions are called back by IEEE stack when a new measurement data is available, a device has associated, and a device has disassociated, respectively. The call_agent_* functions (not shown in this document) just forward the message to interested parties, via D-Bus.
Page 14 of 23
These functions could be static as well; main() will participate their pointers to the plug-in (in this application; other apps could just use public functions). void network_app_has_recv(guint64 handle) { communication_read_input_stream(context_get((ContextId)handle)); }
This is a callback needed by BlueZ HDP plug-in. Therefore it is an interface defined between application and plug-in, completely outside IEEE stack. The function name could be any other. When data arrives, application notifies the IEEE stack by calling communication_read_input_stream(). Upon this call, the stack will ask the plug-in for data. Normally, at this stage, data has already been read from transport, and it is waiting in some kind of buffer. The handle identifies which channel has incoming data. As stated before, the 64-bit handle that identifies the channel is determined by the transport plug-in. It is plug-in's responsibility to ensure that this handle is unique and has 1:1 relationship with a channel (and there may be several channels with the same device, so device MAC address would not make a good handle in this case). Since the field of a 64-bit handle is virtually infinite, the plug-in implementation may well choose not to recycle handles. void device_connected(guint64 handle, const char* device) { call_agent_connected(handle, device); communication_transport_connect_indication(handle); } void device_disconnected(guint64 handle, const char* device) { call_agent_disconnected(handle, device); communication_transport_disconnect_indication(handle); }
Agent device connection and disconnection are out-of-band events, they don't map to a given APDU. So, the plug-in needs a callback to notify such events, so IEEE stack knows that a connection exists before the indication APDU arrives.
Page 15 of 23
In BSD/Sockets API, a recv() that returns zero means that connection has closed. Sending a zero-length APDU to IEEE stack is not understood as connection closure. That's why a specific disconnection callback is needed. If the particular transport handled by the plug-in follows that BSD/Sockets rule, it is plugin's responsibility to call (for example) device_disconnected or network_app_has_recv, depending on recv() return. Moreover, in the case of BlueZ HDP, socket-level disconnection does not mean actual channel closure. Only the HDP channel deletion, or failure to acquire a channel file descriptor, provoke a call to device_disconnected(), because the channel is truly really gone. These functions are public because the BlueZ HDP plug-in calls them by name, not via pointer. As happened with network_app_has_recv(), this is an implementation issue; IEEE stack does not impose their current names. gboolean device_reqmeasurement(Device *obj, GError** err) { DEBUG("device_reqmeasurement"); manager_request_measurement_data_transmission(obj->handle, NULL); return TRUE; } gboolean device_reqactivationscanner(Device *obj, gint handle, GError** err) { DEBUG("device_reqactivationscanner"); manager_set_operational_state_of_the_scanner(obj->handle, (HANDLE) handle, os_enabled, NULL); return TRUE; } gboolean device_reqdeactivationscanner(Device *obj, gint handle, GError** err) { DEBUG("device_reqdeactivationscanner"); manager_set_operational_state_of_the_scanner(obj->handle, (HANDLE) handle, os_disabled, NULL); return TRUE; } gboolean device_releaseassoc(Device *obj, GError** err) { DEBUG("device_releaseassoc"); manager_request_association_release(obj->handle); return TRUE; } gboolean device_abortassoc(Device *obj, GError** err) { DEBUG("device_abortassoc"); Page 16 of 23
manager_request_association_release(obj->handle); return TRUE; } gboolean device_get_segminfo(Device *obj, GError** err) { DEBUG("device_get_segminfo"); manager_request_get_segment_info(obj->handle, NULL); return TRUE; } gboolean device_get_segmdata(Device *obj, GError** err) { DEBUG("device_getsegmdata"); manager_request_get_segment_data(obj->handle, NULL); return TRUE; } gboolean device_clearsegmdata(Device *obj, GError** err) { DEBUG("device_clearsegmdata"); manager_request_clear_segments(obj->handle, NULL); return TRUE; }
These functions are not IEEE-related callbacks; they are called whenever the health service client requests something. They just forward the given request to IEEE stack, which in turn creates an APDU and sends it to the agent device. The Device pointer is not an IEEE stack typedef; it is a GObject. This channels representation was chosen in this particular application because it allows easy exporting through D-Bus when using Glib. Other platforms/frameworks would do this in a completely different way, and IEEE stack does not care about this. int main() { ... CommunicationPlugin plugin = communication_plugin(); /* Configure communications plugin */ plugin_bluez_setup(&plugin); plugin.timer_count_timeout = timer_count_timeout; plugin.timer_reset_timeout = timer_reset_timeout; manager_init(plugin); ManagerListener listener = MANAGER_LISTENER_EMPTY; listener.measurement_data_updated = &new_data_received; Page 17 of 23
listener.device_available = &device_associated; listener.device_unavailable = &device_disassociated; manager_add_listener(listener); manager_start(); mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_ref(mainloop); g_main_loop_run(mainloop); DEBUG("Main loop stopped"); manager_finalize(); app_clean_up(); dbus_shutdown(); return 0; }
This is the application's main() function. Here, we instantiate the communication plug-in and the IEEE manager. All callback functions that we had defined before are participated either to IEEE stack or BlueZ HDP plugin. The CommunicationPlugin is an IEEE stack typedef, because it uses the plug-in to carry out communication, but contents are completely defined by application (or by the plug-in). As an illustration, follows a small section of plug-in code: // implemented in top-level application void network_app_has_recv(guint64 handle); void device_connected(guint64, const char*); void device_disconnected(guint64, const char*); void plugin_bluez_setup(CommunicationPlugin *plugin) { plugin->network_init = init; plugin->network_get_apdu_stream = get_apdu; plugin->network_send_apdu_stream = send_apdu_stream; plugin->network_finalize = finalize; }
Note that BlueZ plug-in fills the CommunicationPlugin structure with its own functions, which may well be static. The IEEE stack will call those functions when it needs some plug-in service. In the other hand, BlueZ plug-in never calls IEEE stack functions directly. It calls application-defined public functions (network_app_has_recv() et al.) which in turn forward the even to the IEEE stack. This is a way to minimize coupling between plugin and IEEE stack.
Page 18 of 23
Page 19 of 23
application), or keep the service (desirable if there is more than one health application in the system) and replacing D-Bus with another IPC mechanism. Replacing D-Bus is perfectly possible but it would take a fair amount of work. Before resorting to pipes or sockets, it is advisable to look for another high-level IPC mechanism that platform may have adopted.
Path: any Interface: com.signove.health.manager Methods: void Connected(object device, string address)
Page 20 of 23
Called back when a channel connects. The first parameter is an opaque device path. The second parameters is a more meaningful device identification (in case of Bluetooth HDP devices, it is the MAC address). The received device object implements at least the interface com.signove.health.device. The name of first parameter (device) is a bit misleading because, if an agent device establishes two separate data channels with manager, there will be two different device objects. So, the object path actually maps 1:1 to channels and 1:n to device addresses. (In practice, most device objects are expected to use a single channel and extended configurations to send multiple-specialization data.) It is guaranteed that a new channel path will always be first sent via Connected() before it appears in Associated() or via any other agent call, so a channel can always be mapped to a MAC address by the application. void Associated(object device, string xmldata) Called back when a device (actually a channel) goes into associated state. The first parameter is the opaque device path. The application may relate it to the actual MAC address by keeping a map and update it upon Connected() and Disconnected() agent calls. The second parameter contains a XML representation of data sent by agent in association APDU. void MeasurementData(object device, string data) Called back when a measurement data event arrived. The second parameter is the XML representation of DIM objects. void DeviceAttributes(object device, string data) Called back when attributes have been fetched from device. This is the asynchronous response to an earlier call to device.RequestDeviceAttributes() (interface com.signove.health.device). void Disassociated(object device) Device (actually, a channel) entered into disassociated state. This normally means that device path will cease to exist, but it might happen that a new association happens over preexisting channel, so the application must wait for Disconnected() to release all channel information. void Disconnected(object device)
Page 21 of 23
Device (channel) disconnected. At this point, the device path ceases to exist and may be forgotten by the application, as well as any associated information. A new connection coming from the same device is guaranteed to have a different path. The device objects export an interface, too, for device- or channel-specific activities.
Path: any Interface: com.signove.health.device Methods: void Connect() void Disconnect() void RequestDeviceAttributes() Requests device attributes. The call returns immediately, and the actual result of this request will be returned asynchronously via agent. void RequestActivationScanner(int handle) void RequestDeactivationScanner(int handle) void RequestMeasurementDataTransmission() void ReleaseAssociation() void GetSegmentInfo() void GetSegmentData() void ClearSegmentData()
Page 22 of 23
Since this library uses pkg-tools, it is very easy to add it to an application that employs automake/autoconf as build system. At configure.in file, the following line is added: PKG_CHECK_MODULES(IEEE11073, antidote) At Makefile.am file, the compilation and link flags are added, like the example below: someapp_CFLAGS = @LIB1_CFLAGS@ @LIB2_CFLAGS@ @IEEE11073_CFLAGS@ someapp_LDADD = \ @LIB1_LIBS@ \ @LIB2_LIBS@ \ @IEEE11073_LIBS@ Lib1 and Lib2 are other hypothetical dependencies of the application. If the application does not use autoconf/automake, the compiler/linker flags and include paths must be configured manually. Since each environment may present different configurations, the best bet is to read the pkgconfig file (/usr/lib/pkgconfig/antidote.pc or /usr/local/lib/pkgconfig/antidote.pc) in order to get the appropriate configurations.
Page 23 of 23