Skip to content

2. Domain Model

Jakob E. Bardram edited this page Jun 3, 2025 · 161 revisions

The carp_mobile_sensing library uses and extends the carp_core domain model. Hence, the CAMS domain model contains two parts:

This section introduces the parts of the overall CARP Domain Models which are used in CAMS. This includes domain classes related to a study protocol, study deployment, data transformers, sampling configurations, and collected measurements.

Defining a Study Protocol

Data collection is configured as a StudyProtocol. A study protocol is always executed by one or more primary devices (e.g., a smartphone) and can use a set of connected devices (e.g., a heart rate monitor). Overall, a study protocol holds a set of triggers, which can trigger one or more tasks, which again hold a set of measures to collect. A measure can be configured using a sampling configuration. This is illustrated below. A trigger configures "when" to collect data (e.g., every hour), a task configures "how" to collect data (e.g., which device to use), and a measure configures "what" to collect (e.g., location).

     StudyProtocol --* Trigger --* Task --* Meassure
           |                                    |
           *                                    |
         Device                        SamplingConfiguration

Study Protocol

A StudyProtocol holds the entire definition of the study to be done. As shown in the code example below, a study protocol defines the owner (id) and name of the study, plus the so-called "primary device" that is responsible for data collection.

// Create a study protocol - owner ID and name are required
SmartphoneStudyProtocol protocol = SmartphoneStudyProtocol(
  ownerId: 'AB',
  name: 'Track patient movement',
);

// Define which devices are used for data collection.
// In this case, it is only a smartphone
var phone = Smartphone();
protocol.addPrimaryDevice(phone);

Triggers

Triggers are configured via a TriggerConfiguration and define the temporal configuration of a study, i.e., WHEN data sampling is done. CAMS comes with a set of built-in triggers:

  • ImmediateTrigger - a trigger that starts sampling immediately
  • OneTimeTrigger - a trigger that triggers only once during a deployment.
  • DelayedTrigger - a trigger that delays sampling for the specified delay measured from the start of sensing.
  • ElapsedTimeTrigger - a trigger that delays sampling for the specified delay measured from the start of the study deployment.
  • PassiveTrigger - a trigger that can be started manually by calling the resume method and paused by calling the pause method.
  • PeriodicTrigger - a trigger that runs periodically with a fixed period
  • DateTimeTrigger - a trigger that can be scheduled on a specific date and time
  • RecurrentScheduledTrigger - a trigger that can be scheduled on a specific date and time with a recurrent pattern
  • CronScheduledTrigger - a trigger that resumes and pauses sampling based on a cron job specification.
  • SamplingEventTrigger - a trigger that starts when a certain sampling event happens, such as when the phone enters a certain geofence.
  • ConditionalSamplingEventTrigger - a trigger that resumes/pauses based on the result of a Dart function.
  • ConditionalPeriodicTrigger - a trigger that periodically checks if an application-specific triggering condition is met.
  • RandomRecurrentTrigger - a trigger that triggers a random number of times within a defined period in a day.
  • UserTaskTrigger - a trigger that triggers based on the state of a UserTask.
  • NoUserTaskTrigger - a trigger that triggers if a certain user task in NOT on the task list.

Tasks

A task is defined via a TaskConfiguration and configures HOW to collect data, i.e., what device to use and what measures to collect. For example, a task can specify to sample accelerometer and gyroscope measures from the smartphone during a tremor assessment, or sample heart rate and ECG data from a wearable device. In the code listing below, a task is defined to sample Bluetooth, connectivity status, accelerometer, and gyroscope data from the smartphone.

The basic task is defined in carp_core as a TaskConfiguration, but in CAMS this base class is rarely used. Instead, CAMS comes with two basic types of tasks - a passive sensing task and an active user task:

  • BackgroundTask - a task that starts collecting data as specified in the measures, when the task is resumed. Hence, this type of task is used in background mobile sensing data sampling, which does not involve the user.
  • AppTask - a task that involves the app (and hence potentially the user) in data sampling. An app task notifies the app when resumed (or paused). For example, if a PeriodicTrigger resumes an AppTask with a survey measure, this survey is handed over to the app whenever the trigger resumes. See Section 4 on how the AppTask model is used.

Measures

A Measure defines WHAT to measure i.e., the specific data stream. A measure is a configuration of the type of data to collect, which on runtime maps to a specific probe that can collect this type of data.

Since CAMS follows a reactive programming model, all sampled data is collected in streams by listening to the underlying sensors. This is configured using the Measure class. However, some probes need to ‘poll’ data, and such probes need to be configured with a sampling frequency. For example, the MemoryProbe needs to be configured with a sampling frequency. For this purpose, the IntervalSamplingConfiguration is used in the sampling schema (see below).

This low-level configuration is, however, often irrelevant to most of the users (programmers) of CAMS and can therefore be handled via a so-called DataTypeSamplingScheme. In the code listing below, the common sampling schema is used to get a set of measures with the most ‘common’ configuration. Sampling schemes are further described below.

A list of available measure types in CAMS can be found in Appendix A.

Example

Now that we know the study protocol domain model, we're ready to create the study protocol - which basically just is a list of triggers of tasks with a set of measures. An example of creating a StudyProtocol is shown here:

// Create a study protocol - owner id and name are required
SmartphoneStudyProtocol protocol = SmartphoneStudyProtocol(
  ownerId: 'AB',
  name: 'Track patient movement',
);

// Define which devices are used for data collection.
// In this case, its only this smartphone
var phone = Smartphone();
protocol.addPrimaryDevice(phone);

Once the study protocol is created, then tasks and measures can be added.

// Automatically collect step count, ambient light, screen activity, and
// battery level from the phone. Sampling is delayed by 10 seconds.
protocol.addTaskControl(
  DelayedTrigger(delay: const Duration(seconds: 10)),
  BackgroundTask(measures: [
    Measure(type: SensorSamplingPackage.STEP_COUNT),
    Measure(type: SensorSamplingPackage.AMBIENT_LIGHT),
    Measure(type: DeviceSamplingPackage.SCREEN_EVENT),
    Measure(type: DeviceSamplingPackage.BATTERY_STATE),
  ]),
  phone,
);

The addTaskControl() method is a convenient way to add a trigger and a task in one go (there is more to this method, but let it be for now).

Sampling Configuration and Schemas

CAMS comes with a set of default sampling configurations for all measures. These configurations are collected in DataTypeSamplingScheme schemas for each sampling package. For example, the SensorSamplingPackage provides a map of samplingSchemes.

However, if you want to change these default configurations, they can be overridden. A measure can be configured using a SamplingConfiguration. For example, the light measure can be configured to sample light at a certain interval and duration, such as once every 10 minutes for 20 seconds. This is done by "overriding" the default configuration, using the overrideSamplingConfiguration property of a measure. This can be done in the constructor of a Measure:

  // specify sampling configuration of a light measure
  var lightMeasure = Measure(
      type: SensorSamplingPackage.AMBIENT_LIGHT,
      samplingConfiguration: PeriodicSamplingConfiguration(
        interval: const Duration(minutes: 10),
        duration: const Duration(seconds: 20),
      ));

  // add it to the protocol
  protocol.addTaskControl(ImmediateTrigger(),
      BackgroundTask(name: 'Light')..addMeasure(lightMeasure), phone);

Devices

Devices need to be configured in the study protocol. In the CAMS domain model, there a two types of devices:

  • Primary devices, which aggregates, synchronizes and optionally uploads incoming data received from one or more connected devices (potentially just itself). The smartphone in CAMS is typically a master device.
  • Connected devices, which are any devices connected to a primary device, such as a wearable heart rate tracker.

CAMS and its sampling packages come with pre-defined device descriptors, like the Smartphone primary device descriptor or the ESenseDevice device descriptor in the eSense sampling package.

A study protocol using an eSense device would be configured like this:

...
  
  // define and add devices that are used for data collection - both phone and eSense
  var phone = Smartphone();
  var eSense = ESenseDevice(
    deviceName: 'eSense-0223',
    samplingRate: 10,
  );

  protocol
    ..addPrimaryDevice(phone)
    ..addConnectedDevice(eSense, phone);

...

  // Add a background task that immediately starts collecting step counts,
  // ambient light, screen activity, and battery level from the phone.
  protocol.addTaskControl(
      ImmediateTrigger(),
      BackgroundTask(measures: [
        Measure(type: SensorSamplingPackage.STEP_COUNT),
        Measure(type: SensorSamplingPackage.AMBIENT_LIGHT),
        Measure(type: DeviceSamplingPackage.SCREEN_EVENT),
        Measure(type: DeviceSamplingPackage.BATTERY_STATE),
      ]),
      phone);

  // Add a background task that immediately starts collecting eSense button and
  // sensor events from the eSense device.
  protocol.addTaskControl(
      ImmediateTrigger(),
      BackgroundTask(measures: [
        Measure(type: ESenseSamplingPackage.ESENSE_BUTTON),
        Measure(type: ESenseSamplingPackage.ESENSE_SENSOR),
      ]),
      eSense);

Note that as a wearable device, the eSense device is a "connected device", i.e., connected to the "master device" which is the phone.

Study Deployment

As we will discuss in more detail in section 3. Using CARP Mobile Sensing, a study protocol is added to a DeploymentService using the createStudyDeployment method.

Once a study protocol is added to the deployment service, a CAMS client ("master device") can get "its" subset of a study protocol from the deployment service via the getDeviceDeploymentFor method. This returns a PrimaryDeviceDeployment, which is a copy of the study protocol with all the tasks to be done by this specific master device.

Measurements and Data

CAMS models data according to the data sub-system of the CARP Core domain model. Data collected during sampling is modeled as a Measurement, which holds a Data object of a specific DataType.

In CAMS, all data objects are of type Data. For example, the BatteryState holds the state of the phone's battery, like battery level and charging status.

All measurement (and data) objects can be serialized to JSON, and the battery measurement looks like:

{
  "sensorStartTime": 1700082464783244,
  "data": {
   "__type": "dk.cachet.carp.batterystate",
   "batteryLevel": 92,
   "batteryStatus": "charging"
  }
}

The sensorStartTime property is the timestamp of the measurement (in microseconds) and the __type property holds the data type.

Similarly, a measurement of ambient light will look like this:

 {
  "sensorStartTime": 1700082626178562,
  "sensorEndTime": 1700082636184268,
  "data": {
   "__type": "dk.cachet.carp.ambientlight",
   "meanLux": 20.76923076923077,
   "stdLux": 0.6965680875490359,
   "minLux": 20,
   "maxLux": 22
 }

Since ambient light is measured over a time period, both sensorStartTime and sensorEndTime is specified.

Timestamps

In CARP in general, we recommend using microseconds (over milliseconds) since epoch. This provides a greater precision, especially in high-frequency sampling.

Sensor timestamps (start and end time) are provided by the sensor, if available. Depending on the unit of the sensor timestamp, this timestamp may be translated to microseconds by the phone before being stored in a measurement. If the sensor does not provide a timestamp, then the phone is used for timestamping by using the Measurement.fromData method.

Sometimes, the Data object may contain time information, which can be modeled using the Dart DateTime format. See for example the dateFrom properties in the HealthData in the health package. In these cases, timestamps in Zulu (UTC) format are used, such as 2023-11-15T22:00:25.864650Z.

Clone this wiki locally