Applying the Data Distribution Service in an IoT Healthcare System

In my last post, I described how the Data Distribution Service (DDS) standard is used to provide ubiquitous access to clinical measurements. In this post, I will be introducing the foundations of this enabling technology that can allow healthcare systems to reap the full potential of the Internet of Things.

The abstraction at the foundation of DDS is a global data space. Think of it as the virtual area where data on a patient is going to be gathered, shared, analyzed and acted upon. DDS applications—sensors, devices, applications, etc.—autonomously, anonymously, securely and efficiently cooperate by reading and writing Topics on this global data space – where a unique name, a type and a set of qualities of service (QoS) identify a Topic. Applications that join the global data space automatically discover their mutual existence as well as their interest (i.e. Topics produced and consumed). This information is then used to optimize the protocol and communication path used for efficient and high performance data sharing.

Conceptually the global data space is active, in the sense that it can retain data for late joiners and make it available even if the producer is no longer available. For example, a doctor can take advantage of this feature to see the history of clinical measurements produced by a given device. This feature is commonly called temporal decoupling as the producer and the consumer don’t need to be running at the same time in order to share data.

DDS’ global data space implementations are required to be fully distributed – no single point of failure or single point of bottleneck. This makes it possible for DDS-based system to scale out well and to experience very low latencies and high throughput. As an example, on contemporary hardware connected by a 1 Gbps Ethernet network, DDS has an inter-node latency of ~30 usec and can easily distributed several millions samples per second.

Anatomy of a DDS Application
Structurally a DDS application is composed by different entities (see picture below). The DomainParticipant provide access to a DDS domain. A domain represents a global data space instance and is identified by a natural number, 0, 1, 2, …, n. A domain can be further organized into partitions – partitions are used to organize data within the domain. Publishers and Subscribers control access to the partitions. Finally, the DataWriter and DataReader are used by applications to write and read data into the partitions associated with their respective Publisher and Subscriber.

As DDS is independent of the programming language and hardware and software platform, it allows applications written in different languages and deployed on different OS to exchange data – in other terms DDS takes care of representing data in a programming language and hardware independent manner, dealing with endianness and data layout. This means that a device developed using C++ and running Linux on an ARM processor is going to be able to send and receive data from a device developed using C# and running Windows Embedded on an x86 processor—and it won’t matter if the devices are in the same room, same facility, same state or same country.

Understanding DDS Topics
As I mentioned before, DDS uses Topics to represent the information shared between applications. So far, I’ve told you that a Topic has (1) a unique name, (2) a type used to represent the information associated with the Topic in a programming language, and (3) a set of qualities of service – but there is more. Topic can be keyed or keyless. When available, they key is used to identify a specific topic instances. Let’s try to understand this with an example. Imagine that we want to make available on DDS the data produced by an oxymeter. In this case the first thing we need to decide is how to model the data with a Topic type. Below you see the Topic type that may represent the information provided by a hypothetical oxymeter. The topic type is defined using IDL, but XML or Java could also be used.

struct Oxymetry {
string deviceId;
long spO2;
long bpm;
long pleth;
};
#pragma keylist Oximetry deviceId

Notice that with the #pragma directive I’ve defined the attribute deviceId as the key for the type. This attribute allows to uniquely identifying the device that produces the data. In DDS terms, each unique value of the deviceId attribute identifies a Topic instance. DDS provides specific life cycle information associated to instances, such as informing you when a new instance is available as well as when is no more in the system. When Topic instances are tied to device, the life-cycle management is particularly useful in learning about the operational status of the device itself.

The values assumed by a specific instance are called samples. Thus, DDS applications write samples belonging to an instance of a specific topic.

Reading and Writing Data
As mentioned above, in DDS data is read and written using DataWriter and DataReader. Specifically, a Datareader will receive in its local cache (a local projection of the global data space) the data written by the DataWriter if the two match. A Datareader and a DataWriter match if they have been defined for the same Topic and with compatible QoS. The set of data received by a Datareader can be controlled through content filters. Content filters are expressions defined with SQL WHERE syntax that predicate Topic attributes. For instance, the filter expression “spO2 < 85” could be used to receive the oxymeter data only when the saturation is below 85percent.

Quality of Service
DDS provides 22 Quality of Service policies that give very fine control over local resource, network utilization, traffic prioritization, and data management. For the complete list refer to the DDS Tutorial.

Your First DDS-Based Pulse Oxymeter
At this point we are ready to write our first oxymeter application. We will write the application that would run on the device and also another application that would display the readings on the console. Below I’ll provide you with the C++ code, but you can also check the Java and Scala version online.

Oxymeter
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cout << “USAGE:\n\t oxymeter <device-id>” << std::endl;
return 1;
}

std::string deviceId(argv[1]);
DomainParticipant dp(0);
Topic<Oxymetry> topic(dp, “TOxymetry”);
Publisher pub(dp);

DataWriter<Oxymetry> dw(dp, topic);

// isActive() becomes false when the device is turning off.
while (isActive()) {
auto o = get_oximetry(deviceId);
// write data
dw.write(o);
std::this_thread::sleep_for(std::chrono::seconds(UPDATE_PERIOD));
}
return 0;
}

Logger
int main(int argc, char* argv[]) {
DomainParticipant dp(0);
Topic<Oxymetry> topic(dp, “TOxymetry”);
Subscriber sub(dp);

DataReader<Oxymetry> dr(dp, topic);

// Create the waitset
WaitSet ws;
// Create a ReadCondition for our data reader
// and configure it for new data
ReadCondition rc(dr, DataState::new_data());
// attache the condition
ws += rc;

while (true) {
ws.wait();
auto samples = dr.read();

std::for_each(samples.begin(),
samples.end(),
[](const dds::sub::Sample<Oxymetry>& s) {
std::cout << s.data() << std::endl;
});
}
return 0;
}

This example reveals how simple is to use DDS to share data. Beside the simplicity, as explained in my last blog post, once in DDS, the data can be made available across any device and shared at any scale. DDS-based platforms such as PrismTech’s Vortex provide a good example of how this can be done.

If you want to learn more about DDS, feel free to read the DDS Tutorial or check out the various educational slides.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s