Skip to content

Commit

Permalink
Release 1.0.0-alpha.34
Browse files Browse the repository at this point in the history
Merge pull request #278 from cph-cachet/develop
  • Loading branch information
Whathecode authored Jun 23, 2021
2 parents 501d40d + 3f42711 commit 41fd1e0
Show file tree
Hide file tree
Showing 46 changed files with 342 additions and 102 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ buildscript {
// Version used for all submodule artifacts.
// Snapshot publishing changes (or adds) the suffix after '-' with 'SNAPSHOT' prior to publishing.
// The 'publishSigned' task publishes to SonaType's staging repo and 'publishSnapshot' instantly uploads to the snapshots repo.
globalVersion = '1.0.0-alpha.33'
globalVersion = '1.0.0-alpha.34'

versions = [
// Kotlin multiplatform versions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dk.cachet.carp.common.application.data.DataType
import dk.cachet.carp.common.application.devices.AnyDeviceDescriptor
import dk.cachet.carp.common.application.devices.AnyMasterDeviceDescriptor
import dk.cachet.carp.common.application.devices.DeviceRegistration
import dk.cachet.carp.common.application.tasks.Measure
import dk.cachet.carp.common.domain.AggregateRoot
import dk.cachet.carp.common.domain.DomainEvent
import dk.cachet.carp.deployments.application.DeploymentService
Expand Down Expand Up @@ -191,7 +192,10 @@ class StudyRuntime private constructor(
?: throw UnsupportedOperationException( "Connecting to device of type \"$deviceType\" is not supported on this client." )
}

val dataTypes = tasks.flatMap { it.measures }.map { it.type }.distinct()
val dataTypes = tasks
.flatMap { it.measures.filterIsInstance<Measure.DataStream>() }
.map { it.type }
.distinct()
for ( dataType in dataTypes )
{
val supportsData =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class StudyRuntimeTest
fun initialize_fails_for_incorrect_deviceRegistration() = runSuspendTest {
val (deploymentService, deploymentStatus) = createStudyDeployment( createSmartphoneStudy() )

val incorrectRegistration = AltBeaconDeviceRegistration( 0, UUID.randomUUID(), 0, 0 )
val incorrectRegistration = AltBeaconDeviceRegistration( 0, UUID.randomUUID(), 0, 0, 0 )
val dataListener = createDataListener()
assertFailsWith<IllegalArgumentException> {
StudyRuntime.initialize(
Expand Down Expand Up @@ -258,10 +258,10 @@ class StudyRuntimeTest
fun tryDeployment_succeeds_when_data_types_of_protocol_measures_are_supported() = runSuspendTest {
// Create protocol that measures on smartphone and one connected device.
val protocol = createSmartphoneWithConnectedDeviceStudy()
val masterTask = StubTaskDescriptor( "Master measure", listOf( Measure( STUB_DATA_TYPE ) ) )
val masterTask = StubTaskDescriptor( "Master measure", listOf( Measure.DataStream( STUB_DATA_TYPE ) ) )
protocol.addTaskControl( smartphone.atStartOfStudy().start( masterTask, smartphone ) )
val connectedDataType = DataType( "custom", "type" )
val connectedTask = StubTaskDescriptor( "Connected measure", listOf( Measure( connectedDataType ) ) )
val connectedTask = StubTaskDescriptor( "Connected measure", listOf( Measure.DataStream( connectedDataType ) ) )
protocol.addTaskControl( smartphone.atStartOfStudy().start( connectedTask, connectedDevice ) )

// Create a data listener which supports the requested devices and types in the protocol
Expand Down Expand Up @@ -289,7 +289,7 @@ class StudyRuntimeTest
fun tryDeployment_fails_when_requested_data_cannot_be_collected() = runSuspendTest {
// Create a protocol that has one measure.
val protocol = createSmartphoneStudy()
val task = StubTaskDescriptor( "One measure", listOf( Measure( STUB_DATA_TYPE ) ) )
val task = StubTaskDescriptor( "One measure", listOf( Measure.DataStream( STUB_DATA_TYPE ) ) )
protocol.addTaskControl( smartphone.atStartOfStudy().start( task, smartphone ) )

// Initializing study runtime for the smartphone deployment should fail since StubMeasure can't be collected.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,16 @@ object CarpDataTypes : DataTypeMetaDataList()
* Acceleration along perpendicular x, y, and z axes.
*/
val ACCELERATION = add( ACCELERATION_TYPE_NAME, "Accelerometry", DataTimeType.POINT )

internal const val SIGNAL_STRENGTH_TYPE_NAME = "$CARP_NAMESPACE.signalstrength"
/**
* The received signal strength of a wireless device.
*/
val SIGNAL_STRENGTH = add( SIGNAL_STRENGTH_TYPE_NAME, "Signal strength", DataTimeType.POINT )

internal const val TRIGGERED_TASK_TYPE_NAME = "$CARP_NAMESPACE.triggeredtask"
/**
* A task which was started or stopped by a trigger, referring to identifiers in the study protocol.
*/
val TRIGGERED_TASK = add( TRIGGERED_TASK_TYPE_NAME, "Triggered task", DataTimeType.POINT )
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dk.cachet.carp.common.application.data
import dk.cachet.carp.common.application.Immutable
import dk.cachet.carp.common.application.ImplementAsDataClass
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.Serializable


/**
Expand All @@ -12,3 +13,11 @@ import kotlinx.serialization.Polymorphic
@Immutable
@ImplementAsDataClass
interface Data


/**
* Placeholder for generic `Data` types to indicate there is no associated data.
* This should not be serialized; instead, nullable `Data` should be used.
*/
@Serializable
object NoData : Data
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ open class DataTypeMetaDataList : EnumObjectList<DataTypeMetaData>()
* which can be displayed to the user using [displayName] and should be stored temporally as specified by [timeType].
*/
fun add( fullyQualifiedName: String, displayName: String, timeType: DataTimeType ): DataTypeMetaData =
DataTypeMetaData( DataType.fromString( fullyQualifiedName ), displayName, timeType )
super.add( DataTypeMetaData( DataType.fromString( fullyQualifiedName ), displayName, timeType ) )
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dk.cachet.carp.common.application.data

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable


/**
* The relative received signal strength of a wireless device.
* The unit of the received signal strength indicator ([rssi]) is arbitrary and determined by the chip manufacturer,
* but the greater the value, the stronger the signal.
*/
@Serializable
@SerialName( CarpDataTypes.SIGNAL_STRENGTH_TYPE_NAME )
data class SignalStrength( val rssi: Short ) : Data
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dk.cachet.carp.common.application.data

import dk.cachet.carp.common.application.triggers.TaskControl
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable


/**
* Indicates the task with [taskName] was started or stopped ([control]) by the trigger with [triggerId]
* on the device with [destinationDeviceRoleName], referring to identifiers in the study protocol.
* [triggerData] may contain additional information related to the circumstances which caused the trigger to fire.
*/
@Serializable
@SerialName( CarpDataTypes.TRIGGERED_TASK_TYPE_NAME )
data class TriggeredTask(
val triggerId: Int,
val taskName: String,
val destinationDeviceRoleName: String,
val control: TaskControl.Control,
val triggerData: Data? = null
) : Data
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package dk.cachet.carp.common.application.devices

import dk.cachet.carp.common.application.Trilean
import dk.cachet.carp.common.application.UUID
import dk.cachet.carp.common.application.data.CarpDataTypes
import dk.cachet.carp.common.application.data.DataType
import dk.cachet.carp.common.application.sampling.DataTypeSamplingSchemeList
import dk.cachet.carp.common.application.sampling.NoOptionsSamplingScheme
import dk.cachet.carp.common.application.sampling.SamplingConfiguration
import dk.cachet.carp.common.application.tasks.TaskDescriptorList
import dk.cachet.carp.common.infrastructure.serialization.NotSerializable
Expand All @@ -18,11 +20,15 @@ import kotlin.reflect.KClass
data class AltBeacon( override val roleName: String ) : DeviceDescriptor<AltBeaconDeviceRegistration, AltBeaconDeviceRegistrationBuilder>()
{
object Sensors : DataTypeSamplingSchemeList()
object Tasks : TaskDescriptorList()
{
/**
* The signal strength as measured by the device listening to the [AltBeacon].
*/
val SIGNAL_STRENGTH = add( NoOptionsSamplingScheme( CarpDataTypes.SIGNAL_STRENGTH ) )
}

object Tasks : TaskDescriptorList()

// The AltBeacon protocol does not expose any measures. Other devices measure proximity to the beacon.
// TODO: Some beacons do include information such as battery charge and temperature.
override fun getSupportedDataTypes(): Set<DataType> = Sensors.getDataTypes()

override val defaultSamplingConfiguration: Map<DataType, SamplingConfiguration> = emptyMap()
Expand Down Expand Up @@ -55,9 +61,23 @@ data class AltBeaconDeviceRegistration(
/**
* The last 2 bytes of the beacon identifier, commonly named minor ID.
*/
val minorId: Short
val minorId: Short,
/**
* The average received signal strength at 1 meter from the beacon in decibel-milliwatts (dBm).
* This value is constrained from -127 to 0.
*/
val referenceRssi: Short
) : DeviceRegistration()
{
companion object
{
val REFERENCE_RSS_RANGE: IntRange = -127..0
}
init
{
require( referenceRssi in REFERENCE_RSS_RANGE ) { "Reference RSSI needs to be in the range from -127 to 0." }
}

override val deviceId: String =
// TODO: Remove this workaround once JS serialization bug is fixed: https://github.com/Kotlin/kotlinx.serialization/issues/716
if ( arrayOf( manufacturerId, organizationId, majorId, minorId ).any { it == null } ) ""
Expand Down Expand Up @@ -88,6 +108,14 @@ class AltBeaconDeviceRegistrationBuilder : DeviceRegistrationBuilder<AltBeaconDe
*/
var minorId: Short = 0x0000

/**
* The average received signal strength at 1 meter from the beacon in decibel-milliwatts (dBm).
* This value is constrained from -127 to 0.
*
* TODO: This presumes that beacons have a fixed reference RSSI; is this the case?
*/
var referenceRssi: Short = 0

override fun build(): AltBeaconDeviceRegistration =
AltBeaconDeviceRegistration( manufacturerId, organizationId, majorId, minorId )
AltBeaconDeviceRegistration( manufacturerId, organizationId, majorId, minorId, referenceRssi )
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ abstract class DeviceDescriptor<
abstract val roleName: String

/**
* The set of [DataType]s defining which data can be collected on this device.
* The set of [DataType]s defining which data stream data can be collected on this device.
*/
abstract fun getSupportedDataTypes(): Set<DataType>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ abstract class DataTypeSamplingScheme<TConfigBuilder : SamplingConfigurationBuil
*
* @throws IllegalArgumentException when a sampling configuration is built which breaks constraints specified in this sampling scheme.
*/
fun measure( samplingConfigurationBuilder: (TConfigBuilder.() -> Unit)? = null ): Measure =
Measure( dataType.type, samplingConfigurationBuilder?.let { samplingConfiguration( it ) } )
fun measure( samplingConfigurationBuilder: (TConfigBuilder.() -> Unit)? = null ): Measure.DataStream =
Measure.DataStream( dataType.type, samplingConfigurationBuilder?.let { samplingConfiguration( it ) } )

/**
* Determines whether [configuration] is valid for the constraints defined in this sampling scheme.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,29 @@ import kotlinx.serialization.Serializable


/**
* Defines data that needs to be measured/collected from a data stream on a [DeviceDescriptor],
* as part of a task defined by [TaskDescriptor].
* Defines data that needs to be measured/collected passively as part of a task defined by [TaskDescriptor].
*/
@Serializable
data class Measure(
sealed class Measure
{
/**
* The type of data this measure collects.
* Defines data that needs to be measured/collected from a data stream on a [DeviceDescriptor].
*/
val type: DataType,
@Serializable
data class DataStream(
/**
* The type of data this measure collects.
*/
val type: DataType,
/**
* Override the default configuration on how to sample the data stream of the matching [type] on the device.
*/
val overrideSamplingConfiguration: SamplingConfiguration? = null
) : Measure()

/**
* Override the default configuration on how to sample the data stream of the matching [type] on the device.
* Specify that the data related to the trigger with [triggerId] which started or stopped the task should be measured.
*/
val overrideSamplingConfiguration: SamplingConfiguration? = null
)
@Serializable
data class TriggerData( val triggerId: Int ) : Measure()
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface TaskDescriptor
val name: String

/**
* The data which needs to be collected/measured as part of this task.
* The data which needs to be collected/measured passively as part of this task.
*/
val measures: List<Measure>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dk.cachet.carp.common.application.triggers

import dk.cachet.carp.common.application.TimeSpan
import dk.cachet.carp.common.application.data.NoData
import dk.cachet.carp.common.application.devices.AnyMasterDeviceDescriptor
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
Expand All @@ -16,7 +17,7 @@ import kotlinx.serialization.Transient
data class ElapsedTimeTrigger private constructor(
override val sourceDeviceRoleName: String,
val elapsedTime: TimeSpan
) : Trigger()
) : Trigger<NoData>()
{
@Transient
override val requiresMasterDevice: Boolean = true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dk.cachet.carp.common.application.triggers

import dk.cachet.carp.common.application.data.NoData
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient

Expand All @@ -18,7 +19,7 @@ data class ManualTrigger(
* An optional description elaborating on what happens when initiating this trigger.
*/
val description: String = ""
) : Trigger()
) : Trigger<NoData>()
{
@Transient
override val requiresMasterDevice: Boolean = true // Software is needed to display this to the user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dk.cachet.carp.common.application.triggers

import dk.cachet.carp.common.application.RecurrenceRule
import dk.cachet.carp.common.application.TimeOfDay
import dk.cachet.carp.common.application.data.NoData
import dk.cachet.carp.common.application.devices.AnyMasterDeviceDescriptor
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
Expand All @@ -18,7 +19,7 @@ data class ScheduledTrigger private constructor(
override val sourceDeviceRoleName: String,
val time: TimeOfDay,
val recurrenceRule: RecurrenceRule
) : Trigger()
) : Trigger<NoData>()
{
@Transient
override val requiresMasterDevice: Boolean = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dk.cachet.carp.common.application.triggers

import dk.cachet.carp.common.application.Immutable
import dk.cachet.carp.common.application.ImplementAsDataClass
import dk.cachet.carp.common.application.data.Data
import dk.cachet.carp.common.application.devices.DeviceDescriptor
import dk.cachet.carp.common.application.devices.MasterDeviceDescriptor
import kotlinx.serialization.Polymorphic
Expand All @@ -10,14 +11,14 @@ import kotlinx.serialization.Transient


/**
* Any condition on a device ([DeviceDescriptor]) which starts or stops tasks at certain points in time when the condition applies.
* Any condition on a device ([DeviceDescriptor]) used to start or stop tasks at certain points in time when the condition applies.
* The condition can either be time-bound, based on data streams, initiated by a user of the platform, or a combination of these.
*/
@Serializable
@Polymorphic
@Immutable
@ImplementAsDataClass
abstract class Trigger
abstract class Trigger<TData : Data>
{
/**
* Determines whether the trigger needs to be evaluated on a master device ([MasterDeviceDescriptor]).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ val COMMON_SERIAL_MODULE = SerializersModule {
// HACK: explicit serializer needs to be registered for object declarations due to limitation of the JS legacy backend.
// https://github.com/Kotlin/kotlinx.serialization/issues/1138#issuecomment-707989920
// This can likely be removed once we upgrade to the new IR backend.
subclass( NoData::class, NoData.serializer() )
subclass( RRInterval::class, RRInterval.serializer() )
subclass( SignalStrength::class )
subclass( SensorSkinContact::class )
subclass( StepCount::class )
subclass( TriggeredTask::class )

// InputDataType classes.
subclass(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dk.cachet.carp.common.infrastructure.serialization

import dk.cachet.carp.common.application.data.NoData
import dk.cachet.carp.common.application.triggers.Trigger
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
Expand All @@ -11,10 +12,10 @@ import kotlinx.serialization.json.Json
*/
@Serializable( TriggerSerializer::class )
data class CustomTrigger( override val className: String, override val jsonSource: String, val serializer: Json ) :
Trigger(), UnknownPolymorphicWrapper
Trigger<NoData>(), UnknownPolymorphicWrapper
{
@Serializable
private data class BaseMembers( override val sourceDeviceRoleName: String ) : Trigger()
private data class BaseMembers( override val sourceDeviceRoleName: String ) : Trigger<NoData>()

override val sourceDeviceRoleName: String

Expand All @@ -29,5 +30,5 @@ data class CustomTrigger( override val className: String, override val jsonSourc
/**
* Custom serializer for a [Trigger] which enables deserializing types that are unknown at runtime, yet extend from [Trigger].
*/
object TriggerSerializer : KSerializer<Trigger>
object TriggerSerializer : KSerializer<Trigger<*>>
by createUnknownPolymorphicSerializer( { className, json, serializer -> CustomTrigger( className, json, serializer ) } )
Loading

0 comments on commit 41fd1e0

Please sign in to comment.