CARP Mobile Sensing for Flutter, including mobile sensing framework, data backend support, and the CARP mobile sensing app.

Overview

CARP-Mobile-Sensing-Vertical

This repo hold the source code for the CACHET Research Platform (CARP) Mobile Sensing (CAMS) Flutter software. It contains the source code for CACHET first-party (i.e., developed by the core CACHET team) CAMS framework, its packages, and example apps.

In addition, the CARP team maintain a set of Flutter plugins (mainly) for sensing purposes. Flutter plugins enable access to platform-specific APIs. For more information about plugins, and how to use them, see the Flutter Packages description.

These plugins are also available on pub.

Software Components

These are the available CARP Mobile Sensing Flutter components in this repository.

Component Description http://pub.dev/
Core Basic components
carp_core The CARP core domain model pub package
carp_mobile_sensing The main CARP Mobile Sensing Framework pub package
Packages Data sampling packages
carp_apps_package App sampling package (installed apps, app usage) pub package
carp_connectivity_package Connectivity sampling package (bluetooth, wifi, connectivity) pub package
carp_communication_package Communication sampling package (phone, sms) pub package
carp_context_package Context sampling package (location, activity, weather) pub package
carp_audio_package Audio sampling package (audio, noise) pub package
carp_movisens_package Movisens Move & ECG sampling package (movement, MET-level, ECG) pub package
carp_esense_package Sampling package for the eSense ear plug device (button pressed & movement) pub package
carp_survey_package Sampling package for collecting survey data from ResearchPackage pub package
carp_health_package Sampling package for collecting health data from Apple Health and Google Fit pub package
Backends Backend data upload components
carp_webservices Flutter API for CARP web services pub package
carp_backend Support for uploading data to a CARP data backend as JSON. pub package
carp_firebase_backend Support for uploading data to Firebase as both zipped files and JSON data pub package
Utilities Misc. CAMS utilities
carp_study_generator A simple command line interface (CLI) to upload study protocols, informed consent and localization files to the CARP backend. pub package
Apps Misc. mobile sensing demo apps
carp_mobile_sensing_app Demonstrates how basic mobile sensing can be implemented in a Flutter app using CAMS.
pulmonary_monitor_app Demonstrates how user tasks (aka. AppTask) are supported in CAMS.

Documentation

The overall documentation on the software architecture of CARP Mobile Sensing, and how to use and extend it is available on this github wiki. Each of the specific packages also contain more specific documentation on how each package is used in the framework (e.g. how the Firebase data backend package is to be used).

Issues

Please check existing issues and file any new issues, bugs, or feature requests in the carp.sensing-flutter repo.

Contributing

Contributing is not entirely in place yet. However, if you wish to contribute a change to any of the existing components in this repo, please review our contribution guide, and send a pull request.

Comments
  • [mobile sensing app] ActivityRecognition header file not found

    [mobile sensing app] ActivityRecognition header file not found

    It seems like the ActivityRecognition plugin is not properly linked in XCode since #import <activity_recognition/ActivityRecognitionPlugin.h> results in the following error:

    'activity_recognition/ActivityRecognitionPlugin.h' file not found

    I've opened an issue on the GIthub page of the plugin (link)

    bug 
    opened by thomasnilsson 13
  • IntervalTrigger not working

    IntervalTrigger not working

    When I use interval trigger it works for a few seconds and then it stops. For example when I have the following code in local_study_protocol_managaer.dart:

        protocol.addTriggeredTask(
            IntervalTrigger(period: Duration(milliseconds: 300)),
            BackgroundTask()
              ..addMeasure(Measure(type: SensorSamplingPackage.PEDOMETER)),
            phone);
    

    I get the following output:

    Output
    Performing hot restart...                                               
    Restarted application in 1.058ms.
    I/flutter (16264): [CAMS INFO] Time zone set to Europe/Brussels
    I/flutter (16264): [CAMS INFO] Settings initialized
    I/flutter (16264): [CAMS INFO] SensingBLoC initialized
    I/flutter (16264): [CAMS INFO] Initializing Sensing - mode: DeploymentMode.LOCAL
    I/flutter (16264): ===========================================================
    I/flutter (16264):   CARP Mobile Sensing (CAMS) - SmartPhoneClientManager
    I/flutter (16264): ===========================================================
    I/flutter (16264):   deployment service : SmartphoneDeploymentService
    I/flutter (16264):    device controller : DeviceController [6]
    I/flutter (16264):            device ID : SP1A.210812.016
    I/flutter (16264):    available devices : (Smartphone, LocationService, AirQualityService, ..., PolarDevice, ESenseDevice)
    I/flutter (16264): ===========================================================
    I/flutter (16264): [CAMS INFO] Adding study to SmartPhoneClientManager - Study - studyDeploymentId: asdf222, deviceRoleName: masterphone
    I/flutter (16264): SmartphoneDeploymentController - Study deployment 'asdf222' successfully deployed.
    I/flutter (16264): [CAMS INFO] Saving deployment to file '/data/user/0/dk.cachet.carp_mobile_sensing_app/app_flutter/carp/deployments/asdf222/deployment.json'.
    I/flutter (16264): [CAMS INFO] Configuring SmartphoneDeploymentController
    I/flutter (16264): [CAMS INFO] Initializing device manager, type: dk.cachet.carp.protocols.domain.devices.Smartphone, descriptor.: Smartphone - roleName: masterphone, isMasterDevice: true
    I/flutter (16264): [CAMS DEBUG] SmartphoneDeviceManager - setting device status: DeviceStatus.initialized
    I/flutter (16264): [CAMS INFO] AppTaskController - Restoring task queue from file '/data/user/0/dk.cachet.carp_mobile_sensing_app/app_flutter/carp/tasks.json'.
    I/flutter (16264): [CAMS INFO] AwesomeNotificationController initialized.
    I/flutter (16264): [CAMS INFO] Asking for permission for all measure types.
    D/permissions_handler(16264): Bluetooth permission missing in manifest
    I/flutter (16264): [CAMS INFO] Permissions for Permission.location : PermissionStatus.granted
    I/flutter (16264): [CAMS INFO] Permissions for Permission.bluetoothScan : PermissionStatus.granted
    I/flutter (16264): [CAMS INFO] Permissions for Permission.locationAlways : PermissionStatus.granted
    I/flutter (16264): [CAMS INFO] Permissions for Permission.activity_recognition : PermissionStatus.granted
    I/flutter (16264): [CAMS INFO] Permissions for Permission.microphone : PermissionStatus.granted
    I/flutter (16264): [CAMS INFO] Permissions for Permission.camera : PermissionStatus.granted
    D/permissions_handler(16264): Bluetooth permission missing in manifest
    I/flutter (16264): [CAMS INFO] Permissions for Permission.bluetooth : PermissionStatus.denied
    I/flutter (16264): [CAMS INFO] Permissions for Permission.bluetoothConnect : PermissionStatus.granted
    I/flutter (16264): [CAMS INFO] Initializing SQLiteDataManager...
    I/flutter (16264): [CAMS DEBUG] SQLiteDataManager - SQLite DB created
    I/flutter (16264): [CAMS INFO] SQLite DB file path : /data/user/0/dk.cachet.carp_mobile_sensing_app/databases/carp-data-2022-11-12-10-11-27-934492Z.db
    I/flutter (16264): [CAMS INFO] SmartphoneDeviceManager - Trying to connect to device of type: dk.cachet.carp.protocols.domain.devices.Smartphone and id: SP1A.210812.016
    I/flutter (16264): [CAMS DEBUG] SmartphoneDeviceManager - setting device status: DeviceStatus.connected
    I/flutter (16264): [CAMS INFO] SmartphoneDeviceManager - Trying to connect to device of type: dk.cachet.carp.protocols.domain.devices.Smartphone and id: SP1A.210812.016
    I/flutter (16264): [CAMS DEBUG] SmartphoneDeviceManager - setting device status: DeviceStatus.connected
    I/flutter (16264): [CAMS INFO] SmartphoneDeviceManager - Trying to connect to device of type: dk.cachet.carp.protocols.domain.devices.Smartphone and id: SP1A.210812.016
    I/flutter (16264): [CAMS DEBUG] SmartphoneDeviceManager - setting device status: DeviceStatus.connected
    I/flutter (16264): [CAMS INFO] SmartphoneDeviceManager - Trying to connect to device of type: dk.cachet.carp.protocols.domain.devices.Smartphone and id: SP1A.210812.016
    I/flutter (16264): [CAMS DEBUG] SmartphoneDeviceManager - setting device status: DeviceStatus.connected
    I/flutter (16264): [CAMS INFO] SmartphoneDeviceManager - Trying to connect to device of type: dk.cachet.carp.protocols.domain.devices.Smartphone and id: SP1A.210812.016
    I/flutter (16264): [CAMS DEBUG] SmartphoneDeviceManager - setting device status: DeviceStatus.connected
    I/flutter (16264): [CAMS INFO] SmartphoneDeviceManager - Trying to connect to device of type: dk.cachet.carp.protocols.domain.devices.Smartphone and id: SP1A.210812.016
    I/flutter (16264): [CAMS DEBUG] SmartphoneDeviceManager - setting device status: DeviceStatus.connected
    I/flutter (16264): [CAMS INFO] Initializing StudyDeploymentExecutor - configuration: SmartphoneDeployment - studyDeploymentId: asdf222, device: masterphone, title: CAMS App - Sensing Coverage Study, responsible: Alex B. Christensen,
     deployment: SmartphoneDeployment - studyDeploymentId: asdf222, device: masterphone, title: CAMS App - Sensing Coverage Study, responsible: Alex B. Christensen
    I/flutter (16264): [CAMS INFO] Initializing TriggeredTaskExecutor - configuration: TriggeredTask - triggerId: 0, task: Task #5, targetDeviceRoleName: masterphone, deployment: SmartphoneDeployment - studyDeploymentId: asdf222, device
    : masterphone, title: CAMS App - Sensing Coverage Study, responsible: Alex B. Christensen
    I/flutter (16264): [CAMS INFO] Initializing IntervalTriggerExecutor - configuration: Instance of 'IntervalTrigger', deployment: SmartphoneDeployment - studyDeploymentId: asdf222, device: masterphone, title: CAMS App - Sensing Covera
    ge Study, responsible: Alex B. Christensen
    I/flutter (16264): [CAMS INFO] Initializing BackgroundTaskExecutor - configuration: BackgroundTask - name: Task #5, measures size: 1, deployment: SmartphoneDeployment - studyDeploymentId: asdf222, device: masterphone, title: CAMS Ap
    p - Sensing Coverage Study, responsible: Alex B. Christensen
    I/flutter (16264): [CAMS INFO] Initializing PedometerProbe - configuration: Measure - type: dk.cachet.carp.pedometer, deployment: SmartphoneDeployment - studyDeploymentId: asdf222, device: masterphone, title: CAMS App - Sensing Cove
    rage Study, responsible: Alex B. Christensen
    I/flutter (16264): ===============================================================
    I/flutter (16264):   CARP Mobile Sensing (CAMS) - SmartphoneDeploymentController
    I/flutter (16264): ===============================================================
    I/flutter (16264):  deployment id : asdf222
    I/flutter (16264):  deployed time : 2022-11-12 11:11:27.848311
    I/flutter (16264):        user id : 04dcd470-626a-11ed-99e1-097a86af6173
    I/flutter (16264):       platform : Android
    I/flutter (16264):      device ID : SP1A.210812.016
    I/flutter (16264):   data manager : SQLiteDataManager
    I/flutter (16264):  data endpoint : SQLiteDataEndPoint - type: SQLITE, dataFormat: dk.cachet.carp
    I/flutter (16264):         status : Deployed
    I/flutter (16264): ===============================================================
    I/flutter (16264): [CAMS INFO] Starting data sampling ...
    I/flutter (16264): END OF STAAAAAAART
    I/flutter (16264): [CAMS INFO] Sensing initialized
    I/ViewRootImpl@f29f95e[MainActivity](16264): ViewPostIme pointer 0
    I/ViewRootImpl@f29f95e[MainActivity](16264): ViewPostIme pointer 1
    I/flutter (16264): [CAMS INFO] Resuming StudyDeploymentExecutor
    I/flutter (16264): [CAMS INFO] Resuming TriggeredTaskExecutor
    I/flutter (16264): [CAMS INFO] Resuming IntervalTriggerExecutor
    I/flutter (16264): [CAMS INFO] Resuming BackgroundTaskExecutor
    I/flutter (16264): [CAMS INFO] Resuming PedometerProbe
    D/SensorManager(16264): registerListener :: 191, step_counter  Non-wakeup, 0, 0,
    I/flutter (16264): {
    I/flutter (16264):  "carp_header": {
    I/flutter (16264):   "study_id": "asdf222",
    I/flutter (16264):   "device_role_name": "masterphone",
    I/flutter (16264):   "trigger_id": "0",
    I/flutter (16264):   "user_id": "04dcd470-626a-11ed-99e1-097a86af6173",
    I/flutter (16264):   "start_time": "2022-11-12T10:11:30.474651Z",
    I/flutter (16264):   "data_format": {
    I/flutter (16264):    "namespace": "dk.cachet.carp",
    I/flutter (16264):    "name": "pedometer"
    I/flutter (16264):   }
    I/flutter (16264):  },
    I/flutter (16264):  "carp_body": {
    I/flutter (16264):   "id": "6097ac60-6272-11ed-a5c8-83045561948c",
    I/flutter (16264):   "timestamp": "2022-11-12T10:11:30.470122Z",
    I/flutter (16264):   "step_count": 33339
    I/flutter (16264):  }
    I/flutter (16264): }
    I/flutter (16264): [CAMS DEBUG] SQLiteDataManager - writing data point to SQLite - id: 1, type: dk.cachet.carp.pedometer
    I/flutter (16264): [CAMS INFO] Resuming BackgroundTaskExecutor
    I/flutter (16264): [CAMS INFO] Resuming PedometerProbe
    D/SensorManager(16264): unregisterListener
    D/SensorManager(16264): registerListener :: 191, step_counter  Non-wakeup, 0, 0,
    I/flutter (16264): {
    I/flutter (16264):  "carp_header": {
    I/flutter (16264):   "study_id": "asdf222",
    I/flutter (16264):   "device_role_name": "masterphone",
    I/flutter (16264):   "trigger_id": "0",
    I/flutter (16264):   "user_id": "04dcd470-626a-11ed-99e1-097a86af6173",
    I/flutter (16264):   "start_time": "2022-11-12T10:11:31.470726Z",
    I/flutter (16264):   "data_format": {
    I/flutter (16264):    "namespace": "dk.cachet.carp",
    I/flutter (16264):    "name": "pedometer"
    I/flutter (16264):   }
    I/flutter (16264):  },
    I/flutter (16264):  "carp_body": {
    I/flutter (16264):   "id": "613042e0-6272-11ed-b646-ad59d42c6d61",
    I/flutter (16264):   "timestamp": "2022-11-12T10:11:31.470129Z",
    I/flutter (16264):   "step_count": 33339
    I/flutter (16264):  }
    I/flutter (16264): }
    I/flutter (16264): [CAMS DEBUG] SQLiteDataManager - writing data point to SQLite - id: 2, type: dk.cachet.carp.pedometer
    I/flutter (16264): [CAMS INFO] Resuming BackgroundTaskExecutor
    I/flutter (16264): [CAMS INFO] Resuming PedometerProbe
    D/SensorManager(16264): unregisterListener
    D/SensorManager(16264): registerListener :: 191, step_counter  Non-wakeup, 0, 0,
    I/flutter (16264): {
    I/flutter (16264):   "user_id": "04dcd470-626a-11ed-99e1-097a86af6173",
    I/flutter (16264):   "start_time": "2022-11-12T10:11:32.467803Z",
    I/flutter (16264):   "data_format": {
    I/flutter (16264):    "namespace": "dk.cachet.carp",
    I/flutter (16264):    "name": "pedometer"
    I/flutter (16264):   }
    I/flutter (16264):  },
    I/flutter (16264):  "carp_body": {
    I/flutter (16264):   "id": "61c86430-6272-11ed-b223-1b4b39d8795f",
    I/flutter (16264):   "timestamp": "2022-11-12T10:11:32.467102Z",
    I/flutter (16264):   "step_count": 33339
    I/flutter (16264):  }
    I/flutter (16264): }
    I/flutter (16264): [CAMS DEBUG] SQLiteDataManager - writing data point to SQLite - id: 3, type: dk.cachet.carp.pedometer
    I/flutter (16264): [CAMS INFO] Resuming BackgroundTaskExecutor
    I/flutter (16264): [CAMS INFO] Resuming PedometerProbe
    D/SensorManager(16264): unregisterListener
    I/flutter (16264): [CAMS INFO] Pausing BackgroundTaskExecutor
    I/flutter (16264): [CAMS INFO] Pausing PedometerProbe
    D/SensorManager(16264): registerListener :: 191, step_counter  Non-wakeup, 0, 0,
    D/SensorManager(16264): unregisterListener
    I/flutter (16264): [CAMS INFO] Pausing BackgroundTaskExecutor
    I/flutter (16264): [CAMS WARNING]  Trying to pause a BackgroundTaskExecutor in a state where this cannot be done - state: ExecutorState.paused
    I/flutter (16264): [CAMS INFO] Pausing BackgroundTaskExecutor
    I/flutter (16264): [CAMS WARNING]  Trying to pause a BackgroundTaskExecutor in a state where this cannot be done - state: ExecutorState.paused
    I/flutter (16264): [CAMS INFO] Pausing BackgroundTaskExecutor
    I/flutter (16264): [CAMS WARNING]  Trying to pause a BackgroundTaskExecutor in a state where this cannot be done - state: ExecutorState.paused
    

    So I have output for a few seconds, and then it completely stops. The same holds for when I use the intervaltrigger with ContextSamplingPackage.LOCATION or DeviceSamplingPackage.DEVICE . The app is still open and I see no errors. I used the example app from GitHub.

    bug help wanted question 
    opened by sc00n 10
  • passive sensing problem

    passive sensing problem

    I tried to use the package for passive sensing, but I can't get it to work.

    I applied the best practice tips (https://github.com/cph-cachet/carp.sensing-flutter/wiki/5.-Best-Practice):

    image

    I used carp_mobile_sensing: 0.9.2. To test the package I simply created a new flutter project and added the following to _MyHomePageState

      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        initCarp();
      }
      
      
      void initCarp() async {
        // Create a study using a local file to store data
        Study study = Study("2", '[email protected]',
            name: 'A study collecting ..',
            dataEndPoint: FileDataEndPoint()
              ..bufferSize = 500 * 1000
              ..zip = true
              ..encrypt = false);
        
        // but delay the sampling by 10 seconds
        study.addTriggerTask(
            ImmediateTrigger(),
            AutomaticTask(name: 'Sensor Task')
              ..addMeasure(PeriodicMeasure(MeasureType(NameSpace.CARP, SensorSamplingPackage.LIGHT),
                  frequency: const Duration(seconds: 10), duration: const Duration(milliseconds: 100))));
    
    
        // Create a Study Controller that can manage this study.
        StudyController controller = StudyController(study);
    
        // await initialization before starting/resuming
        await controller.initialize();
        controller.resume();
    
        // listening and print all data events from the study
        controller.events.forEach(print);
      }
    
    

    I also adapted the AndroidManifest as mentioned in the Readme.

    When I read the generated files (e.g. carp/data/2/carp-data-2020-09-24-17-35-33-471360.json) I see that the app clearly logs every 10 second when I am in the app. When the app is open but the screen is closed I barely get any log and when the app is closed I get no logs at all.

    I tested with Android emulator (Pixel 2, API 28) and my phone (Samsung Galaxy A40, Android 9).

    Am I doing something wrong/can I change something, or is passive sensing just an illusion with current Android battery restrictions?

    Thank you for your help!

    Merijn

    help wanted documentation question 
    opened by sc00n 7
  • All app files gone on iOS

    All app files gone on iOS

    On almost all our iOS participants we had an issue that after a while of mobile sensing all the app related files (in NSDocumentDirectory on iOS) were gone (removed). As this does not happen to our app when mobile sensing is turned off I suspect it has something to do with the sensing.

    I don't think that there is anything wrong with the CARP code that causes this. I just want to warn the community that this might happen and see if anybody else encountered this problem. We suspect however that it has something to do with gathering a big chunk of data and saving many files with sensing data on the phone.

    We now implemented two fixes which seem (until now, fingers crossed) to solve the problem:

    1. We make sure all mobile sensing files are uploaded regularly and deleted afterwards. Even if there is no Wifi. We think the problem might be caused by some storage issue. Uploading and deleting the files often may solve this.
    2. Giving the most important files in the NSDocumentDirectory the key NSURLIsExcludedFromBackupKey. The reasoning here would be that some files are backed up and removed from the phone when too many files are written in the NSDocumentDirectory.

    For now, we have no idea which of both solutions has helped :).

    enhancement 
    opened by sc00n 6
  • [carp_communication_package 0.12.0] Using this package implies requesting restricted permissions

    [carp_communication_package 0.12.0] Using this package implies requesting restricted permissions

    Motivation

    We wish to publish our app, which uses various CARP package, to the Google Play Store. However, initially the app was rejected because we tried to collect SMS texts and call logs which Google considers to be a restricted group of permissions for which you need a valid reason to access those. Unfortunately, research is not in the list of exceptions. Hence, we would like to remove collection of SMS texts and call logs while still keeping collection of calendar information (all in the carp_communication_package).

    The problem

    Upon adding carp_communication_package 12.0.0 (or any later or earlier version for that matter), various restricted permissions such as android.permission.READ_SMS and android.permissions.REAL_CALL_LOG are automatically added because they reside in the call_log and sms plugin that the package uses. As a consequence, the app is automatically rejected from the Play Store. Unfortunately, this problem persists even when removing call log and SMS texts from the sensing list, since these plugins are loaded immediately upon registering carp_communication_package, regardless of whether the plugins will be used or not.

    Resolution

    I'm not sure whether there could be any solution for this problem since it would almost mean disassembling the package itself. Perhaps there exists some code which prevents the permission from being requested or the plugin from being loaded? At least the documentation could use a warning for other users puzzling why problems occurs when trying to get an app published to the Play Store.

    opened by koenniem 6
  • Accelerometer frequency and duration

    Accelerometer frequency and duration

    Prior to carp_mobile_sensing: 0.10:0 the accelerometer worked fine like this

          MapEntry(
              SensorSamplingPackage.ACCELEROMETER,
              PeriodicMeasure(MeasureType(NameSpace.CARP, SensorSamplingPackage.ACCELEROMETER),
                  name: 'Accelerometer',
                  enabled: true,
                  frequency: const Duration(milliseconds: 1000),
                  duration: const Duration(milliseconds: 10))),
    

    This would give me 1 measurement per second with 5 samples in the MultiDatum.

    However, after version 0:10.0 was released, this no longer works and instead it gives something like

    MultiDatum - format: unknown.unknown, id: 6eccc2d0-34b5-11eb-a996-8ffab3efffeb, timestamp: 2020-12-02 15:45:41.756658Z, size: 0
    

    Only when I increase the frequency and duration to something like 3 and 1 seconds respectively does it work properly again, now with 4 or 5 samples in the MultiDatum every 3 seconds.

    My questions:

    • How do I increase the number of measurements to, say, every second?
    • What does the duration actually do?
    opened by koenniem 6
  • Noise and Audio probe cannot run at the same time

    Noise and Audio probe cannot run at the same time

    It seems like the access to the microphone is "taken" by the Noise probe. For example in the example below, first the noise probe collect data (for 2 secs) and work fine and returns a noise recording. Then, when the audio probe wants to record, it get and error in starting the recording -- in the line

    E/MediaRecorder( 5788): start failed: -38
    

    Full print from the carp mobile sensing app is:

    I/flutter ( 5788): Starting Instance of 'NoiseProbe'
    I/MediaRecorderJNI( 5788): setup
    I/flutter ( 5788): Sensing started ...
    I/MediaRecorderJNI( 5788): setAudioSource(1)
    I/MediaRecorderJNI( 5788): setAudioEncoder(0)
    I/MediaRecorderJNI( 5788): setOutputFile
    I/MediaRecorderJNI( 5788): prepare
    I/MediaRecorderJNI( 5788): start
    D/NoiseMeterPlugin( 5788): listen()
    I/flutter ( 5788): >> {"id":"fece0e90-5970-11e9-f9a7-a331c1237735","timestamp":"2019-04-28T11:30:16.184358Z","mean_decibel":34.23076923076923,"std_decibel":5.984859266008269,"min_decibel":0,"max_decibel":52}
    I/flutter ( 5788): Noise - mean: 34.23076923076923, std: 5.984859266008269, min: 0, max: 52
    D/FlutterSoundPlugin( 5788): startRecorder
    I/MediaRecorderJNI( 5788): setup
    I/MediaRecorderJNI( 5788): setAudioSource(1)
    I/MediaRecorderJNI( 5788): setAudioEncoder(3)
    I/MediaRecorderJNI( 5788): setParameter()
    I/MediaRecorderJNI( 5788): setOutputFile
    I/MediaRecorderJNI( 5788): prepare
    I/MediaRecorderJNI( 5788): start
    E/MediaRecorder( 5788): start failed: -38
    E/FlutterSoundPlugin( 5788): Exception: 
    E/FlutterSoundPlugin( 5788): java.lang.IllegalStateException
    E/FlutterSoundPlugin( 5788): 	at android.media.MediaRecorder._start(Native Method)
    E/FlutterSoundPlugin( 5788): 	at android.media.MediaRecorder.start(MediaRecorder.java:1310)
    E/FlutterSoundPlugin( 5788): 	at com.dooboolab.fluttersound.FlutterSoundPlugin.startRecorder(FlutterSoundPlugin.java:162)
    E/FlutterSoundPlugin( 5788): 	at com.dooboolab.fluttersound.FlutterSoundPlugin.lambda$onMethodCall$0$FlutterSoundPlugin(FlutterSoundPlugin.java:65)
    E/FlutterSoundPlugin( 5788): 	at com.dooboolab.fluttersound.FlutterSoundPlugin$$Lambda$0.run(Unknown Source:8)
    E/FlutterSoundPlugin( 5788): 	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
    E/FlutterSoundPlugin( 5788): 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    E/FlutterSoundPlugin( 5788): 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
    E/FlutterSoundPlugin( 5788): 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
    E/FlutterSoundPlugin( 5788): 	at java.lang.Thread.run(Thread.java:764)
    
    enhancement 
    opened by bardram 6
  • Accelerometer gravity

    Accelerometer gravity

    Currently, accelerometer and periodic_accelerometer contain raw accelerometer measurements that include gravity. However, in analyses this gravity can be problematic as it convolutes any calculation to, for example, classify a distinct type of activity. More concretely, the real value along the x, y, and z-axis cannot be simply measured as these values are added by a (partial) portion of the gravity, based on the phone's rotation. Getting rid of the gravity on the individual x, y, and z-axes has only recently been possibly thanks to the addition of the magnetometer to CARP but this is not a straightforward process.

    I am wondering if there is any reason why CARP uses measurments from the regular accelerometer instead those of those from the linear accelerometer on Android or the userAcceleration on iOS. Measurements from the linear accelerometer are based on the same measurements from the gyroscope but processed internally to remove gravity by including either the gyroscope or the magnetometer. I can imagine it may be due to unavailability on lower level APIs (though it has been available since API 9) or higher battery consumption, but even with these concerns I think there is a strong case to prefer accelerometer measurements without gravity.

    Looking forward to hear your thoughts on this.

    enhancement 
    opened by koenniem 5
  • Code signing issues on iOS v. 13.3.x

    Code signing issues on iOS v. 13.3.x

    When installing apps on a real iPhone (not the simulator) on the iOS v. 13.3.1 the following problem occurs often.

    dyld: Library not loaded: @rpath/CSV.framework/CSV
      Referenced from: /private/var/containers/Bundle/Application/42980BC7-DE1E-4D30-8C3D-81A9C20CC7DE/ESense_Example.app/ESense_Example
      Reason: no suitable image found.  Did find:
    	/private/var/containers/Bundle/Application/42980BC7-DE1E-4D30-8C3D-81A9C20CC7DE/ESense_Example.app/Frameworks/CSV.framework/CSV: code signature invalid for '/private/var/containers/Bundle/Application/42980BC7-DE1E-4D30-8C3D-81A9C20CC7DE/ESense_Example.app/Frameworks/CSV.framework/CSV'
    

    In this case it is the ESense causing problems, but it can be any library.

    bug 
    opened by bardram 5
  • Android dependency - different version for compile and runtime classpath

    Android dependency - different version for compile and runtime classpath

    When compiling, we get the error

    FAILURE: Build failed with an exception.
    
    * What went wrong:
    Execution failed for task ':app:preDebugBuild'.
    > Android dependency 'com.android.support:support-compat' has different version for the compile (26.1.0) and runtime (27.1.1) classpath. You should manually set the same version via DependencyResolution
    
    opened by bardram 5
  • Location being retrieved even when not mentioned by the study protocol

    Location being retrieved even when not mentioned by the study protocol

    Basically, I removed the mention of "location" or "geolocation" from the protocol, but this is still being retrieved. Also, I tried to adjust the frequency of this collection (before removing it from the protocol), and it didn't work—always getting data every 500 ms or so (around twice every second, based on the logs).

    Tested with a real device—iPhone 12 Pro Max on the latest public stable iOS version. The location permissions are set to "always allow." I tried downgrading them to "when using," and location data stopped being collected at all (even when on the app's screen).

    Find below the protocol.json and the dependencies version.

    I hope these information helps you finding the cause.

    PROTOCOL ---

    { "ownerId": "[email protected]", "name": "second-test-study", "creationDate": "2022-11-08T12:00:00.000000Z", "masterDevices": [ { "$type": "dk.cachet.carp.protocols.domain.devices.CustomProtocolDevice", "isMasterDevice": true, "roleName": "masterphone", "samplingConfiguration": {} } ], "connectedDevices": [ { "$type": "dk.cachet.carp.protocols.domain.devices.LocationService", "isMasterDevice": false, "roleName": "location_service", "samplingConfiguration": {}, "accuracy": "balanced", "distance": 10.0, "interval": 60000000000 }, { "$type": "dk.cachet.carp.protocols.domain.devices.WeatherService", "isMasterDevice": false, "roleName": "weather_service", "samplingConfiguration": {}, "apiKey": "a44242484ff4686854dd8361c2fb48ab" } ], "connections": [], "triggers": { "0": { "$type": "dk.cachet.carp.protocols.domain.triggers.RandomRecurrentTrigger", "startTime": { "hour": 0, "minute": 0, "second": 0 }, "endTime": { "hour": 23, "minute": 59, "second": 0 }, "minNumberOfTriggers": 24, "maxNumberOfTriggers": 48, "duration": 1000 }, "1": { "$type": "dk.cachet.carp.protocols.domain.triggers.RandomRecurrentTrigger", "startTime": { "hour": 8, "minute": 0, "second": 0 }, "endTime": { "hour": 20, "minute": 0, "second": 0 }, "minNumberOfTriggers": 3, "maxNumberOfTriggers": 8, "duration": 1000 }, "2": { "$type": "dk.cachet.carp.protocols.domain.triggers.ImmediateTrigger" }, "4": { "$type": "dk.cachet.carp.protocols.domain.triggers.ImmediateTrigger" }, "5": { "$type": "dk.cachet.carp.protocols.domain.triggers.IntervalTrigger", "period": 1800000 } }, "tasks": [ { "$type": "dk.cachet.carp.protocols.domain.tasks.BackgroundTask", "name": "Task #5", "measures": [ { "$type": "dk.cachet.carp.protocols.domain.tasks.measures.Measure", "type": "dk.cachet.carp.pedometer" }, { "$type": "dk.cachet.carp.protocols.domain.tasks.measures.Measure", "type": "dk.cachet.carp.battery" } ] }, { "$type": "dk.cachet.carp.protocols.domain.tasks.BackgroundTask", "name": "Task #6", "measures": [ { "$type": "dk.cachet.carp.protocols.domain.tasks.measures.Measure", "type": "dk.cachet.carp.device" } ] }, { "$type": "dk.cachet.carp.protocols.domain.tasks.BackgroundTask", "name": "Task #7", "measures": [ { "$type": "dk.cachet.carp.protocols.domain.tasks.measures.Measure", "type": "dk.cachet.carp.activity" } ] }, { "$type": "dk.cachet.carp.protocols.domain.tasks.BackgroundTask", "name": "Task #9", "measures": [ { "$type": "dk.cachet.carp.protocols.domain.tasks.measures.Measure", "type": "dk.cachet.carp.mobility" } ] }, { "$type": "dk.cachet.carp.protocols.domain.tasks.BackgroundTask", "name": "Task #10", "measures": [ { "$type": "dk.cachet.carp.protocols.domain.tasks.measures.Measure", "type": "dk.cachet.carp.weather" } ] } ], "triggeredTasks": [ { "triggerId": 0, "taskName": "Task #5", "targetDeviceRoleName": "masterphone" }, { "triggerId": 1, "taskName": "Task #6", "targetDeviceRoleName": "masterphone" }, { "triggerId": 2, "taskName": "Task #7", "targetDeviceRoleName": "masterphone" }, { "triggerId": 4, "taskName": "Task #9", "targetDeviceRoleName": "location_service" }, { "triggerId": 5, "taskName": "Task #10", "targetDeviceRoleName": "weather_service" } ], "expectedParticipantData": [], "protocolDescription": { "$type": "StudyDescription", "title": "second test study", "description": "second test study", "purpose": "To test sensing coverage", "responsible": { "$type": "StudyResponsible", "id": "kw", "name": "Katarzyna Wac", "title": "Professor", "email": "[email protected]", "address": "Route de Drize 7", "affiliation": "University of Geneva" } }, "description": "The default study testing coverage of most measures. Used in the coverage tests.", "dataEndPoint": { "$type": "CarpDataEndPoint", "type": "CARP", "dataFormat": "dk.cachet.carp", "bufferSize": 500000, "zip": true, "encrypt": false, "uploadMethod": "DATA_POINT", "name": "CARP Production Server", "collection": "carp_sensing", "deleteWhenUploaded": true } }


    DEPENDENCIES ---

    environment: sdk: ">=2.12.0 <3.0.0"

    dependencies: flutter: sdk: flutter

    carp_core: ^0.40.3 carp_mobile_sensing: ^0.40.0 research_package: any carp_context_package: ^0.40.0 carp_connectivity_package: ^0.40.0 screen_state: ^2.0.0 #carp_communication_package: ^0.33.0 carp_health_package: any carp_apps_package: ^0.40.0 carp_backend: ^0.40.0 carp_webservices: ^0.40.1 #redirecting to settings ios/android app_settings: ^4.1.8 adaptive_theme: ^3.1.1 permission_handler: ^10.2.0

    rxdart: ^0.27.5

    provider: ^6.0.4 intl: ^0.17.0 http: ^0.13.5 cupertino_list_tile: ^0.2.1

    The following adds the Cupertino Icons font to your application.

    Use with the CupertinoIcons class for iOS style icons.

    cupertino_icons: ^1.0.5 shared_preferences: ^2.0.15 flutter_localizations: sdk: flutter version: any dependency_overrides: rxdart: any telephony: ^0.2.0 battery_plus: any

    dev_dependencies: flutter_test: sdk: flutter flutter_launcher_icons: ^0.10.0`

    opened by IgorMatias3 4
  • Question: is there an easy way to change the protocol.

    Question: is there an easy way to change the protocol.

    Lets say I want to completely change the protocol in the middle of a study. I have protocolPast and protocolFuture on the phone. What is the easiest way to do this in the app? I want to

    1. Stop all sampling from protocolPast
    2. initiate protocolFuture
    3. Start all sampling from protocolFuture

    Just running await Sensing().initialize() again with a new protocol clearly doesn't work. I'm probably ovelrooking something but I couldnt find it.

    I use DeploymentMode.LOCAL from in the example app with

            _status = await SmartphoneDeploymentService().createStudyDeployment(
              protocolFuture,
              bloc.studyDeploymentId,
            );
    

    After this Sensing().initialize() runs normally but doesn't change the protocol.

    controller!.deployment?.hasChanged(); does not work and controller!.stop() gives an error:

    E/flutter ( 2625): #0      _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:243:24)
    E/flutter ( 2625): #1      AbstractExecutor._setState (package:carp_mobile_sensing/runtime/executors/executors.dart:135:27)
    E/flutter ( 2625): #2      _AbstractExecutorState.stop (package:carp_mobile_sensing/runtime/executors/executors.dart:298:14)
    E/flutter ( 2625): #3      AbstractExecutor.stop (package:carp_mobile_sensing/runtime/executors/executors.dart:168:19)
    E/flutter ( 2625): #4      SmartphoneDeploymentController.stop (package:carp_mobile_sensing/runtime/study_controller.dart:342:16)
    E/flutter ( 2625): #5      Sensing.initialize (package:flutter_app2/SenseNew/sensing/sensing.dart:65:19)
    E/flutter ( 2625): #6      MPathSensingList.doUpdateMobileSensing (package:flutter_app2/KULeuvenSensingFiles/mPathSensingModel.dart:220:21)
    E/flutter ( 2625): #7      MPathSensingList.updateMobileSensing (package:flutter_app2/KULeuvenSensingFiles/mPathSensingModel.dart:145:17)
    
    help wanted question 
    opened by sc00n 4
  • Manually triggering an event-based measure once

    Manually triggering an event-based measure once

    CARP now resumes sampling when the app restarts as part of the brand new release 0.40.0. This is a great (and much needed) feature as operating systems become more aggressive in killing apps that consume power in the background.

    However, there is one component missing to bridge the gap of missing data: triggering event-based measures only once. For example, instead of waiting for a change in screen state to come in after the app has been killed and restarted, it would be useful to know what the current screen state is. In the case of screen states, I suppose one could infer this because each screen off event must be followed by a screen on event, but this is not the case for all sensors. Thus, manually requesting the current state of event-based sensors - rather than resuming sampling and waiting for a new event - may be advantageous for minimising data gaps.

    Now, I am not sure how feasible this is for all sensors, as the underlying API in some cases is truly event-based, but I believe it is feasible in most cases. Activity recognition and screen state are of particular interest because, unlike other event-based sensors, these do not immediately sample when resumed. For the other event-based sensors, a workaround would be to create a very brief periodic measurement so that they are only triggered once, but I believe a more appropriate solution would be preferable.

    enhancement question 
    opened by koenniem 3
  • Even if location permission is not granted always, the location service should be started

    Even if location permission is not granted always, the location service should be started

    Right now it seems to not start, if not granted "always".

    I/flutter (19417): [CAMS INFO] LocationServiceManager - Trying to connect to device of type: dk.cachet.carp.protocols.domain.devices.LocationService and id: location_service
    I/flutter (19417): [CAMS INFO] Configuring LocationManager - configuration: LocationService - roleName: location_service, isMasterDevice: false
    I/flutter (19417): [CAMS WARNING]  LocationManager - Permission to collect location data 'Always' in the background has not been granted. Make sure to grant this BEFORE sensing is resumed. The context sampling package does not handle permissions. This should be handled on the application level.
    I/flutter (19417): [CAMS DEBUG] LocationServiceManager - setting device status: DeviceStatus.disconnected
    
    opened by bardram 0
  • Upgrade to carp_core R 1.0.0

    Upgrade to carp_core R 1.0.0

    This issue contain notes on how to upgrade to carp_core v1.0.0-alpha.39.

    • add / use the rpc-examples.zip json examples.
    • update all the different application and service end point json
    • update to the new carp.data domain model
    • send data using the appendToDataStreams end point.
    opened by bardram 1
  • [carp_mobile_sensing] Flutter 2.10 - android 12 requires

    [carp_mobile_sensing] Flutter 2.10 - android 12 requires "exported" tag. Update `flutter_local_notifications` as minimum.

    Issue uploading appbundles using android 12 to google play:

    Android App Bundle which has an activity, activity alias, service or broadcast receiver with intent filter, but without the 'android:exported' property set. This file can't be installed on Android 12 or higher. See: developer.android.com/about/versions/12/behavior-changes-12#exported

    https://stackoverflow.com/a/71134965 suggests that updating flutter_local_notifications might resolve this. However, other plugins might require updates as well.

    opened by MadsVSChristensen 0
Releases(v0.40.x)
  • v0.40.x(Oct 31, 2022)

    Major upgrade of carp_mobile_sensing plus all packages. Main features include:

    • Better support for app (re)start, especially when killed by the OS
    • Better support for notifications for AppTasks, also when the app is in the background or even killed by the OS
    • Better support for device management on runtime, especially device descriptors and reconnection

    The has also be added support for the Polar devices via the carp_polar_package sampling package.

    All the demo apps – CAMS Demo, Pulmonary Monitors and the CARP Study App - have all been updated and used for debugging.

    Source code(tar.gz)
    Source code(zip)
  • v0.33.x(Mar 22, 2022)

    CAMS Core

    • release of carp_core v. 0.33.x incl. the device API
    • update of carp_mobile_sensing to reflect carp_core domain and runtime models
    • improvements and bug fixing
    • upgrading to recent Flutter plugins

    Packages

    • all packages updated to v. 0.33.x API
    • fix of various bugs
    • update to new DeviceDescriptor for
      • eSense package
      • Movisens package

    Utilities

    • update to the carp_study_generator to 0.33.x API

    Apps

    • carp_mobile_sensing_app updated to v. 0.33.0 API
    • pulmonary_monitor app
      • updated to v. 0.33.0 API
      • included example of using cognition_package
    Source code(tar.gz)
    Source code(zip)
  • v0.30.x(Aug 11, 2021)

    Release of null-safe version of the entire CAMS suite of products, including:

    • carp_core
    • carp_mobile_sensing
    • all carp_XXX_package sampling packages
    • carp_webservices and carp_backend
    • carp_study_generator
    • carp_mobile_sensing_app

    Various bug fixes and PRs done as well. See the CHANGELOG of each package on pub.dev.

    Source code(tar.gz)
    Source code(zip)
  • v0.21.0(May 27, 2021)

    CAMS Core

    • release of carp_core v. 0.21.x incl. the client sub-system
    • update of carp_mobile_sensing to reflect carp_core domain and runtime models

    Packages

    • all packages updated to v. 0.21.x API
    • carp_webservices updated to v. 0.21.x API
    • carp_backend supports handling
      • custom protocols
      • informed consent
      • localization files

    Utilities

    • created the carp_study_generator as a CLI tool for uploading protocols, informed consent, and localization to CARP

    Apps

    • carp_mobile_sensing_app updated to v. 0.21.0 API
    • pulmonary_monitor app updated to v. 0.21.0 API
    Source code(tar.gz)
    Source code(zip)
  • v0.20.0(Apr 11, 2021)

    CAMS Core

    • release of Dart implementation of carp_core
    • complete re-writes of carp_mobile_sensing to reflect carp_core domain and runtime models

    Packages

    • all packages updated to v. 0.20.0 API
    • carp_webservices update to CARP web service and carp_core models (using study deployment ids)
    • support for download of custom protocols from CANS in carp_backend

    Apps

    • carp_mobile_sensing_app updated to v. 0.20.0 API
    • pulmonary_monitor app updated to v. 0.20.0 API
    Source code(tar.gz)
    Source code(zip)
  • v0.12.0(Jan 19, 2021)

    CAMS Core

    • feature: support for handling a Device as part of a study configuration.
    • refactor: rename of Device to DeviceInfo.

    Packages

    • all packages updated to v. 0.12.0 API
    Source code(tar.gz)
    Source code(zip)
  • v0.11.0(Dec 21, 2020)

    CAMS core

    • refactor: improved (de)serialization in the Serialization class.
    • refactor: named constructors in the Study and Measure classes.
    • feature: support for handling local settings on the phone in the Settings.
    • feature: added description property to Measure.
    • feature: added support for an AppTask can expire, i.e. be removed from the queue.
    • fix: issue #139
    • fix: issue #140

    Packages

    • all packages updated to v. 0.11.0 API

    Apps

    • all apps updated to version 0.11.0 API
    Source code(tar.gz)
    Source code(zip)
  • v0.10.0(Dec 7, 2020)

    carp_mobile_sensing:

    • breaking: a new AppTask model is implemented
      • see documentation on the CAMS wiki.
      • see the Pulmonary app for an example on how to use this new model.
    • fix: issue #98
    • fix: issue #114
    • fix: issue #113

    Sampling Packages:

    Demo Applications:

    • update and release of the Pulmonary app as an example on how to use the new AppTask model.

    All Flutter packages released to pub.dev.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.5(Nov 2, 2020)

    carp_mobile_sensing:

    • feature: added the validNextState() method to the Probe class, which checks if a probe can be move to a next state.
    • fix : issue #112
      • this means that the initialize() method is no longer a Future (and hence cannot be awaited)

    Sampling Packages:

    • all sampling packages upgraded to use carp_mobile_sensing version 0.9.5

    All Flutter packages released to pub.dev.

    Source code(tar.gz)
    Source code(zip)
Owner
Copenhagen Center for Health Technology (CACHET)
Copenhagen Center for Health Technology (CACHET)
Flutter mobile app with firestore authentication including Email and Social auth.

Flutter mobile app with firestore authentication including Email and Social auth.

Ionicfirebaseapp 96 Dec 7, 2022
A Very Good Blog App using Flutter, flutter_bloc, django rest framework for backend✨

Very Good Blog App - Blog sharing Features Login, register, manage personal information. Compose, edit, post blog to share with everyone. Like, save b

Nguyen Minh Dung 14 Nov 27, 2022
An app to show everything bus related in Singapore, including arrival times and a directory

NextBus SG An app to show everything bus related in Singapore, including bus arrival times and a directory, with extra features. ?? Gallery Click here

null 103 Sep 13, 2022
📅 Customizable flutter calendar widget including day and week views

?? Customizable, animated calendar widget including day, week, and month views. Navigation Animation Callbacks Changing the VisibleDateRange Available

Jonas Wanke 276 Jan 1, 2023
A port of kotlin-stdlib for Dart/Flutter including immutable collections (KtList, KtMap, KtSet) and other packages

kt.dart This project is a port of Kotlin's Kotlin Standard library for Dart/Flutter projects. It's a useful addition to dart:core and includes collect

Pascal Welsch 460 Jan 9, 2023
A iOS like table view including section, row, section header and divider

flutter_section_table_view A iOS like table view including section, row, section header and divider Support both Android and iOS Support drop-down ref

刘彦博 73 Nov 4, 2022
Building a simple Flutter app for understanding the BLoC State Management including: Cubit, Managing Route & showSnackBar.

Building a simple Flutter app for understanding the BLoC State Management including: Cubit, Managing Route & showSnackBar.

TAD 8 Dec 3, 2022
Building a simple Flutter app * Switch Theme * for understanding the BLoC State Management including: Cubit Communications with StreamSubscription & Managing Route.

Building a simple Flutter app * Switch Theme * for understanding the BLoC State Management including: Cubit Communications with StreamSubscription & Managing Route.

TAD 1 Oct 3, 2022
Boilerplate codes including Superbase settings for Flutter

flutter_boilerplate_supabase Boilerplate codes including Superbase settings for Flutter. Getting Started You have to create the .env file. Rename the

Flutter Seoul 2 Feb 7, 2022
Intel Corporation 238 Dec 24, 2022
Flying Fish is full-stack Dart framework - a semi-opinionated framework for building applications exclusively using Dart and Flutter

Flying Fish is full-stack Dart framework - a semi-opinionated framework for building applications exclusively using Dart and Flutter.

Flutter Fish 3 Dec 27, 2022
Flutter plugin, support android/ios.Support crop, flip, rotate, color martix, mix image, add text. merge multi images.

image_editor The version of readme pub and github may be inconsistent, please refer to github. Use native(objc,kotlin) code to handle image data, it i

FlutterCandies 317 Jan 3, 2023
The ROHD Verification Framework is a hardware verification framework built upon ROHD for building testbenches.

ROHD Verification Framework The ROHD Verification Framework (ROHD-VF) is a verification framework built upon the Rapid Open Hardware Development (ROHD

Intel Corporation 18 Dec 20, 2022
Codeflow 19 Sep 29, 2022
Wraps Flutter shared_preferences plugin, providing a iOS Suite Name support, it's helpful for sharing data from App to Widget.

shared_preferences_ios_sn Wraps Flutter shared_preferences plugin and provides an iOS Suite Name support, it's helpful for sharing data from App to iO

null 3 Sep 14, 2022
Dart package for Async Data Loading and Caching. Combine local (DB, cache) and network data simply and safely.

Stock is a dart package for loading data from both remote and local sources. It is inspired by the Store Kotlin library.

xmartlabs 59 Dec 24, 2022
State Persistence - Persist state across app launches. By default this library store state as a local JSON file called `data.json` in the applications data directory. Maintainer: @slightfoot

State Persistence Persist state across app launches. By default this library store state as a local JSON file called data.json in the applications dat

Flutter Community 70 Sep 28, 2022
Local data hive - Local data hive for flutter

local_data_hive A new Flutter application. ScreenShot

Mehmet Emre ÖZ 0 Jan 8, 2022
IoTF app is a smart farming app for IoT and AI-powered tomato plant disease detection. It is built with Flutter and uses Firebase as its backend.

Internet of Tomato Farming IoTF app is a smart farming app for IoT and AI-powered tomato plant disease detection. It is built with Flutter and uses Fi

ILYAS IMZAGNAN 4 Dec 9, 2022