Skip to content
CARP Docs
Ask AISystemLightDark
On this page

Domain model

Study protocols, triggers, tasks, measures, devices and measurements in the CAMS domain model.

Copy MarkdownOpen inOpen in GitHubView as MarkdownOpen in Scira AIOpen in ChatGPTOpen in ClaudeOpen in Cursor

The carp_mobile_sensing library uses and extends the carp_core domain model. The CAMS domain model has two parts:

This page covers the parts of the CARP domain models that CAMS uses: the domain classes related to a study protocol, data transformers, sampling configurations and collected measurements. Sampling produces Measurement streams with typed Data payloads.

Defining a study protocol

Data collection is configured as a StudyProtocol. A study protocol is always executed by one or more primary devices (for example a smartphone) and can use a set of connected devices (for example a heart rate monitor).

A study protocol holds a set of triggers. Each trigger can trigger one or more tasks, and each task holds a set of measures to collect. A measure can be configured with a sampling configuration.

ElementConfiguresExample
TriggerWhen to collect dataEvery hour
TaskHow to collect dataWhich device to use
MeasureWhat to collectLocation

Study protocol

A StudyProtocol holds the entire definition of the study, including the owner and name of the study, where to store the data, and which primary device is responsible for data collection.

// Create a study protocol storing data in a local SQLite database.
final protocol = SmartphoneStudyProtocol(
  ownerId: 'abc@dtu.dk',
  name: 'Track patient movement',
  dataEndPoint: SQLiteDataEndPoint(),
);

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

See Data managers for the available data managers and endpoints.

Tip

To describe a protocol and the nature of a study in more detail, specify a StudyDescription. It includes the study purpose, homepage, privacy and responsible researcher.

Triggers

Triggers are configured through a TriggerConfiguration and define the temporal configuration of a study, that is, when data sampling is done. CAMS includes these triggers:

TriggerDescription
ImmediateTriggerStarts sampling immediately.
OneTimeTriggerTriggers only once during a deployment.
DelayedTriggerDelays sampling for the specified delay, measured from sensing start.
ElapsedTimeTriggerDelays sampling for the specified delay, measured from study deployment start.
PassiveTriggerStarted manually with resume and paused with pause.
PeriodicTriggerRuns periodically with a fixed period.
DateTimeTriggerSchedules sampling at a specific date and time.
RecurrentScheduledTriggerSchedules sampling with a recurrent date/time pattern.
CronScheduledTriggerResumes and pauses sampling based on a cron expression.
SamplingEventTriggerStarts when a sampling event occurs, for example entering a geofence.
ConditionalSamplingEventTriggerResumes or pauses based on the result of a Dart function.
ConditionalPeriodicTriggerPeriodically checks whether an app-specific condition is met.
RandomRecurrentTriggerTriggers a random number of times within a defined daily period.
UserTaskTriggerTriggers based on the state of a UserTask.
NoUserTaskTriggerTriggers when a certain user task is not on the task list.
AppLifecycleTriggerTriggers when the life cycle of the app changes, as defined in AppLifecycleState.

To add your own triggers, see Adding new triggers.

Tasks and task controls

A task is defined through a TaskConfiguration and configures which measures to collect. For example, a task can sample accelerometer and gyroscope measures from the smartphone during a tremor assessment, or heart rate and ECG data from a wearable device.

CAMS has two basic types of tasks:

TaskTypeDescription
BackgroundTaskPassive sensingStarts collecting data as specified in its measures when the task is resumed. Used for background mobile sensing that does not involve the user.
AppTaskActive user taskInvolves the app, and potentially the user, in data sampling. Notifies the app when resumed or paused. For example, if a PeriodicTrigger resumes an AppTask with a survey measure, the survey is handed over to the app whenever the trigger resumes. See the app task model.

A TaskControl configures how to collect data and links a trigger, a task and a device. A task control controls (starts or stops) when a task is triggered on a device.

Measures

A Measure defines what to measure. It specifies the type of data to collect, which at runtime maps to a specific probe that can collect this type of data. See Measure types for the available types.

CAMS follows a reactive programming model: all sampled data is collected in streams by listening to the underlying sensors. This is configured with the Measure class. Some probes, however, need to poll data and must be configured with a sampling frequency. For example, the MemoryProbe needs a sampling frequency, set with an IntervalSamplingConfiguration in the sampling schema.

Most CAMS users do not need this low-level configuration. It is handled through a SamplingConfiguration and the default sampling schemas, described in Sampling configurations and schemas.

Sampling configurations and schemas

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

To change a default configuration, override it with the overrideSamplingConfiguration property of a measure. For example, the default configuration of the light measure is 10 seconds of sampling every 5 minutes:

  // Override the sampling configuration of the light measure in the protocol.
  protocol.addTaskControl(
    ImmediateTrigger(),
    BackgroundTask(
      measures: [
        Measure(type: SensorSamplingPackage.AMBIENT_LIGHT)
          ..overrideSamplingConfiguration = PeriodicSamplingConfiguration(
            interval: const Duration(minutes: 10),
            duration: const Duration(seconds: 20),
          ),
      ],
    ),
    phone,
  );

Devices

Devices are configured in the study protocol. The CAMS domain model has two types of devices:

  • Primary devices aggregate, synchronize and optionally upload incoming data received from one or more connected devices (potentially just itself). In CAMS, the smartphone is typically the primary device.
  • Connected devices are any devices connected to a primary device, such as a wearable heart rate monitor.

CAMS and its sampling packages include predefined device descriptors, such as the Smartphone primary device descriptor or the PolarDevice device descriptor in the Polar sampling package.

A study protocol that uses a Polar device as a connected device of the phone:

// Create a study protocol
StudyProtocol protocol = StudyProtocol(...);

// Define which devices are used for data collection - both phone and polar devices
var phone = Smartphone();
var polar = PolarDevice(roleName: 'hr-sensor');

// Add both devices
protocol
  ..addPrimaryDevice(phone)
  ..addConnectedDevice(polar, 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()
    ..addMeasure(Measure(type: SensorSamplingPackage.STEP_EVENT))
    ..addMeasure(Measure(type: SensorSamplingPackage.AMBIENT_LIGHT))
    ..addMeasure(Measure(type: DeviceSamplingPackage.SCREEN_EVENT))
    ..addMeasure(Measure(type: DeviceSamplingPackage.BATTERY_STATE)),
  phone,
);

// Add a background task that immediately starts collecting HR and ECG data
// from the Polar device.
protocol.addTaskControl(
  ImmediateTrigger(),
  BackgroundTask(
    measures: [
      Measure(type: PolarSamplingPackage.HR),
      Measure(type: PolarSamplingPackage.ECG),
    ],
  ),
  polar,
);  

As a wearable device, the Polar device is a connected device, connected to the phone as the primary device.

Note

Connected devices must connect to the phone (for example over BLE) at runtime before data can be collected from them. The DeviceController handles this.

Protocol examples

A study protocol is a list of triggers and tasks with a set of measures:

Background sensingTriggersAppTask
final protocol =
    SmartphoneStudyProtocol(
        ownerId: 'AB',
        name: 'Tracking steps, light, screen, and battery',
        dataEndPoint: SQLiteDataEndPoint(),
      )
      ..addPrimaryDevice(Smartphone())
      ..addParticipantRole(ParticipantRole('Participant'))
      ..addTaskControl(
        ImmediateTrigger(),
        BackgroundTask(
          measures: [
            Measure(type: SensorSamplingPackage.STEP_EVENT),
            Measure(type: SensorSamplingPackage.AMBIENT_LIGHT),
            Measure(type: DeviceSamplingPackage.SCREEN_EVENT),
            Measure(type: DeviceSamplingPackage.BATTERY_STATE),
            Measure(type: DeviceSamplingPackage.DEVICE_INFORMATION),
          ],
        ),
      );
final protocol = SmartphoneStudyProtocol(...);
var phone = Smartphone();
protocol.addPrimaryDevice(phone);

// Collect timezone info every time the app restarts.
protocol.addTaskControl(
  ImmediateTrigger(),
  BackgroundTask(measures: [Measure(type: DeviceSamplingPackage.TIMEZONE)]),
  phone,
);

// Collect device info only once, when this study is deployed.
protocol.addTaskControl(
  OneTimeTrigger(),
  BackgroundTask(
    measures: [Measure(type: DeviceSamplingPackage.DEVICE_INFORMATION)],
  ),
  phone,
);

// Collect IMU data every 10 secs for 1 sec.
protocol.addTaskControl(
  PeriodicTrigger(period: const Duration(seconds: 10)),
  BackgroundTask(
    measures: [
      Measure(type: SensorSamplingPackage.ACCELERATION),
      Measure(type: SensorSamplingPackage.ROTATION),
    ],
    duration: const Duration(seconds: 1),
  ),
  phone,
);

// Check periodically for a condition to trigger the task
protocol.addTaskControl(
  ConditionalPeriodicTrigger(
    period: Duration(seconds: 20),
    triggerCondition: () => ('jakob'.length == 5),
  ),
  BackgroundTask()
    ..addMeasure(Measure(type: DeviceSamplingPackage.DEVICE_INFORMATION)),
  phone,
);
final protocol = SmartphoneStudyProtocol(...);
var phone = Smartphone();
protocol.addPrimaryDevice(phone);

// Add a cron job that triggers an app task every day at 11:45
// This will show a notification in the OS and when the user click this
// the app task is triggered and the device information is collected.
protocol.addTaskControl(
  CronScheduledTrigger.parse(cronExpression: '45 11 * * *'),
  AppTask(
    type: AppTask.SENSING_TYPE,
    title: "Click here to collect device information",
    measures: [Measure(type: DeviceSamplingPackage.DEVICE_INFORMATION)],
    notification: true,
  ),
  phone,
);

Measurements and data

CAMS models data according to the data subsystem 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, BatteryState holds the state of the phone's battery, such as battery level and charging status. A data object is measured either at a point in time or over a time span.

All measurement and data objects can be serialized to and from JSON:

Point in timeTime span

sensorStartTime is the timestamp of the measurement in microseconds, and __type holds the data type.

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

Ambient light is measured over a time period, so both sensorStartTime and sensorEndTime are specified.

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

Timestamps

  • CARP recommends microseconds (over milliseconds) since epoch. This gives 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, the phone may translate it to microseconds before storing it in a measurement.
  • If the sensor does not provide a timestamp, the phone time-stamps the data through the Measurement.fromData method.
  • A Data object may itself contain time information, modeled with the Dart DateTime format; see for example the dateFrom properties in HealthData in the health package. These timestamps use Zulu (UTC) format, such as 2023-11-15T22:00:25.864650Z.

Quick links

Page · DocumentationCARP DocumentationPage · DocumentationIntroductionPage · DocumentationQuickstartPage · DocumentationTerminologyPage · DocumentationArchitecture