Skip to content

Commit

Permalink
Merge pull request #436 from cph-cachet/bardram/missing_audio_measure…
Browse files Browse the repository at this point in the history
…ment

carp_mobile_sensing 1.11.3
  • Loading branch information
bardram authored Oct 29, 2024
2 parents 6ea7ad4 + 5181718 commit 0bf795f
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 48 deletions.
11 changes: 5 additions & 6 deletions backends/carp_backend/test/carp_backend_test.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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));
});

Expand Down
50 changes: 50 additions & 0 deletions backends/carp_backend/test/json/messages.json
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 2 additions & 1 deletion carp_mobile_sensing/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
1 change: 0 additions & 1 deletion carp_mobile_sensing/lib/runtime/app_task_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ class AppTaskController {
DateTime? triggerTime,
bool sendNotification = true,
}) {
debug('$runtimeType - Buffering task $executor for later scheduling.');
_userTaskBuffer.add(UserTaskBufferItem(
taskControl,
executor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class TaskControlExecutor extends AbstractExecutor<TaskControl> {
///
/// 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(
Expand Down
34 changes: 20 additions & 14 deletions carp_mobile_sensing/lib/runtime/executors/task_executors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ class BackgroundTaskExecutor extends TaskExecutor<BackgroundTask> {
bool get haveAllProbesStopped =>
!probes.any((probe) => probe.state != ExecutorState.stopped);

/// Connect all connectable devices used by the [probes] in this
/// background task executor.
Future<void> 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<bool> onStart() async {
// Early out if no probes.
Expand All @@ -85,22 +96,17 @@ class BackgroundTaskExecutor extends TaskExecutor<BackgroundTask> {
}
});

// 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
Expand Down
21 changes: 17 additions & 4 deletions carp_mobile_sensing/lib/runtime/user_tasks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion carp_mobile_sensing/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
5 changes: 3 additions & 2 deletions packages/carp_audio_package/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
43 changes: 27 additions & 16 deletions packages/carp_audio_package/lib/audio_probe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> 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);
Expand All @@ -43,21 +51,21 @@ class AudioProbe extends Probe {

@override
Future<bool> 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
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);
Expand All @@ -66,6 +74,11 @@ class AudioProbe extends Probe {
return true;
}

@override
Future<void> onDispose() async {
await _recorder.closeRecorder();
}

Future<void> _startAudioRecording() async {
// fast out if recording is already in progress (can only record one at a time)
if (_isRecording) {
Expand All @@ -77,7 +90,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;
Expand All @@ -86,7 +99,6 @@ class AudioProbe extends Probe {
_isRecording = true;

// start the recording
_recorder.openRecorder();
await _recorder.startRecorder(
toFile: _soundFileName,
codec: Codec.aacMP4,
Expand All @@ -95,11 +107,10 @@ class AudioProbe extends Probe {

Future<void> _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.
Expand Down
4 changes: 2 additions & 2 deletions packages/carp_audio_package/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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
Expand Down

0 comments on commit 0bf795f

Please sign in to comment.