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, data transformers, sampling configurations, and collected measurements.

Defining a Study Protocol

Model devices, triggers, tasks, measures, and sampling configuration.

Measurements and Data

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 (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).
carp_mobile_sensing_architecture

Study protocol

A StudyProtocol holds the entire definition of the study to be done, including the owner and name of the study, where to store the data, and which “primary device” that 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 a list of available data manager and endpoints.
You can specify more elaborate descriptions of a protocol and the nature of a study by specifying a StudyDescription which includes information on study purpose, homepage, privacy, and responsible researcher.

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:
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.
PassiveTriggerCan be started 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).
ConditionalSampling EventTriggerResumes/pauses based on the result of a Dart function.
ConditionalPeriodicTriggerPeriodically checks if 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.
You can extend CAMS with your own custom triggers - see Adding New Triggers.

Tasks and task controls

A task is defined via a TaskConfiguration and configures 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. 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 the AppTask Model on how the AppTask model is used.
A TaskControl configures how to collect data and specifies the linkage between 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. A measure specify 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 of CAMS and can therefore be handled via a so-called SamplingConfiguration. 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 Measure Types.

Examples

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. Examples are included below.
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),
          ],
        ),
      );

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 default samplingSchemes for the measures included in this package. However, if you want to change these default configurations, they can be overridden. For example, the default configuration of the light measure is 10 seconds sampling every 5 minutes. This configuration can be “overridden” using the overrideSamplingConfiguration property of a measure:
  // 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 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 monitor.
CAMS and its sampling packages come with pre-defined device descriptors, like the Smartphone primary device descriptor or the PolarDevice device descriptor in the Polar sampling package. A study protocol using a Polar device would be configured like this:
// 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,
);  
Note that as a wearable device, the Polar device is a “connected device”, i.e., connected to the “master device” which is the phone.
Connected devices needs to connect to the phone (e.g., using BLE) during runtime before data can be collected from them. This is handled by the DeviceController. See Handling Devices.

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. A data object is either measured at a point in time or a time span. All measurement and data objects can be serialized to/from JSON:
The sensorStartTime property is the timestamp of the measurement (in microseconds) and the __type property holds the data type.
{
  "sensorStartTime": 1700082464783244,
  "data": {
   "__type": "dk.cachet.carp.batterystate",
   "batteryLevel": 92,
   "batteryStatus": "charging"
  }
}
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 time-stamping 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.