Skip to content

Commit

Permalink
Release 1.0.0-alpha.38
Browse files Browse the repository at this point in the history
Merge pull request #337 from cph-cachet/develop
  • Loading branch information
Whathecode authored Dec 2, 2021
2 parents cd8bd29 + 7c0c2e7 commit a01c008
Show file tree
Hide file tree
Showing 145 changed files with 3,427 additions and 1,691 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Two key **design goals** differentiate this project from similar projects:
- [Study and device deployment state](docs/carp-deployments.md#study-and-device-deployment-state)
- [Application services](docs/carp-deployments.md#application-services)
- [Clients](docs/carp-clients.md)
- [Study runtime state](docs/carp-clients.md#study-runtime-state)
- [Study state](docs/carp-clients.md#study-state)
- [Data](docs/carp-data.md)
- [Data streams](docs/carp-data.md#data-streams)
- [Application services](docs/carp-data.md#application-services)
Expand Down Expand Up @@ -222,8 +222,8 @@ if ( studyStatus.canDeployToParticipants )
val participation = AssignParticipantDevices( participant.id, setOf( patientPhone.roleName ) )
val participantGroup = setOf( participation )

val groupStatus: ParticipantGroupStatus = recruitmentService.deployParticipantGroup( studyId, participantGroup )
val isInvited = groupStatus.studyDeploymentStatus is StudyDeploymentStatus.Invited // True.
val groupStatus: ParticipantGroupStatus = recruitmentService.inviteNewParticipantGroup( studyId, participantGroup )
val isInvited = groupStatus is ParticipantGroupStatus.Invited // True.
}
```

Expand Down Expand Up @@ -264,9 +264,9 @@ if ( patientPhoneStatus.canObtainDeviceDeployment ) // True since there are no d
deploymentService.deploymentSuccessful( studyDeploymentId, patientPhone.roleName, deployedOn )
}

// Now that all devices have been registered and deployed, the deployment is ready.
// Now that all devices have been registered and deployed, the deployment is running.
status = deploymentService.getStudyDeploymentStatus( studyDeploymentId )
val isReady = status is StudyDeploymentStatus.DeploymentReady // True.
val isReady = status is StudyDeploymentStatus.Running // True.
```

<a name="example-client"></a>
Expand All @@ -283,7 +283,7 @@ val invitation: ActiveParticipationInvitation =
val studyDeploymentId: UUID = invitation.participation.studyDeploymentId
val deviceToUse: String = invitation.assignedDevices.first().device.roleName // This matches "Patient's phone".

// Create a study runtime for the study.
// Add the study to a client device manager.
val clientRepository = createRepository()
val client = SmartphoneClient( clientRepository, deploymentService, dataCollectorFactory )
client.configure {
Expand All @@ -292,18 +292,18 @@ client.configure {
// E.g., for a smartphone, a UUID deviceId is generated. To override this default:
deviceId = "xxxxxxxxx"
}
var status: StudyRuntimeStatus = client.addStudy( studyDeploymentId, deviceToUse )
var status: StudyStatus = client.addStudy( studyDeploymentId, deviceToUse )

// Register connected devices in case needed.
if ( status is StudyRuntimeStatus.RegisteringDevices )
if ( status is StudyStatus.RegisteringDevices )
{
val connectedDevice = status.remainingDevicesToRegister.first()
val connectedRegistration = connectedDevice.createRegistration()
deploymentService.registerDevice( studyDeploymentId, connectedDevice.roleName, connectedRegistration )

// Re-try deployment now that devices have been registered.
// Try deployment now that devices have been registered.
status = client.tryDeployment( status.id )
val isDeployed = status is StudyRuntimeStatus.Deployed // True.
val isDeployed = status is StudyStatus.Running // True.
}
```

Expand Down
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ buildscript {
ext {
// Version used for all submodule artifacts.
// Snapshot publishing changes (or adds) the suffix after '-' with 'SNAPSHOT' prior to publishing.
globalVersion = '1.0.0-alpha.37'
globalVersion = '1.0.0-alpha.38'

versions = [
// Kotlin multiplatform versions.
Expand All @@ -24,6 +24,7 @@ buildscript {

// JS versions.
nodePlugin:'3.1.1',
bigJs:'6.1.1',

// DevOps versions.
detektPlugin:'1.18.1',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package dk.cachet.carp.clients.domain
package dk.cachet.carp.clients.application

import dk.cachet.carp.clients.domain.ClientRepository
import dk.cachet.carp.clients.domain.DeviceRegistrationStatus
import dk.cachet.carp.clients.domain.data.ConnectedDeviceDataCollector
import dk.cachet.carp.clients.domain.data.DataListener
import dk.cachet.carp.clients.domain.data.DeviceDataCollector
import dk.cachet.carp.clients.domain.data.DeviceDataCollectorFactory
import dk.cachet.carp.clients.domain.study.Study
import dk.cachet.carp.clients.domain.study.StudyDeploymentProxy
import dk.cachet.carp.clients.application.study.StudyId
import dk.cachet.carp.clients.application.study.StudyStatus
import dk.cachet.carp.common.application.UUID
import dk.cachet.carp.common.application.devices.DeviceRegistration
import dk.cachet.carp.common.application.devices.DeviceRegistrationBuilder
Expand All @@ -12,7 +18,7 @@ import dk.cachet.carp.deployments.application.DeploymentService


/**
* Allows managing [StudyRuntime]'s on a client device.
* Allows managing [Study]'s on a client device.
*/
abstract class ClientManager<
TMasterDevice : MasterDeviceDescriptor<TRegistration, TRegistrationBuilder>,
Expand All @@ -35,10 +41,11 @@ abstract class ClientManager<
)
{
private val dataListener: DataListener = DataListener( dataCollectorFactory )
private val studyDeployment: StudyDeploymentProxy = StudyDeploymentProxy( deploymentService, dataListener )


/**
* Determines whether a [DeviceRegistration] has been configured for this client, which is necessary to start adding [StudyRuntime]s.
* Determines whether a [DeviceRegistration] has been configured for this client, which is necessary to start adding [Study]s.
*/
suspend fun isConfigured(): Boolean = repository.getDeviceRegistration() != null

Expand All @@ -60,89 +67,84 @@ abstract class ClientManager<
/**
* Get the status for the studies which run on this client device.
*/
suspend fun getStudiesStatus(): List<StudyRuntimeStatus> = repository.getStudyRuntimeList().map { it.getStatus() }
suspend fun getStudiesStatus(): List<StudyStatus> = repository.getStudyList().map { it.getStatus() }

/**
* Add a study which needs to be executed on this client. This involves registering this device for the specified study deployment.
* Add a study which needs to be executed on this client. No deployment is attempted yet.
*
* @param studyDeploymentId The ID of a study which has been deployed already and for which to collect data.
* @param deviceRoleName The role which the client device this runtime is intended for plays as part of the deployment identified by [studyDeploymentId].
* @param studyDeploymentId The ID of the study deployment for which to collect data.
* @param deviceRoleName The role of the client device which takes part in the deployment identified by [studyDeploymentId].
*
* @throws IllegalArgumentException when:
* - the client has not yet been configured
* - a deployment with [studyDeploymentId] does not exist
* - [deviceRoleName] is not present in the deployment or is already registered by a different device
* - a study with the same [studyDeploymentId] and [deviceRoleName] has already been added to this client
* - the configured device registration of this client is invalid for the specified device
* - the configured device registration of this client uses a device ID which has already been used as part of registration of a different device
* @return The [StudyRuntime] through which data collection for the newly added study can be managed.
* @throws IllegalArgumentException if a study with the same [studyDeploymentId] and [deviceRoleName] has already been added to this client.
* @return The [StudyStatus] of the newly added study.
*/
suspend fun addStudy( studyDeploymentId: UUID, deviceRoleName: String ): StudyRuntimeStatus
suspend fun addStudy( studyDeploymentId: UUID, deviceRoleName: String ): StudyStatus
{
// TODO: Can/should it be reinforced here that only study runtimes for a matching master device type can be created?
require( isConfigured() ) { "The client has not been configured yet." }
require( repository.getStudyBy( studyDeploymentId, deviceRoleName ) == null )
{ "A study with the same study deployment ID and device role name has already been added." }

val alreadyAdded = repository.getStudyRuntimeBy( studyDeploymentId, deviceRoleName ) != null
require( !alreadyAdded ) { "A study with the same study deployment ID and device role name has already been added." }
val study = Study( studyDeploymentId, deviceRoleName )
repository.addStudy( study )

// Create the study runtime.
// IllegalArgumentException's will be thrown here when deployment or role name does not exist, or device is already registered.
val deviceRegistration = repository.getDeviceRegistration()!!
val runtime = StudyRuntime.initialize(
deploymentService, dataListener,
studyDeploymentId, deviceRoleName, deviceRegistration
)

repository.addStudyRuntime( runtime )
return runtime.getStatus()
return study.getStatus()
}

/**
* Verifies whether the device is ready for deployment of the study runtime identified by [studyRuntimeId],
* Verifies whether the device is ready for deployment of the study identified by [studyId],
* and in case it is, deploys. In case already deployed, nothing happens.
*
* @throws IllegalArgumentException in case no [StudyRuntime] with the given [studyRuntimeId] exists.
* @throws UnsupportedOperationException in case deployment failed since not all necessary plugins to execute the study are available.
* @throws IllegalArgumentException if:
* - the client has not yet been configured
* - a [Study] with the given [studyId] does not exist
* - deployment failed because of unexpected study deployment ID, device role name, or device registration
* @throws UnsupportedOperationException if deployment failed since the runtime does not support all requirements of the study.
*/
suspend fun tryDeployment( studyRuntimeId: StudyRuntimeId ): StudyRuntimeStatus
suspend fun tryDeployment( studyId: StudyId ): StudyStatus
{
val runtime = getStudyRuntime( studyRuntimeId )
require( isConfigured() ) { "The client has not been configured yet." }

// Early out in case this runtime has already received and validated deployment information.
val status = runtime.getStatus()
if ( status is StudyRuntimeStatus.Deployed ) return status
val study = getStudy( studyId )

val newStatus = runtime.tryDeployment( deploymentService, dataListener )
if ( status != newStatus )
{
repository.updateStudyRuntime( runtime )
}
// Early out in case this study has already received and validated deployment information.
val status = study.getStatus()
if ( status is StudyStatus.Running ) return status

// Try to deploy the study.
// IllegalArgumentException's will be thrown here when deployment or role name does not exist, or device is already registered.
// TODO: Can/should it be reinforced here that only matching master device type can be deployed?
val registration = repository.getDeviceRegistration()!!
studyDeployment.tryDeployment( study, registration )

val newStatus = study.getStatus()
if ( status != newStatus ) repository.updateStudy( study )

return newStatus
}

/**
* Permanently stop collecting data for the study runtime identified by [studyRuntimeId].
* Permanently stop collecting data for the study identified by [studyId].
*
* @throws IllegalArgumentException in case no [StudyRuntime] with the given [studyRuntimeId] exists.
* @throws IllegalArgumentException in case no [Study] with the given [studyId] exists.
*/
suspend fun stopStudy( studyRuntimeId: StudyRuntimeId ): StudyRuntimeStatus
suspend fun stopStudy( studyId: StudyId ): StudyStatus
{
val runtime = getStudyRuntime( studyRuntimeId )
val status = runtime.getStatus()
val study = getStudy( studyId )
val status = study.getStatus()

studyDeployment.stop( study )

val newStatus = runtime.stop( deploymentService )
val newStatus = study.getStatus()
if ( status != newStatus )
{
repository.updateStudyRuntime( runtime )
repository.updateStudy( study )
}

return newStatus
}

private suspend fun getStudyRuntime( studyRuntimeid: StudyRuntimeId ): StudyRuntime =
repository.getStudyRuntimeList().firstOrNull { it.id == studyRuntimeid }
?: throw IllegalArgumentException( "The specified study runtime does not exist." )
private suspend fun getStudy( studyId: StudyId ): Study =
requireNotNull( repository.getStudyList().firstOrNull { it.id == studyId } )
{ "The specified study does not exist." }

/**
* Once a connected device has been registered, this returns a manager which provides access to the status of the [registeredDevice].
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dk.cachet.carp.clients.domain
package dk.cachet.carp.clients.application

import dk.cachet.carp.clients.domain.data.AnyConnectedDeviceDataCollector
import dk.cachet.carp.common.application.devices.DeviceRegistration
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package dk.cachet.carp.clients.domain
package dk.cachet.carp.clients.application.study

import dk.cachet.carp.common.application.UUID


/**
* Uniquely identifies a [StudyRuntime] running on a [ClientManager].
* Uniquely identifies a [Study] added to a [ClientManager].
*/
data class StudyRuntimeId(
data class StudyId(
/**
* The ID of the deployed study for which to collect data.
*/
Expand Down
Loading

0 comments on commit a01c008

Please sign in to comment.