From a653f992f1dfdbbde38a45d01322662a5dd83099 Mon Sep 17 00:00:00 2001 From: bardram Date: Tue, 29 Oct 2024 13:50:57 +0100 Subject: [PATCH 1/3] fix of #341 * https://github.com/cph-cachet/carp_studies_app/issues/341 --- .../carp_backend/test/carp_backend_test.dart | 11 ++-- backends/carp_backend/test/json/messages.json | 50 +++++++++++++++++++ carp_mobile_sensing/CHANGELOG.md | 3 +- .../lib/runtime/app_task_controller.dart | 1 - .../executors/task_control_executors.dart | 2 +- .../lib/runtime/executors/task_executors.dart | 34 +++++++------ .../lib/runtime/user_tasks.dart | 21 ++++++-- carp_mobile_sensing/pubspec.yaml | 2 +- .../carp_audio_package/lib/audio_probe.dart | 45 +++++++++++------ 9 files changed, 125 insertions(+), 44 deletions(-) create mode 100644 backends/carp_backend/test/json/messages.json diff --git a/backends/carp_backend/test/carp_backend_test.dart b/backends/carp_backend/test/carp_backend_test.dart index a1a8d7b4..152dc64a 100644 --- a/backends/carp_backend/test/carp_backend_test.dart +++ b/backends/carp_backend/test/carp_backend_test.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:carp_serializable/carp_serializable.dart'; import 'package:flutter/material.dart'; import 'package:test/test.dart'; // import 'package:flutter_test/flutter_test.dart'; @@ -257,19 +258,17 @@ void main() { group("Documents & Collections", () { test('- get by id', () async { - DocumentSnapshot? doc = await CarpService().documentById(167).get(); - print(doc); + final doc = await CarpService().documentById(102).get(); + print(toJsonString(doc?.data)); }); test('- get by collection', () async { - CollectionReference ref = - await CarpService().collection('localizations').get(); + final ref = await CarpService().collection('localizations').get(); print((ref)); }); test(' - get document by path', () async { - DocumentSnapshot? doc = - await CarpService().document('localizations/da').get(); + final doc = await CarpService().document('localizations/da').get(); print((doc)); }); diff --git a/backends/carp_backend/test/json/messages.json b/backends/carp_backend/test/json/messages.json new file mode 100644 index 00000000..ee0fdd52 --- /dev/null +++ b/backends/carp_backend/test/json/messages.json @@ -0,0 +1,50 @@ + { + "id": 30, + "name": "messages", + "study_id": "d6d047d5-4cfd-479b-87ac-99f4b5fee172", + "study_deployment_id": "", + "document_id": null, + "documents": [ + { + "id": 103, + "name": "ebc95008-76f9-4a08-8805-7f580bdbbee1", + "collection_id": 30, + "data": { + "id": "8f63d8e8-6001-4f9b-9d70-9731a25d3a92", + "url": "https://github.com/cph-cachet/carp.sensing-flutter/issues/434", + "type": "news", + "image": null, + "title": "Test news", + "message": "Jakob testing", + "subTitle": "For unit testing", + "timestamp": "2024-10-28T20:27:03.561Z" + }, + "created_by": "9c354cd9-0fd9-49a4-910d-46b28ea43997", + "created_at": "2024-10-28T20:27:03.781416Z", + "updated_by": "9c354cd9-0fd9-49a4-910d-46b28ea43997", + "updated_at": "2024-10-28T20:27:03.781416Z" + }, + { + "id": 102, + "name": "8d67565c-9f21-449f-bf8a-b084e15b771e", + "collection_id": 30, + "data": { + "id": "8d67565c-9f21-449f-bf8a-b084e15b771e", + "url": "https://www.who.int/initiatives/behealthy/healthy-diet", + "type": "article", + "title": "The importance of healthy eating", + "message": "A healthy diet is essential for good health and nutrition. It protects you against many chronic noncommunicable diseases, such as heart disease, diabetes and cancer. Eating a variety of foods and consuming less salt, sugars and saturated and industrially-produced trans-fats, are essential for healthy diet.\n\nA healthy diet comprises a combination of different foods. These include:\n\n - Staples like cereals (wheat, barley, rye, maize or rice) or starchy tubers or roots (potato, yam, taro or cassava).\n - Legumes (lentils and beans).\n - Fruit and vegetables.\n - Foods from animal sources (meat, fish, eggs and milk).\n\nHere is some useful information, based on WHO recommendations, to follow a healthy diet, and the benefits of doing so.", + "sub_title": "", + "timestamp": "2024-10-28T21:18:11.275882" + }, + "created_by": "9c354cd9-0fd9-49a4-910d-46b28ea43997", + "created_at": "2024-10-28T20:18:11.719098Z", + "updated_by": "9c354cd9-0fd9-49a4-910d-46b28ea43997", + "updated_at": "2024-10-28T20:18:11.719098Z" + } + ], + "created_by": "9c354cd9-0fd9-49a4-910d-46b28ea43997", + "created_at": "2024-09-25T11:33:43.252362Z", + "updated_by": "9c354cd9-0fd9-49a4-910d-46b28ea43997", + "updated_at": "2024-09-25T11:33:43.252362Z" + } \ No newline at end of file diff --git a/carp_mobile_sensing/CHANGELOG.md b/carp_mobile_sensing/CHANGELOG.md index d71e4313..b02b605c 100644 --- a/carp_mobile_sensing/CHANGELOG.md +++ b/carp_mobile_sensing/CHANGELOG.md @@ -1,8 +1,9 @@ -## 1.11.2 +## 1.11.3 * Extended the `SmartphoneStudy` and `SmartphoneDeployment` to hold info on participant ID and participant role for a study running on the phone. This helps upload participant data (like informed consent) without the need to specify this every time. * Fix of [#429](https://github.com/cph-cachet/carp.sensing-flutter/issues/429) * Fix of [#430](https://github.com/cph-cachet/carp.sensing-flutter/issues/430) +* Fix of [#431](https://github.com/cph-cachet/carp_studies_app/issues/341) ## 1.10.0 diff --git a/carp_mobile_sensing/lib/runtime/app_task_controller.dart b/carp_mobile_sensing/lib/runtime/app_task_controller.dart index d45dc5fb..68189d3e 100644 --- a/carp_mobile_sensing/lib/runtime/app_task_controller.dart +++ b/carp_mobile_sensing/lib/runtime/app_task_controller.dart @@ -161,7 +161,6 @@ class AppTaskController { DateTime? triggerTime, bool sendNotification = true, }) { - debug('$runtimeType - Buffering task $executor for later scheduling.'); _userTaskBuffer.add(UserTaskBufferItem( taskControl, executor, diff --git a/carp_mobile_sensing/lib/runtime/executors/task_control_executors.dart b/carp_mobile_sensing/lib/runtime/executors/task_control_executors.dart index 7b7c8ad5..f14237ec 100644 --- a/carp_mobile_sensing/lib/runtime/executors/task_control_executors.dart +++ b/carp_mobile_sensing/lib/runtime/executors/task_control_executors.dart @@ -126,7 +126,7 @@ class TaskControlExecutor extends AbstractExecutor { /// /// In contrast to the [TaskControlExecutor] (which runs in the background), /// this [AppTaskControlExecutor] will try to schedule the [AppTask] using -/// the [AppTaskController]. This means that the [trigger] for has to be +/// the [AppTaskController]. This means that the [trigger] has to be /// [Schedulable]. class AppTaskControlExecutor extends TaskControlExecutor { AppTaskControlExecutor( diff --git a/carp_mobile_sensing/lib/runtime/executors/task_executors.dart b/carp_mobile_sensing/lib/runtime/executors/task_executors.dart index 8985f85a..b1010f15 100644 --- a/carp_mobile_sensing/lib/runtime/executors/task_executors.dart +++ b/carp_mobile_sensing/lib/runtime/executors/task_executors.dart @@ -62,6 +62,17 @@ class BackgroundTaskExecutor extends TaskExecutor { bool get haveAllProbesStopped => !probes.any((probe) => probe.state != ExecutorState.stopped); + /// Connect all connectable devices used by the [probes] in this + /// background task executor. + Future connectAllConnectableDevices() async { + debug( + '$runtimeType - Trying to connect to all connectable devices for this background executor.'); + + probes + .where((probe) => !probe.deviceManager.isConnected) + .forEach((probe) async => await probe.deviceManager.connect()); + } + @override Future onStart() async { // Early out if no probes. @@ -85,22 +96,17 @@ class BackgroundTaskExecutor extends TaskExecutor { } }); - // Check if the device for this task is connected. - if (probes.first.deviceManager.isConnected) { - if (configuration?.duration != null) { - // If the task has a duration (optional), stop it again after this duration has passed. - Timer(Duration(seconds: configuration!.duration!.inSeconds.truncate()), - () => stop()); - } + // Check if the devices for this task is connected. + await connectAllConnectableDevices(); - // Now - finally - we can start the probes. - return await super.onStart(); - } else { - warning( - 'A $runtimeType could not be started since the device for this task is not connected. ' - 'Device type: ${probes.first.deviceManager.typeName}'); - return false; + if (configuration?.duration != null) { + // If the task has a duration (optional), stop it again after this duration has passed. + Timer(Duration(seconds: configuration!.duration!.inSeconds.truncate()), + () => stop()); } + + // Now - finally - we can start the probes. + return await super.onStart(); } @override diff --git a/carp_mobile_sensing/lib/runtime/user_tasks.dart b/carp_mobile_sensing/lib/runtime/user_tasks.dart index db8f4b94..f6ac4682 100644 --- a/carp_mobile_sensing/lib/runtime/user_tasks.dart +++ b/carp_mobile_sensing/lib/runtime/user_tasks.dart @@ -100,7 +100,7 @@ abstract class UserTask { /// The task executor which is used to collect the sensor measures of this user /// task in the background once started. - TaskExecutor backgroundTaskExecutor = BackgroundTaskExecutor(); + BackgroundTaskExecutor backgroundTaskExecutor = BackgroundTaskExecutor(); /// The result of this task, once done. Data? result; @@ -134,6 +134,19 @@ abstract class UserTask { state = UserTaskState.started; } + /// Listen to remove the background executor when all of its underlying + /// probes have stopped. + /// Issue => https://github.com/cph-cachet/carp_studies_app/issues/341 + void _removeExecutor() { + backgroundTaskExecutor.states + .where((event) => event == ExecutorState.stopped) + .listen((_) { + if (backgroundTaskExecutor.haveAllProbesStopped) { + _executor.removeExecutor(backgroundTaskExecutor); + } + }); + } + /// Callback from the app if this task is canceled. /// /// If [dequeue] is `true` the task is removed from the queue. @@ -142,7 +155,7 @@ abstract class UserTask { void onCancel({bool dequeue = false}) { state = UserTaskState.canceled; if (dequeue) AppTaskController().dequeue(id); - _executor.removeExecutor(backgroundTaskExecutor); + _removeExecutor(); } /// Callback from the app if this task expires. @@ -152,7 +165,7 @@ abstract class UserTask { void onExpired() { state = UserTaskState.expired; AppTaskController().dequeue(id); - _executor.removeExecutor(backgroundTaskExecutor); + _removeExecutor(); } /// Callback from the app when this task is done. @@ -165,7 +178,7 @@ abstract class UserTask { state = UserTaskState.done; AppTaskController().done(id, result); if (dequeue) AppTaskController().dequeue(id); - _executor.removeExecutor(backgroundTaskExecutor); + _removeExecutor(); } /// Callback from the OS when this task is clicked by the user in the diff --git a/carp_mobile_sensing/pubspec.yaml b/carp_mobile_sensing/pubspec.yaml index 2cec5c55..9b6bf0c6 100644 --- a/carp_mobile_sensing/pubspec.yaml +++ b/carp_mobile_sensing/pubspec.yaml @@ -1,6 +1,6 @@ name: carp_mobile_sensing description: Mobile Sensing Framework for Flutter. A software framework for collecting sensor data from the phone and attached wearable devices via probes. Can be extended. -version: 1.11.2 +version: 1.11.3 homepage: https://github.com/cph-cachet/carp.sensing-flutter environment: diff --git a/packages/carp_audio_package/lib/audio_probe.dart b/packages/carp_audio_package/lib/audio_probe.dart index 3279de44..0c303c23 100644 --- a/packages/carp_audio_package/lib/audio_probe.dart +++ b/packages/carp_audio_package/lib/audio_probe.dart @@ -22,14 +22,22 @@ class AudioProbe extends Probe { bool _isRecording = false; Media? _data; String? _soundFileName; + bool get isRecording => _isRecording; + @override + bool onInitialize() { + _recorder.openRecorder(); + return super.onInitialize(); + } + @override Future onStart() async { if (await requestPermissions()) { try { await _startAudioRecording(); - debug('Audio recording started - sound file : $_soundFileName'); + debug( + '$runtimeType [$hashCode] - Audio recording started - sound file : $_soundFileName'); } catch (error) { warning('An error occurred trying to start audio recording - $error'); addError(error); @@ -43,21 +51,23 @@ class AudioProbe extends Probe { @override Future onStop() async { - // when stopping the audio sampling, stop recording and collect the measurement if (_isRecording) { try { await _stopAudioRecording(); + debug(' $runtimeType [$hashCode] - Audio recording stopped.'); - var measurement = _data != null - ? Measurement( - sensorStartTime: - _data!.startRecordingTime!.microsecondsSinceEpoch, - sensorEndTime: _data!.endRecordingTime?.microsecondsSinceEpoch, - data: _data!) - : null; - - if (measurement != null) addMeasurement(measurement); - debug('Audio recording stopped - sound file : $_soundFileName'); + // when stopping the audio sampling, stop recording and collect the measurement + // HOWEVER - this does not work....? + // issue => https://github.com/cph-cachet/carp_studies_app/issues/341 + if (_data != null) { + _data?.endRecordingTime = DateTime.now().toUtc(); + var measurement = Measurement( + sensorStartTime: + _data!.startRecordingTime!.microsecondsSinceEpoch, + sensorEndTime: _data!.endRecordingTime?.microsecondsSinceEpoch, + data: _data!); + addMeasurement(measurement); + } } catch (error) { warning('An error occurred trying to stop audio recording - $error'); addError(error); @@ -66,6 +76,11 @@ class AudioProbe extends Probe { return true; } + @override + Future onDispose() async { + await _recorder.closeRecorder(); + } + Future _startAudioRecording() async { // fast out if recording is already in progress (can only record one at a time) if (_isRecording) { @@ -77,7 +92,7 @@ class AudioProbe extends Probe { _data = Media( mediaType: MediaType.audio, - filename: 'ignored for now', + filename: 'no_file_available', startRecordingTime: DateTime.now().toUtc(), ); _soundFileName = await _filePath; @@ -86,7 +101,6 @@ class AudioProbe extends Probe { _isRecording = true; // start the recording - _recorder.openRecorder(); await _recorder.startRecorder( toFile: _soundFileName, codec: Codec.aacMP4, @@ -95,11 +109,10 @@ class AudioProbe extends Probe { Future _stopAudioRecording() async { _data?.endRecordingTime = DateTime.now().toUtc(); - _isRecording = false; // stop the recording and close the recorder await _recorder.stopRecorder(); - _recorder.closeRecorder(); + _isRecording = false; } /// The local path on the device where sound files are stored. From 69018661fe0f599cc6291062938cc4120a0380b4 Mon Sep 17 00:00:00 2001 From: bardram Date: Tue, 29 Oct 2024 13:52:06 +0100 Subject: [PATCH 2/3] Update audio_probe.dart --- packages/carp_audio_package/lib/audio_probe.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/carp_audio_package/lib/audio_probe.dart b/packages/carp_audio_package/lib/audio_probe.dart index 0c303c23..d61a7e9b 100644 --- a/packages/carp_audio_package/lib/audio_probe.dart +++ b/packages/carp_audio_package/lib/audio_probe.dart @@ -57,8 +57,6 @@ class AudioProbe extends Probe { debug(' $runtimeType [$hashCode] - Audio recording stopped.'); // when stopping the audio sampling, stop recording and collect the measurement - // HOWEVER - this does not work....? - // issue => https://github.com/cph-cachet/carp_studies_app/issues/341 if (_data != null) { _data?.endRecordingTime = DateTime.now().toUtc(); var measurement = Measurement( From 5181718ddbfc8167bd4ca6737890bddb1679b6fa Mon Sep 17 00:00:00 2001 From: bardram Date: Tue, 29 Oct 2024 13:55:30 +0100 Subject: [PATCH 3/3] carp_audio_package 1.7.1 published --- packages/carp_audio_package/CHANGELOG.md | 5 +++-- packages/carp_audio_package/pubspec.yaml | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/carp_audio_package/CHANGELOG.md b/packages/carp_audio_package/CHANGELOG.md index cbc9f151..4c231306 100644 --- a/packages/carp_audio_package/CHANGELOG.md +++ b/packages/carp_audio_package/CHANGELOG.md @@ -1,6 +1,7 @@ -## 1.7.0 +## 1.7.1 -* upgrade to carp_serialization v. 2.0 & carp_mobile_sensing: 1.10.0 +* upgrade to carp_serialization v. 2.0 & carp_mobile_sensing: 1.11.0 +* Fix of [#431](https://github.com/cph-cachet/carp_studies_app/issues/341) ## 1.6.0 diff --git a/packages/carp_audio_package/pubspec.yaml b/packages/carp_audio_package/pubspec.yaml index df843b58..55a76e60 100644 --- a/packages/carp_audio_package/pubspec.yaml +++ b/packages/carp_audio_package/pubspec.yaml @@ -1,6 +1,6 @@ name: carp_audio_package description: CARP Media Sampling Package. Samples audio, video, image, and noise. -version: 1.7.0 +version: 1.7.1 homepage: https://github.com/cph-cachet/carp.sensing-flutter/tree/master/packages/carp_audio_package environment: @@ -13,7 +13,7 @@ dependencies: carp_serializable: ^2.0.0 carp_core: ^1.8.0 - carp_mobile_sensing: ^1.10.0 + carp_mobile_sensing: ^1.11.0 json_annotation: ^4.8.0 permission_handler: ^11.0.0