Skip to content
CARP Docs
Ask AISystemLightDark
On this page

Using CAMS

Define a study protocol, deploy it, run sampling with the client manager and consume the collected measurements.

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

This page describes the runtime flow of CARP Mobile Sensing (CAMS). Complete the installation first.

CAMS can run fully local on the device, including protocol creation and deployment. The flow is:

  1. Define a study protocol with the triggers, tasks, devices and measures to collect.
  2. Deploy the protocol to a local or remote DeploymentService, such as SmartphoneDeploymentService, to create a study deployment (optional).
  3. Configure the client manager, then add and deploy the study.
  4. Resume or pause data sampling for all studies or for each study.
  5. Consume or save the collected measurements from the measurements stream of one or more studies.

Define a study protocol

In CAMS, sensing is configured in a SmartphoneStudyProtocol. Create one with the Dart API directly in your app:

final phone = Smartphone();
final protocol =
    SmartphoneStudyProtocol(
        ownerId: 'AB',
        name: 'Tracking steps, light, screen, and battery',
        dataEndPoint: SQLiteDataEndPoint(),
      )
      ..addPrimaryDevice(phone)
      ..addParticipantRole(ParticipantRole('Participant'))
      ..addTaskControl(
        DelayedTrigger(delay: const Duration(seconds: 10)),
        BackgroundTask(
          measures: [
            Measure(type: SensorSamplingPackage.STEP_EVENT),
            Measure(type: SensorSamplingPackage.AMBIENT_LIGHT),
            Measure(type: DeviceSamplingPackage.SCREEN_EVENT),
            Measure(type: DeviceSamplingPackage.BATTERY_STATE),
          ],
        ),
        phone,
        Control.Start,
      );

This protocol collects steps, ambient light, and screen and battery events from the phone, and stores them in the local SQLite database. Data sampling starts after a delay of 10 seconds.

Local protocols

Use the SmartphoneStudyProtocol.local constructor for a local protocol that:

  • only collects simple measures in the background
  • only uses the phone as a device
  • only has one participant
  • stores data locally in the SQLite database
var protocol = SmartphoneStudyProtocol.local(
  name: 'Tracking steps, light, screen, and battery',
  measures: [
    Measure(type: SensorSamplingPackage.STEP_EVENT),
    Measure(type: SensorSamplingPackage.AMBIENT_LIGHT),
    Measure(type: DeviceSamplingPackage.SCREEN_EVENT),
    Measure(type: DeviceSamplingPackage.BATTERY_STATE),
  ],
);

Related

Deploy the protocol

This step is optional. A study protocol is deployed through a DeploymentService:

// Use the on-phone deployment service.
DeploymentService deploymentService = SmartphoneDeploymentService();

// Create a study deployment using the protocol.
var status = await deploymentService.createStudyDeployment(protocol);

Configure the client manager

The client manager SmartPhoneClientManager handles the runtime management of studies. It is a singleton, so you can always access it with SmartPhoneClientManager().

The simplest configuration is:

await SmartPhoneClientManager().configure();

configure() accepts these options:

OptionDescription
registrationUnique device registration for this phone.
deploymentServiceDeployment backend. Default: the local SmartphoneDeploymentService.
dataCollectorFactoryCustom collector factory. Default: the standard DeviceController.
enableNotificationsShow task notifications. Default: true.
enableBackgroundModeRun sampling in the background. Default: true. Android only.
backgroundNotificationTitle, backgroundNotificationTextText for the Android background notification.
askForPermissionsRequest package permissions automatically. Default: true.
DefaultCustomized
final client = SmartPhoneClientManager();
await client.configure();
final client = SmartPhoneClientManager();

await client.configure(
  deploymentService: deploymentService,
  enableNotifications: true,
  enableBackgroundMode: true,
  backgroundNotificationTitle: 'CAMS is running',
  backgroundNotificationText: 'Collecting sensing data in background',
  askForPermissions: false, // app handles permissions manually
);

Tip

Set askForPermissions: false when your app needs a custom permission flow or staged consent screens.

Add and deploy studies

To deploy a study on the client, get a study deployment from the deployment service, based on the protocol. Configure the client to use this deployment service, then add and deploy the study:

// Use the on-phone deployment service.
DeploymentService deploymentService = SmartphoneDeploymentService();

// Create a study deployment using a protocol.
var status = await deploymentService.createStudyDeployment(protocol);

// Create and configure a client manager to use the deploymentService.
SmartPhoneClientManager client = SmartPhoneClientManager();
await client.configure(deploymentService: deploymentService);

// Add a study based on the deployed protocol to the client
final study = await client.addStudy(
  SmartphoneStudy(
    studyDeploymentId: status.studyDeploymentId,
    deviceRoleName: phone.roleName,
  ),
);

/// Deploy the study to the client.
SmartPhoneClientManager().tryDeployment(
  study.studyDeploymentId,
  study.deviceRoleName,
);

With the local SmartphoneDeploymentService, you can write this more compactly with addStudyFromProtocol:

// Create and configure a client manager for this phone.
await SmartPhoneClientManager().configure();

// Create a study based on the protocol.
var study = await SmartPhoneClientManager().addStudyFromProtocol(protocol);

/// Deploy the study.
await SmartPhoneClientManager().tryDeployment(
  study.studyDeploymentId,
  study.deviceRoleName,
);

The same flow as a single statement:

SmartPhoneClientManager().configure().then(
  (_) => SmartPhoneClientManager()
      .addStudyFromProtocol(protocol)
      .then(
        (study) => SmartPhoneClientManager().tryDeployment(
          study.studyDeploymentId,
          study.deviceRoleName,
        ),
      ),
);

The client manager can handle multiple studies. Add each one to the client:

var study_1 = await client.addStudyFromProtocol(protocol_1);
var study_2 = await client.addStudyFromProtocol(protocol_2);
var study_3 = await client.addStudyFromProtocol(protocol_3);

Control data sampling

Control data sampling with the resume and pause methods:

// Resume data sampling for all studies added to the client.
SmartPhoneClientManager().resume();

// .. and pause all data sampling for all studies again.
SmartPhoneClientManager().pause();

Calling resume() or pause() on the client manager resumes or pauses all studies added to the client. For fine-grained control, use the SmartphoneStudyController of each study:

SmartphoneStudyController? controller = client.getStudyController(study);
controller?.resume();

Stop a study and dispose the client

stopStudy permanently stops a study. A stopped study cannot be started again. Call dispose to dispose of the client, typically in the Flutter dispose method.

// Permanently stop the study.
// This will mark the study as stopped and remove it from the client manager.
SmartPhoneClientManager().stopStudy(
  study.studyDeploymentId,
  study.deviceRoleName,
);

// Dispose of the client (e.g. in Flutter dispose())
client.dispose();

Run a single local protocol

To run just one local protocol, configure, add, deploy and resume in one statement:

SmartPhoneClientManager().configure().then(
  (_) => SmartPhoneClientManager()
    .addStudyFromProtocol(protocol)
    .then(
      (study) => SmartPhoneClientManager()
        .tryDeployment(study.studyDeploymentId, study.deviceRoleName)
        .then((_) => SmartPhoneClientManager().resume()),
    ),
);

Consume measurements

All sampled data is available in the measurements stream, on both the client and each study controller:

  // Listening on the measurements stream.
  client.measurements.listen((measurement) {
    // Do something with the measurement, e.g. print the json.
    print(toJsonString(measurement));
  });

measurements is a standard Dart Stream, so you can subscribe to, filter and map it as needed:

SubscribeFilterMap
// subscribe to the stream of measurements
var subscription = controller?.measurements.listen((Measurement measurement) {
  // do something w. the measurement
  ...
});

... 

// Cancel the subscription.
await subscription?.cancel();
// listen on ambient light measurements only
controller?.measurements
    .where((measurement) =>
        measurement.data.jsonType == SensorSamplingPackage.AMBIENT_LIGHT)
    .listen((measurement) => print(measurement));
// map measurements to JSON and then print
controller?.measurements
    .map((measurement) => measurement.toJson())
    .listen((json) => print(json));

Next steps

Quick links

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