carp_mobile_sensing 0.40.13 copy "carp_mobile_sensing: ^0.40.13" to clipboard
carp_mobile_sensing: ^0.40.13 copied to clipboard

Mobile Sensing Framework for Flutter. A software framework for collecting sensor data from the phone and attached wearable devices via probes. Can be extended.

example/lib/main.dart

/*
 * Copyright 2021 Copenhagen Center for Health Technology (CACHET) at the
 * Technical University of Denmark (DTU).
 * Use of this source code is governed by a MIT-style license that can be
 * found in the LICENSE file.
 */
import 'package:carp_serializable/carp_serializable.dart';
import 'package:carp_core/carp_core.dart';
import 'package:carp_mobile_sensing/carp_mobile_sensing.dart';
import 'package:flutter/material.dart' hide TimeOfDay;

void main() => runApp(CARPMobileSensingApp());

class CARPMobileSensingApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CARP Mobile Sensing Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ConsolePage(title: 'CARP Mobile Sensing Demo'),
    );
  }
}

class ConsolePage extends StatefulWidget {
  final String title;
  ConsolePage({super.key, required this.title});
  Console createState() => Console();
}

/// A simple UI with a console that shows the sensed data in a json format.
class Console extends State<ConsolePage> {
  String _log = '';
  Sensing? sensing;

  @override
  void initState() {
    sensing = Sensing();
    Settings().init().then((_) {
      sensing?.init().then((_) {
        log('Setting up study : ${sensing!.study}');
        log('Deployment status : ${sensing!.status}');
        Future.delayed(Duration(seconds: 1), () {
          sensing?.resume();
          log('\nSensing resumed ...');
        });
      });
    });

    super.initState();
  }

  @override
  void dispose() {
    sensing!.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        child: StreamBuilder(
          stream: sensing?.controller?.data,
          builder: (context, AsyncSnapshot<DataPoint> snapshot) {
            if (snapshot.hasData) _log += '${toJsonString(snapshot.data!)}\n';
            return Text(_log);
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: restart,
        tooltip: 'Restart study & probes',
        child: sensing!.isRunning ? Icon(Icons.pause) : Icon(Icons.play_arrow),
      ),
    );
  }

  void log(String msg) {
    setState(() {
      _log += '$msg\n';
    });
  }

  void clearLog() {
    setState(() {
      _log += '';
    });
  }

  void restart() {
    setState(() {
      if (sensing!.isRunning) {
        sensing!.pause();
        log('\nSensing paused ...');
      } else {
        sensing!.resume();
        log('\nSensing resumed ...');
      }
    });
  }
}

/// This class handles sensing logic.
///
/// This example is useful for creating a Business Logical Object (BLOC) in a
/// Flutter app. See e.g. the CARP Mobile Sensing App.
class Sensing {
  SmartPhoneClientManager? client;
  SmartphoneDeploymentController? controller;
  Study? study;

  /// Initialize sensing.
  Future<void> init() async {
    Settings().debugLevel = DebugLevel.debug;

    // Get the local protocol.
    StudyProtocol protocol =
        await LocalStudyProtocolManager().getStudyProtocol('ignored');

    // Create and configure a client manager for this phone, and
    // create a study based on the protocol.
    client = SmartPhoneClientManager();
    await client?.configure();
    study = await client?.addStudyProtocol(protocol);

    // Get the study controller and try to deploy the study.
    //
    // Note that if the study has already been deployed on this phone
    // it has been cached locally in a file and the local cache will
    // be used pr. default.
    // If not deployed before (i.e., cached) the study deployment will be
    // fetched from the deployment service.
    controller = client?.getStudyRuntime(study!);
    await controller?.tryDeployment(useCached: false);

    // Configure the controller.
    //
    // Notifications are enabled for demo purpose and if you add an AppTask to the
    // protocol below, you should see notifications on the phone.
    // However, nothing will happen when you click on them.
    // See the PulmonaryMonitor demo app for a full-scale example of how to use
    // the App Task model.
    await controller?.configure();

    // Listening on the data stream and print them as json.
    controller?.data.listen((data) => print(toJsonString(data)));
  }

  /// Is sensing running, i.e. has the study executor been resumed?
  bool get isRunning =>
      (controller != null) &&
      controller!.executor!.state == ExecutorState.resumed;

  /// Status of sensing.
  StudyStatus? get status => controller?.status;

  /// Resume sensing
  void resume() async => controller?.executor?.resume();

  /// Pause sensing
  void pause() async => controller?.executor?.pause();

  /// Remove sensing from this phone.
  void remove() async => controller?.remove();

  /// Dispose the entire deployment.
  void dispose() async => client?.dispose();

  /// Stop sensing.
  void stop() async => controller!.stop();
}

/// This is a simple local [StudyProtocolManager].
///
/// This class shows how to configure a [StudyProtocol] with Triggers,
/// Tasks and Measures.
class LocalStudyProtocolManager implements StudyProtocolManager {
  Future<void> initialize() async {}

  /// Create a new CAMS study protocol.
  Future<SmartphoneStudyProtocol> getStudyProtocol(String id) async {
    // Create a protocol. Note that the [id] is not used for anything.
    SmartphoneStudyProtocol protocol = SmartphoneStudyProtocol(
      ownerId: 'AB',
      name: 'Track patient movement',
      // dataEndPoint: SQLiteDataEndPoint(),
      dataEndPoint: FileDataEndPoint(bufferSize: 50 * 1000, zip: false),
    );

    // Define which devices are used for data collection.
    //
    // In this case, its only this phone.
    // See the CARP Mobile Sensing app for a full-blown example of how to
    // use connected devices (e.g., a Polar heart rate monitor) and online
    // services (e.g., a weather service).
    var phone = Smartphone();
    protocol.addMasterDevice(phone);

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

    // // // Add measures from the [DeviceSamplingPackage] and [SensorSamplingPackage]
    // // // sampling packages.
    // protocol.addTriggeredTask(
    //     ImmediateTrigger(),
    //     BackgroundTask()
    //       ..addMeasures([
    //         // Measure(type: SensorSamplingPackage.ACCELEROMETER),
    //         // Measure(type: SensorSamplingPackage.GYROSCOPE),
    //         Measure(type: DeviceSamplingPackage.MEMORY),
    //         Measure(type: DeviceSamplingPackage.BATTERY),
    //         Measure(type: DeviceSamplingPackage.SCREEN),
    //         Measure(type: SensorSamplingPackage.PEDOMETER),
    //         Measure(type: SensorSamplingPackage.LIGHT)
    //       ]),
    //     phone);

    // // Collect device info only once
    // protocol.addTriggeredTask(
    //     OneTimeTrigger(),
    //     BackgroundTask()
    //       ..addMeasure(Measure(type: DeviceSamplingPackage.DEVICE)),
    //     phone);

    // protocol.addTriggeredTask(
    //     IntervalTrigger(period: Duration(seconds: 5)),
    //     BackgroundTask(measures: [Measure(type: DeviceSamplingPackage.DEVICE)]),
    //     phone);

    protocol.addTriggeredTask(
        PeriodicTrigger(
            period: Duration(seconds: 6), duration: Duration(seconds: 2)),
        BackgroundTask(measures: [
          Measure(type: SensorSamplingPackage.USER_ACCELEROMETER)
        ]),
        phone);

    protocol.addTriggeredTask(IntervalTrigger(period: Duration(seconds: 5)),
        FunctionTask(function: () => print('Her går det godt....!')), phone);

    // // add a random trigger to collect device info at random times
    // protocol.addTriggeredTask(
    //     RandomRecurrentTrigger(
    //       startTime: TimeOfDay(hour: 07, minute: 45),
    //       endTime: TimeOfDay(hour: 22, minute: 30),
    //       minNumberOfTriggers: 2,
    //       maxNumberOfTriggers: 8,
    //     ),
    //     BackgroundTask()
    //       ..addMeasure(Measure(type: DeviceSamplingPackage.DEVICE)),
    //     phone);

    // // add a ConditionalPeriodicTrigger to check periodically
    // protocol.addTriggeredTask(
    //     ConditionalPeriodicTrigger(
    //       period: Duration(seconds: 10),
    //       resumeCondition: () {
    //         //
    //         return ('jakob'.length == 5);
    //       },
    //       pauseCondition: () => true,
    //     ),
    //     BackgroundTask()
    //       ..addMeasure(Measure(type: DeviceSamplingPackage.DEVICE)),
    //     phone);

    // Add an app task 2 minutes after deployment and make a notification.
    //
    // This App Task is added for demo purpose and you should see notifications
    // on the phone. However, nothing will happen when you click on it.
    // See the PulmonaryMonitor demo app for a full-scale example of how to use
    // the App Task model.
    //
    // Note also that the timezone measure is added. This entails that timezone
    // information is collected when the user 'executes' this app task.
    protocol.addTriggeredTask(
        ElapsedTimeTrigger(
          elapsedTime: const Duration(minutes: 2),
        ),
        AppTask(
          measures: [Measure(type: DeviceSamplingPackage.TIMEZONE)],
          type: BackgroundSensingUserTask.ONE_TIME_SENSING_TYPE,
          title: "Elapsed Time - 2 minutes",
          notification: true,
        ),
        phone);

    // // add an app task at exact date & time
    // protocol.addTriggeredTask(
    //     DateTimeTrigger(schedule: DateTime(2022, 5, 15, 21, 00)),
    //     AppTask(
    //       type: BackgroundSensingUserTask.ONE_TIME_SENSING_TYPE,
    //       title: "DateTime - 15/5 2022 at 21:00",
    //       notification: true,
    //     )..addMeasure(Measure(type: DeviceSamplingPackage.DEVICE)),
    //     phone);

    // // add an app task every hour
    // protocol.addTriggeredTask(
    //     IntervalTrigger(period: const Duration(hours: 1)),
    //     AppTask(
    //       type: BackgroundSensingUserTask.ONE_TIME_SENSING_TYPE,
    //       title: "Interval - Every hour",
    //       notification: true,
    //     )..addMeasure(Measure(type: DeviceSamplingPackage.DEVICE)),
    //     phone);

    // // add a cron job every day at 11:45
    // protocol.addTriggeredTask(
    //     CronScheduledTrigger.parse(cronExpression: '45 11 * * *'),
    //     AppTask(
    //       type: BackgroundSensingUserTask.ONE_TIME_SENSING_TYPE,
    //       title: "Cron - every day at 11:45",
    //       notification: true,
    //     )..addMeasure(Measure(type: DeviceSamplingPackage.DEVICE)),
    //     phone);

    // // add a random app task
    // protocol.addTriggeredTask(
    //     RandomRecurrentTrigger(
    //       startTime: TimeOfDay(hour: 8),
    //       endTime: TimeOfDay(hour: 20),
    //       minNumberOfTriggers: 2,
    //       maxNumberOfTriggers: 8,
    //     ),
    //     AppTask(
    //       type: BackgroundSensingUserTask.ONE_TIME_SENSING_TYPE,
    //       title: "Random - 2-8 times at 08:00-20:00",
    //       notification: true,
    //     )..addMeasure(Measure(type: DeviceSamplingPackage.DEVICE)),
    //     phone);

    return protocol;
  }

  Future<bool> saveStudyProtocol(String studyId, StudyProtocol protocol) async {
    throw UnimplementedError();
  }
}