-
Notifications
You must be signed in to change notification settings - Fork 28
3. Using CARP Mobile Sensing
A CAMS study is configured, deployed, executed, and used in the following steps:
- Define a
StudyProtcol
. - Deploy this protocol to a
DeploymentService
. - Get a study deployment for the phone and start executing this study deployment using a
SmartPhoneClientManager
. - Use the generated data locally in the app or specify how and where to store or upload it using a
DataEndPoint
.
Note that as a mobile sensing framework running on a phone, CAMS could be limited to support 3-4. However, to support the 'full cycle', CAMS also supports 1-2. This allows for local creation, deployment, and execution of study protocols (which in many applications have shown to be useful).
In CAMS, a sensing protocol is configured in a StudyProtocol
.
Below is a simple example of how to set up a protocol that sense step counts (pedometer
), ambient light (light
), screen activity (screen
), and power consumption (battery
).
// import package
import 'package:carp_core/carp_core.dart';
import 'package:carp_mobile_sensing/carp_mobile_sensing.dart';
void example() async {
// Create a study protocol storing data in files.
SmartphoneStudyProtocol protocol = SmartphoneStudyProtocol(
ownerId: 'AB',
name: 'Track patient movement',
dataEndPoint: FileDataEndPoint(
bufferSize: 500 * 1000,
zip: true,
encrypt: false,
),
);
// Define which devices are used for data collection.
// In this case, its only this smartphone.
Smartphone phone = Smartphone();
protocol.addMasterDevice(phone);
// Add a background task that immediately starts collecting step counts,
// ambient light, screen activity, and battery level.
protocol.addTriggeredTask(
ImmediateTrigger(),
BackgroundTask()
..addMeasures([
Measure(type: SensorSamplingPackage.PEDOMETER),
Measure(type: SensorSamplingPackage.LIGHT),
Measure(type: DeviceSamplingPackage.SCREEN),
Measure(type: DeviceSamplingPackage.BATTERY),
]),
phone);
The above example defines a simple SmartphoneStudyProtocol
which will store data in a file locally on the phone using a FileDataEndPoint
. Sampling is configured by using the pre-defined SamplingSchema
from the SensorSamplingPackage
and DeviceSamplingPackage
packages.
This sampling schema contains a set of default sampling configurations for how to sample the different measures. These measures are triggered immediately when sensing is started (see below), and runs automatically in the background using a BackgroundTask
.
Sampling can be configured in a very sophisticated ways, by specifying different types of triggers, tasks, measures, and sampling configurations. See the CAMS domain model for an overview.
You can write your own DataEndPoint
definitions and coresponding DataManager
s for uploading data to your own data endpoint. See the wiki on how to add a new data manager.
A device deployment specifies how a study protocol is executed on a specific device - in this case a smartphone.
A StudyProtocol
can be deployed to a DeploymentService
which handles the deployment of protocols for different devices. CAMS comes with a simple deployment service (the SmartphoneDeploymentService
) which runs locally on the phone. This can be used to deploy a protocol and get back a MasterDeviceDeployment
, which can be executed on the phone.
...
// Use the on-phone deployment service.
DeploymentService deploymentService = SmartphoneDeploymentService();
// Create a study deployment using the protocol.
StudyDeploymentStatus status =
await deploymentService.createStudyDeployment(protocol);
...
A study deployment for a phone (master device) is handled by a SmartPhoneClientManager
.
This client manager is able to create a StudyDeploymentController
, which controls the execution of a study deployment.
...
String studyDeploymentId = ... // any id obtained e.g. from an invitation
String deviceRolename = ... // the rolename of this phone in the protocol;
// Create and configure a client manager for this phone.
SmartPhoneClientManager client = SmartPhoneClientManager();
await client.configure(deploymentService: deploymentService);
// Create a study object based on the deployment id and the rolename
Study study = Study(
status.studyDeploymentId,
status.masterDeviceStatus!.device.roleName,
);
// Add the study to the client manager and get a study runtime to control this deployment
await client.addStudy(study);
SmartphoneDeploymentController? controller = client.getStudyRuntime(study);
// Deploy the study on this phone.
await controller?.tryDeployment();
// Configure the controller and start sampling.
await controller?.configure();
controller?.start();
...
CAMS will now take care of executing the study, i.e. collect the data as specified and upload it to the specified data backend (as zipped files on the local device in this example).
- Appendix A lists the supported measure types (i.e., what data the framework can collect).
- Appendix C lists available data backends and describes how data can be stored both locally on the device and on remote servers (like CARP and Google Firebase).
Normally, once data sampling has been started (by calling the controller.start()
method), data sampling will run automatically in the background, and you don't need to do more.
However, if you want to control the data sampling from your app, there are several lifecycle methods available.
For example, you can pause and resume sampling;
controller.executor?.pause();
...
controller.executor?.resume();
You can also pause / resume specific probes;
controller.executor
?.lookupProbe(SensorSamplingPackage.ACCELEROMETER)
.forEach((probe) => probe.pause());
If you want to configure or dynamically adapt your sampling this can be done by changing the configuration of a Measure
in a study. For example, in the following we first configure a measure of type LIGHT
and then adapt it while sampling is running. This change is propagated to the probe, which then restarts sampling according to the new measure configuration.
// override configuration details of a light measure
Measure lightMeasure = Measure(
type: SensorSamplingPackage.LIGHT,
)..overrideSamplingConfiguration = PeriodicSamplingConfiguration(
interval: const Duration(minutes: 10),
duration: const Duration(seconds: 20),
);
// add it to the study to start immediately
protocol.addTriggeredTask(
ImmediateTrigger(),
BackgroundTask(name: 'Light')..addMeasure(lightMeasure),
phone,
);
...
// start the sampling as above
...
// adapt the measure
lightMeasure
..overrideSamplingConfiguration = PeriodicSamplingConfiguration(
interval: const Duration(minutes: 5),
duration: const Duration(seconds: 10),
);
// Restart the light probe(s)
controller.executor
?.lookupProbe(SensorSamplingPackage.LIGHT)
.forEach((probe) => probe.restart());
// Alternatively mark the deplyment as changed - calling hasChanged()
// this will force a restart of the entire sampling
controller.deployment?.hasChanged();
Finally, you can permanently stop a study by calling stop
:
// Once the sampling has to stop, e.g. in a Flutter dispose() methods, call stop.
// Note that once a sampling has stopped, it cannot be restarted.
controller.stop();
The approaches above assume that you use the framework to collect data as part of your app and just sends it to one of the available data backends, without using the data in your app.
However, if you want to use the collected data in your app while it is being collected, you can access data via the data
stream. For example, the following code would listen on all data events from the study created above and print it to the Dart console.
controller.data.forEach(print);
This data
stream is a standard Dart Stream
and you can hence do all the normal stream (reactive) operations on it. For example, if you only wants to listen on CARP events, write;
controller.data
.where((dataPoint) => dataPoint.data!.format.namespace == NameSpace.CARP)
.listen((event) => print(event));
Or if you only want to listen to light
events, write;
controller.data
.where((dataPoint) =>
dataPoint.data!.format.toString() == SensorSamplingPackage.LIGHT)
.listen((event) => print(event));
In general you can listen to events using the Dart StreamSubscription
;
StreamSubscription<DataPoint> subscription =
controller.data.listen((DataPoint dataPoint) {
// do something w. the datum, e.g. print the json
print(JsonEncoder.withIndent(' ').convert(dataPoint));
});
The data
stream originates from each individual Probe
and you can listen directly to a probe (actually, the StudyExecutor
in the StudyController
is merely a super-probe, merging all data
stream from the underlying probes).
CAMS supports data transformation via two concepts:
-
DatumTransformer which can transform one type of
Datum
to another type ofDatum
. -
Transformer Schemas which hold a set of
DatumTransformer
s that can map from the native CARP namespace to another namespace.
Transformation is configured by specifying which data format is to be used in the data endpoint specified in the study protocol. For example, to transform a data into another format like Open mHealth (OMH), the study protocol would be specified like this:
// Create a study protocol storing data in files using the OMH data format
SmartphoneStudyProtocol protocol = SmartphoneStudyProtocol(
ownerId: 'AB',
name: 'Track patient movement',
dataEndPoint: FileDataEndPoint(
bufferSize: 500 * 1000,
zip: true,
encrypt: false,
dataFormat: NameSpace.OMH,
),
);
See also the section on how to add data transformers of your own.
CARP Mobile Sensing comes with a default PrivacySchema
which implements privacy protection and anonymization methods on the data being collected before it is uploaded to a data backend. For example, one-way hashing of phone numbers before they are stored.
To use this schema, simply specify this schema when configuring your SmartphoneDeploymentController
, as shown below:
// configure the controller with the default privacy schema and start sampling
await controller?.configure(
privacySchemaName: PrivacySchema.DEFAULT,
);
controller?.start();
This will create a controller that uses the default privacy schema. This schema can be extended or you can write your own privacy schemas and use these instead.