Documentation and DeviceSpec fix
This commit is contained in:
parent
7103786ec9
commit
aabf2b85a4
@ -35,6 +35,10 @@ Example view of a demo:
|
|||||||
|
|
||||||
![](docs/pictures/demo-view.png)
|
![](docs/pictures/demo-view.png)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
* [Creating a device](docs/Device%20and%20DeviceSpec.md)
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
|
|
||||||
@ -119,6 +123,11 @@ Example view of a demo:
|
|||||||
>
|
>
|
||||||
> **Maturity**: EXPERIMENTAL
|
> **Maturity**: EXPERIMENTAL
|
||||||
|
|
||||||
|
### [demo/many-devices](demo/many-devices)
|
||||||
|
>
|
||||||
|
>
|
||||||
|
> **Maturity**: EXPERIMENTAL
|
||||||
|
|
||||||
### [demo/mks-pdr900](demo/mks-pdr900)
|
### [demo/mks-pdr900](demo/mks-pdr900)
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
|
@ -39,7 +39,6 @@ public interface DevicePropertySpec<in D : Device, T> {
|
|||||||
*/
|
*/
|
||||||
@InternalDeviceAPI
|
@InternalDeviceAPI
|
||||||
public suspend fun read(device: D): T?
|
public suspend fun read(device: D): T?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,7 +13,6 @@ import kotlin.reflect.KProperty
|
|||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
public abstract class DeviceSpec<D : Device> {
|
public abstract class DeviceSpec<D : Device> {
|
||||||
//initializing meta property for everyone
|
//initializing meta property for everyone
|
||||||
@ -38,20 +37,24 @@ public abstract class DeviceSpec<D : Device> {
|
|||||||
return deviceProperty
|
return deviceProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T> registerProperty(
|
// public fun <T> registerProperty(
|
||||||
converter: MetaConverter<T>,
|
// converter: MetaConverter<T>,
|
||||||
readOnlyProperty: KProperty1<D, T>,
|
// readOnlyProperty: KProperty1<D, T>,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
// descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
): DevicePropertySpec<D, T> {
|
// ): DevicePropertySpec<D, T> {
|
||||||
val deviceProperty = object : DevicePropertySpec<D, T> {
|
// val deviceProperty = object : DevicePropertySpec<D, T> {
|
||||||
override val descriptor: PropertyDescriptor =
|
//
|
||||||
PropertyDescriptor(readOnlyProperty.name).apply(descriptorBuilder)
|
// override val descriptor: PropertyDescriptor = PropertyDescriptor(readOnlyProperty.name)
|
||||||
override val converter: MetaConverter<T> = converter
|
// .apply(descriptorBuilder)
|
||||||
override suspend fun read(device: D): T =
|
//
|
||||||
withContext(device.coroutineContext) { readOnlyProperty.get(device) }
|
// override val converter: MetaConverter<T> = converter
|
||||||
}
|
//
|
||||||
return registerProperty(deviceProperty)
|
// override suspend fun read(device: D): T = withContext(device.coroutineContext) {
|
||||||
}
|
// readOnlyProperty.get(device)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return registerProperty(deviceProperty)
|
||||||
|
// }
|
||||||
|
|
||||||
public fun <T> property(
|
public fun <T> property(
|
||||||
converter: MetaConverter<T>,
|
converter: MetaConverter<T>,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package space.kscience.controls.spec
|
package space.kscience.controls.spec
|
||||||
|
|
||||||
|
import space.kscience.controls.api.Device
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
import space.kscience.controls.api.PropertyDescriptor
|
||||||
import space.kscience.controls.api.metaDescriptor
|
import space.kscience.controls.api.metaDescriptor
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
@ -10,7 +11,7 @@ import kotlin.properties.ReadOnlyProperty
|
|||||||
|
|
||||||
//read only delegates
|
//read only delegates
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Boolean?
|
read: suspend D.() -> Boolean?
|
||||||
@ -35,7 +36,7 @@ private inline fun numberDescriptor(
|
|||||||
descriptorBuilder()
|
descriptorBuilder()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
public fun <D : Device> DeviceSpec<D>.numberProperty(
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
read: suspend D.() -> Number?
|
read: suspend D.() -> Number?
|
||||||
@ -46,7 +47,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
|||||||
read
|
read
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
public fun <D : Device> DeviceSpec<D>.doubleProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Double?
|
read: suspend D.() -> Double?
|
||||||
@ -57,7 +58,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
|||||||
read
|
read
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
public fun <D : Device> DeviceSpec<D>.stringProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> String?
|
read: suspend D.() -> String?
|
||||||
@ -73,7 +74,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
|||||||
read
|
read
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
public fun <D : Device> DeviceSpec<D>.metaProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Meta?
|
read: suspend D.() -> Meta?
|
||||||
@ -91,7 +92,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
|||||||
|
|
||||||
//read-write delegates
|
//read-write delegates
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Boolean?,
|
read: suspend D.() -> Boolean?,
|
||||||
@ -111,7 +112,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.booleanProperty(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
public fun <D : Device> DeviceSpec<D>.numberProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Number,
|
read: suspend D.() -> Number,
|
||||||
@ -119,7 +120,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.numberProperty(
|
|||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Number>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Number>>> =
|
||||||
mutableProperty(MetaConverter.number, numberDescriptor(descriptorBuilder), name, read, write)
|
mutableProperty(MetaConverter.number, numberDescriptor(descriptorBuilder), name, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
public fun <D : Device> DeviceSpec<D>.doubleProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Double,
|
read: suspend D.() -> Double,
|
||||||
@ -127,7 +128,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.doubleProperty(
|
|||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Double>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, Double>>> =
|
||||||
mutableProperty(MetaConverter.double, numberDescriptor(descriptorBuilder), name, read, write)
|
mutableProperty(MetaConverter.double, numberDescriptor(descriptorBuilder), name, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
public fun <D : Device> DeviceSpec<D>.stringProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> String,
|
read: suspend D.() -> String,
|
||||||
@ -135,7 +136,7 @@ public fun <D : DeviceBase<D>> DeviceSpec<D>.stringProperty(
|
|||||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, WritableDevicePropertySpec<D, String>>> =
|
||||||
mutableProperty(MetaConverter.string, descriptorBuilder, name, read, write)
|
mutableProperty(MetaConverter.string, descriptorBuilder, name, read, write)
|
||||||
|
|
||||||
public fun <D : DeviceBase<D>> DeviceSpec<D>.metaProperty(
|
public fun <D : Device> DeviceSpec<D>.metaProperty(
|
||||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
read: suspend D.() -> Meta,
|
read: suspend D.() -> Meta,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Module controls-magix-client
|
# Module controls-magix-client
|
||||||
|
|
||||||
|
Magix service for binding controls devices (both as RPC client and server
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -147,6 +147,7 @@ class DemoControllerView : View(title = " Demo controller remote") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DemoControllerApp : App(DemoControllerView::class) {
|
class DemoControllerApp : App(DemoControllerView::class) {
|
||||||
private val controller: DemoController by inject()
|
private val controller: DemoController by inject()
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package space.kscience.controls.demo
|
package space.kscience.controls.demo
|
||||||
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import space.kscience.controls.api.Device
|
||||||
import space.kscience.controls.api.metaDescriptor
|
import space.kscience.controls.api.metaDescriptor
|
||||||
import space.kscience.controls.spec.*
|
import space.kscience.controls.spec.*
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
@ -10,39 +11,47 @@ import space.kscience.dataforge.meta.ValueType
|
|||||||
import space.kscience.dataforge.meta.descriptors.value
|
import space.kscience.dataforge.meta.descriptors.value
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
|
||||||
class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<DemoDevice>(DemoDevice, context, meta) {
|
interface IDemoDevice: Device {
|
||||||
private var timeScaleState = 5000.0
|
var timeScaleState: Double
|
||||||
private var sinScaleState = 1.0
|
var sinScaleState: Double
|
||||||
private var cosScaleState = 1.0
|
var cosScaleState: Double
|
||||||
|
|
||||||
|
fun time(): Instant = Instant.now()
|
||||||
|
fun sinValue(): Double
|
||||||
|
fun cosValue(): Double
|
||||||
|
}
|
||||||
|
|
||||||
companion object : DeviceSpec<DemoDevice>(), Factory<DemoDevice> {
|
class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<IDemoDevice>(Companion, context, meta), IDemoDevice {
|
||||||
|
override var timeScaleState = 5000.0
|
||||||
|
override var sinScaleState = 1.0
|
||||||
|
override var cosScaleState = 1.0
|
||||||
|
|
||||||
|
override fun sinValue(): Double = sin(time().toEpochMilli().toDouble() / timeScaleState) * sinScaleState
|
||||||
|
|
||||||
|
override fun cosValue(): Double = cos(time().toEpochMilli().toDouble() / timeScaleState) * cosScaleState
|
||||||
|
|
||||||
|
companion object : DeviceSpec<IDemoDevice>(), Factory<DemoDevice> {
|
||||||
|
|
||||||
override fun build(context: Context, meta: Meta): DemoDevice = DemoDevice(context, meta)
|
override fun build(context: Context, meta: Meta): DemoDevice = DemoDevice(context, meta)
|
||||||
|
|
||||||
// register virtual properties based on actual object state
|
// register virtual properties based on actual object state
|
||||||
val timeScale by mutableProperty(MetaConverter.double, DemoDevice::timeScaleState) {
|
val timeScale by mutableProperty(MetaConverter.double, IDemoDevice::timeScaleState) {
|
||||||
metaDescriptor {
|
metaDescriptor {
|
||||||
type(ValueType.NUMBER)
|
type(ValueType.NUMBER)
|
||||||
}
|
}
|
||||||
info = "Real to virtual time scale"
|
info = "Real to virtual time scale"
|
||||||
}
|
}
|
||||||
|
|
||||||
val sinScale by mutableProperty(MetaConverter.double, DemoDevice::sinScaleState)
|
val sinScale by mutableProperty(MetaConverter.double, IDemoDevice::sinScaleState)
|
||||||
val cosScale by mutableProperty(MetaConverter.double, DemoDevice::cosScaleState)
|
val cosScale by mutableProperty(MetaConverter.double, IDemoDevice::cosScaleState)
|
||||||
|
|
||||||
val sin by doubleProperty {
|
val sin by doubleProperty(read = IDemoDevice::sinValue)
|
||||||
val time = Instant.now()
|
val cos by doubleProperty(read = IDemoDevice::cosValue)
|
||||||
kotlin.math.sin(time.toEpochMilli().toDouble() / timeScaleState) * sinScaleState
|
|
||||||
}
|
|
||||||
|
|
||||||
val cos by doubleProperty {
|
|
||||||
val time = Instant.now()
|
|
||||||
kotlin.math.cos(time.toEpochMilli().toDouble() / timeScaleState) * cosScaleState
|
|
||||||
}
|
|
||||||
|
|
||||||
val coordinates by metaProperty(
|
val coordinates by metaProperty(
|
||||||
descriptorBuilder = {
|
descriptorBuilder = {
|
||||||
@ -52,15 +61,21 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<DemoDevice>(DemoDe
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Meta {
|
Meta {
|
||||||
val time = Instant.now()
|
"time" put time().toEpochMilli()
|
||||||
"time" put time.toEpochMilli()
|
|
||||||
"x" put read(sin)
|
"x" put read(sin)
|
||||||
"y" put read(cos)
|
"y" put read(cos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override suspend fun DemoDevice.onOpen() {
|
val resetScale by action(MetaConverter.meta, MetaConverter.meta) {
|
||||||
|
write(timeScale, 5000.0)
|
||||||
|
write(sinScale, 1.0)
|
||||||
|
write(cosScale, 1.0)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun IDemoDevice.onOpen() {
|
||||||
launch {
|
launch {
|
||||||
read(sinScale)
|
read(sinScale)
|
||||||
read(cosScale)
|
read(cosScale)
|
||||||
@ -72,13 +87,5 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<DemoDevice>(DemoDe
|
|||||||
read(coordinates)
|
read(coordinates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val resetScale by action(MetaConverter.meta, MetaConverter.meta) {
|
|
||||||
write(timeScale, 5000.0)
|
|
||||||
write(sinScale, 1.0)
|
|
||||||
write(cosScale, 1.0)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
# Module all-things
|
# Module many-devices
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class MassDevice(context: Context, meta: Meta) : DeviceBySpec<MassDevice>(MassDe
|
|||||||
val value by doubleProperty { randomValue }
|
val value by doubleProperty { randomValue }
|
||||||
|
|
||||||
override suspend fun MassDevice.onOpen() {
|
override suspend fun MassDevice.onOpen() {
|
||||||
doRecurring(200.milliseconds) {
|
doRecurring(2.milliseconds) {
|
||||||
read(value)
|
read(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ fun main() {
|
|||||||
RSocketMagixFlowPlugin()
|
RSocketMagixFlowPlugin()
|
||||||
)
|
)
|
||||||
|
|
||||||
val numDevices = 1000
|
val numDevices = 100
|
||||||
|
|
||||||
repeat(numDevices) {
|
repeat(numDevices) {
|
||||||
val deviceContext = Context("Device${it}") {
|
val deviceContext = Context("Device${it}") {
|
||||||
@ -99,7 +99,7 @@ fun main() {
|
|||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(1000)
|
delay(200)
|
||||||
val now = Clock.System.now()
|
val now = Clock.System.now()
|
||||||
x.strings = latest.keys
|
x.strings = latest.keys
|
||||||
y.numbers = latest.values.map { now.minus(it).inWholeMilliseconds / 1000.0 }
|
y.numbers = latest.values.map { now.minus(it).inWholeMilliseconds / 1000.0 }
|
||||||
|
147
docs/Device and DeviceSpec.md
Normal file
147
docs/Device and DeviceSpec.md
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# Device and DeviceSpec - what is the difference?
|
||||||
|
|
||||||
|
One of the problems with creating device servers is that one needs device properties to be accessible both in static and dynamic mode. For example, consider a property:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
var property: Double = 1.0
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
We can change the state of the property, but neither propagate this change to the device, nor observe changes made to the property value by the device. The propagation to the device state could be added via custom getters and setters:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
var property: Double
|
||||||
|
get() = device.read(...)
|
||||||
|
set(value){
|
||||||
|
device.write(..., value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
But this approach does not solve the observability problem. Neither it exposes the property to be automatically collected from the outside of the device
|
||||||
|
|
||||||
|
The next stop is to use Kotlin delegates:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
var property by property(
|
||||||
|
read = { device.read(...)},
|
||||||
|
write = {value-> device.write(..., value)}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Delegate solves almost all problems: it allows reading and writing the hardware, also it allows registering observation handles to listen to property changes externally (one needs to use [delegate providers](https://kotlinlang.org/docs/delegated-properties.html#providing-a-delegate) to register properties eagerly on instance creation. The only problem left is that properties registered this way are created on object instance creation and not accessible without creating the device instance.
|
||||||
|
|
||||||
|
In order to solve this problem `Controls-kt` allows to separate device properties specification from the device itself.
|
||||||
|
|
||||||
|
Check [DemoDevice](../demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt) for an example of a device with a specification.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
interface IDemoDevice: Device {
|
||||||
|
var timeScaleState: Double
|
||||||
|
var sinScaleState: Double
|
||||||
|
var cosScaleState: Double
|
||||||
|
|
||||||
|
fun time(): Instant = Instant.now()
|
||||||
|
fun sinValue(): Double
|
||||||
|
fun cosValue(): Double
|
||||||
|
}
|
||||||
|
|
||||||
|
class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<IDemoDevice>(Companion, context, meta), IDemoDevice {
|
||||||
|
override var timeScaleState = 5000.0
|
||||||
|
override var sinScaleState = 1.0
|
||||||
|
override var cosScaleState = 1.0
|
||||||
|
|
||||||
|
override fun sinValue(): Double = sin(time().toEpochMilli().toDouble() / timeScaleState) * sinScaleState
|
||||||
|
|
||||||
|
override fun cosValue(): Double = cos(time().toEpochMilli().toDouble() / timeScaleState) * cosScaleState
|
||||||
|
|
||||||
|
companion object : DeviceSpec<IDemoDevice>(), Factory<DemoDevice> {
|
||||||
|
|
||||||
|
override fun build(context: Context, meta: Meta): DemoDevice = DemoDevice(context, meta)
|
||||||
|
|
||||||
|
// register virtual properties based on actual object state
|
||||||
|
val timeScale by mutableProperty(MetaConverter.double, IDemoDevice::timeScaleState) {
|
||||||
|
metaDescriptor {
|
||||||
|
type(ValueType.NUMBER)
|
||||||
|
}
|
||||||
|
info = "Real to virtual time scale"
|
||||||
|
}
|
||||||
|
|
||||||
|
val sinScale by mutableProperty(MetaConverter.double, IDemoDevice::sinScaleState)
|
||||||
|
val cosScale by mutableProperty(MetaConverter.double, IDemoDevice::cosScaleState)
|
||||||
|
|
||||||
|
val sin by doubleProperty(read = IDemoDevice::sinValue)
|
||||||
|
val cos by doubleProperty(read = IDemoDevice::cosValue)
|
||||||
|
|
||||||
|
val coordinates by metaProperty(
|
||||||
|
descriptorBuilder = {
|
||||||
|
metaDescriptor {
|
||||||
|
value("time", ValueType.NUMBER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Meta {
|
||||||
|
"time" put time().toEpochMilli()
|
||||||
|
"x" put read(sin)
|
||||||
|
"y" put read(cos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val resetScale by action(MetaConverter.meta, MetaConverter.meta) {
|
||||||
|
write(timeScale, 5000.0)
|
||||||
|
write(sinScale, 1.0)
|
||||||
|
write(cosScale, 1.0)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun IDemoDevice.onOpen() {
|
||||||
|
launch {
|
||||||
|
read(sinScale)
|
||||||
|
read(cosScale)
|
||||||
|
read(timeScale)
|
||||||
|
}
|
||||||
|
doRecurring(50.milliseconds) {
|
||||||
|
read(sin)
|
||||||
|
read(cos)
|
||||||
|
read(coordinates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Device body
|
||||||
|
|
||||||
|
Device inherits the class `DeviceBySpec` and takes the specification as an argument. The device itself contains hardware logic, but not communication logic. For example, it does not define properties exposed to the external observers. In the given example, it stores states for virtual properties (states) and contains logic to request current values for two properties.
|
||||||
|
|
||||||
|
States for logical properties could also be stored via device mechanics without explicit state variables.
|
||||||
|
|
||||||
|
## Device specification
|
||||||
|
|
||||||
|
Specification is an object (singleton) that defines property scheme for external communication. Specification could define the following components:
|
||||||
|
|
||||||
|
* Properties specifications via `property` delegate or specialized delegate variants.
|
||||||
|
* Action specification via `action` delegate or specialized delegates.
|
||||||
|
* Initialization logic (override `onOpen`).
|
||||||
|
* Finalization logic (override `onClose`).
|
||||||
|
|
||||||
|
Properties can reference properties and method of the device. They also could contain device-independent logic or manipulate properties (like `coordinates` property in the example does). It is not recommended to implement direct device integration from the spec (yet it is possible).
|
||||||
|
|
||||||
|
## Device specification abstraction
|
||||||
|
|
||||||
|
In the example, the specification is a companion for `DemoDevice` and could be used as a factory for the device. Yet it works with the abstraction `IDemoDevice`. It is done to demonstrate that the device logic could be separated from the hardware logic. For example, one could swap a real device or a virtual device anytime without changing integrations anywhere. There could be also layers of abstractions for a device.
|
||||||
|
|
||||||
|
## Access to properties
|
||||||
|
|
||||||
|
In order to access property values, one needs to use both the device instance and property descriptor from the spec like follows:
|
||||||
|
```kotlin
|
||||||
|
val device = DemoDevice.build()
|
||||||
|
|
||||||
|
val res = device.read(DemoDevice.sin)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other ways to create a device
|
||||||
|
|
||||||
|
It is not obligatory to use `DeviceBySpec` to define a `Device`. One could directly implement the `Device` interface or use intermediate abstraction `DeviceBase`, which uses properties schema but allows to define it manually.
|
||||||
|
|
4
docs/templates/README-TEMPLATE.md
vendored
4
docs/templates/README-TEMPLATE.md
vendored
@ -35,6 +35,10 @@ Example view of a demo:
|
|||||||
|
|
||||||
![](docs/pictures/demo-view.png)
|
![](docs/pictures/demo-view.png)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
* [Creating a device](docs/Device%20and%20DeviceSpec.md)
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
${modules}
|
${modules}
|
||||||
|
Loading…
Reference in New Issue
Block a user