Clean up property access syntax

This commit is contained in:
Alexander Nozik 2023-05-07 11:13:42 +03:00
parent 86273101b6
commit 691b2ae67a
23 changed files with 170 additions and 168 deletions

View File

@ -1,3 +1,45 @@
job("Build and run tests") { import kotlin.io.path.readText
gradle("gradle:8.1.1-jdk11", "build")
job("Build") {
gradlew("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3", "build")
}
job("Publish") {
startOn {
gitPush { enabled = false }
}
container("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3") {
env["SPACE_USER"] = "{{ project:space_user }}"
env["SPACE_TOKEN"] = "{{ project:space_token }}"
kotlinScript { api ->
val spaceUser = System.getenv("SPACE_USER")
val spaceToken = System.getenv("SPACE_TOKEN")
// write the version to the build directory
api.gradlew("version")
//read the version from build file
val version = java.nio.file.Path.of("build/project-version.txt").readText()
val revisionSuffix = if (version.endsWith("SNAPSHOT")) {
"-" + api.gitRevision().take(7)
} else {
""
}
api.space().projects.automation.deployments.start(
project = api.projectIdentifier(),
targetIdentifier = TargetIdentifier.Key("maps-kt"),
version = version+revisionSuffix,
// automatically update deployment status based on the status of a job
syncWithAutomationJob = true
)
api.gradlew(
"publishAllPublicationsToSpaceRepository",
"-Ppublishing.space.user=\"$spaceUser\"",
"-Ppublishing.space.token=\"$spaceToken\"",
)
}
}
} }

View File

@ -35,8 +35,4 @@ ksciencePublish {
space("https://maven.pkg.jetbrains.space/spc/p/controls/maven") space("https://maven.pkg.jetbrains.space/spc/p/controls/maven")
} }
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md") readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")
apiValidation {
validationDisabled = true
}

View File

@ -13,6 +13,7 @@ kscience {
useSerialization{ useSerialization{
json() json()
} }
useContextReceivers()
dependencies { dependencies {
api("space.kscience:dataforge-io:$dataforgeVersion") api("space.kscience:dataforge-io:$dataforgeVersion")
api(spclibs.kotlinx.datetime) api(spclibs.kotlinx.datetime)

View File

@ -4,7 +4,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import space.kscience.controls.api.* import space.kscience.controls.api.*
@ -18,7 +17,7 @@ import kotlin.coroutines.CoroutineContext
* A base abstractions for [Device], introducing specifications for properties * A base abstractions for [Device], introducing specifications for properties
*/ */
@OptIn(InternalDeviceAPI::class) @OptIn(InternalDeviceAPI::class)
public abstract class DeviceBase<D : DeviceBase<D>>( public abstract class DeviceBase<D : Device>(
override val context: Context = Global, override val context: Context = Global,
override val meta: Meta = Meta.EMPTY, override val meta: Meta = Meta.EMPTY,
) : Device { ) : Device {
@ -75,7 +74,7 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
/** /**
* Update logical state using given [spec] and its convertor * Update logical state using given [spec] and its convertor
*/ */
protected suspend fun <T> updateLogical(spec: DevicePropertySpec<D, T>, value: T) { public suspend fun <T> updateLogical(spec: DevicePropertySpec<D, T>, value: T) {
updateLogical(spec.name, spec.converter.objectToMeta(value)) updateLogical(spec.name, spec.converter.objectToMeta(value))
} }
@ -99,7 +98,7 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
} }
override suspend fun writeProperty(propertyName: String, value: Meta): Unit { override suspend fun writeProperty(propertyName: String, value: Meta): Unit {
//If there is a physical property with given name, invalidate logical property and write physical one //If there is a physical property with a given name, invalidate logical property and write physical one
(properties[propertyName] as? WritableDevicePropertySpec<D, out Any?>)?.let { (properties[propertyName] as? WritableDevicePropertySpec<D, out Any?>)?.let {
invalidate(propertyName) invalidate(propertyName)
it.writeMeta(self, value) it.writeMeta(self, value)
@ -111,49 +110,13 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
override suspend fun execute(action: String, argument: Meta?): Meta? = override suspend fun execute(action: String, argument: Meta?): Meta? =
actions[action]?.executeWithMeta(self, argument) actions[action]?.executeWithMeta(self, argument)
/**
* Read typed value and update/push event if needed.
* Return null if property read is not successful or property is undefined.
*/
public suspend fun <T> DevicePropertySpec<D, T>.readOrNull(): T? {
val res = read(self) ?: return null
updateLogical(name, converter.objectToMeta(res))
return res
}
public suspend fun <T> DevicePropertySpec<D, T>.read(): T =
readOrNull() ?: error("Failed to read property $name state")
public fun <T> DevicePropertySpec<D, T>.get(): T? = getProperty(name)?.let(converter::metaToObject)
/**
* Write typed property state and invalidate logical state
*/
public suspend fun <T> WritableDevicePropertySpec<D, T>.write(value: T) {
invalidate(name)
write(self, value)
//perform asynchronous read and update after write
launch {
read()
}
}
/**
* Reset logical state of a property
*/
public suspend fun DevicePropertySpec<D, *>.invalidate() {
invalidate(name)
}
public suspend operator fun <I, O> DeviceActionSpec<D, I, O>.invoke(input: I? = null): O? = execute(self, input)
} }
/** /**
* A device generated from specification * A device generated from specification
* @param D recursive self-type for properties and actions * @param D recursive self-type for properties and actions
*/ */
public open class DeviceBySpec<D : DeviceBySpec<D>>( public open class DeviceBySpec<D : Device>(
public val spec: DeviceSpec<in D>, public val spec: DeviceSpec<in D>,
context: Context = Global, context: Context = Global,
meta: Meta = Meta.EMPTY, meta: Meta = Meta.EMPTY,

View File

@ -91,30 +91,56 @@ public suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta(
return res?.let { outputConverter.objectToMeta(res) } return res?.let { outputConverter.objectToMeta(res) }
} }
/**
public suspend fun <D : DeviceBase<D>, T : Any> D.read( * Read typed value and update/push event if needed.
propertySpec: DevicePropertySpec<D, T>, * Return null if property read is not successful or property is undefined.
): T = propertySpec.read() */
@OptIn(InternalDeviceAPI::class)
public suspend fun <D : Device, T : Any> D.read( public suspend fun <T, D : Device> D.readOrNull(propertySpec: DevicePropertySpec<D, T>): T? {
propertySpec: DevicePropertySpec<D, T>, val res = propertySpec.read(this) ?: return null
): T = propertySpec.converter.metaToObject(readProperty(propertySpec.name)) @Suppress("UNCHECKED_CAST")
?: error("Property meta converter returned null") (this as? DeviceBase<D>)?.updateLogical(propertySpec, res)
return res
public fun <D : Device, T> D.write(
propertySpec: WritableDevicePropertySpec<D, T>,
value: T,
): Job = launch {
writeProperty(propertySpec.name, propertySpec.converter.objectToMeta(value))
} }
public fun <D : DeviceBase<D>, T> D.write( public suspend fun <T, D : Device> D.read(propertySpec: DevicePropertySpec<D, T>): T =
propertySpec: WritableDevicePropertySpec<D, T>, readOrNull(propertySpec) ?: error("Failed to read property ${propertySpec.name} state")
value: T,
): Job = launch { public operator fun <T, D : Device> D.get(propertySpec: DevicePropertySpec<D, T>): T? =
propertySpec.write(value) getProperty(propertySpec.name)?.let(propertySpec.converter::metaToObject)
/**
* Write typed property state and invalidate logical state
*/
@OptIn(InternalDeviceAPI::class)
public suspend fun <T, D : Device> D.write(propertySpec: WritableDevicePropertySpec<D, T>, value: T) {
invalidate(propertySpec.name)
propertySpec.write(this, value)
//perform asynchronous read and update after write
launch {
read(propertySpec)
}
} }
/**
* Fire and forget variant of property writing. Actual write is performed asynchronously on a [Device] scope
*/
public operator fun <T, D : Device> D.set(propertySpec: WritableDevicePropertySpec<D, T>, value: T): Unit {
launch {
write(propertySpec, value)
}
}
/**
* Reset the logical state of a property
*/
public suspend fun <D : Device> D.invalidate(propertySpec: DevicePropertySpec<D, *>) {
invalidate(propertySpec.name)
}
public suspend fun <I, O, D : Device> D.execute(actionSpec: DeviceActionSpec<D, I, O>, input: I? = null): O? =
actionSpec.execute(this, input)
/** /**
* A type safe property change listener * A type safe property change listener
*/ */

View File

@ -1,10 +0,0 @@
package space.kscience.controls.spec
import kotlinx.coroutines.runBlocking
/**
* Blocking property get call
*/
public operator fun <D : DeviceBase<D>, T : Any> D.get(
propertySpec: DevicePropertySpec<D, T>
): T? = runBlocking { read(propertySpec) }

View File

@ -1,6 +1,7 @@
package space.kscience.controls.modbus package space.kscience.controls.modbus
import com.ghgande.j2mod.modbus.facade.AbstractModbusMaster import com.ghgande.j2mod.modbus.facade.AbstractModbusMaster
import space.kscience.controls.api.Device
import space.kscience.controls.api.DeviceHub import space.kscience.controls.api.DeviceHub
import space.kscience.controls.spec.DeviceBySpec import space.kscience.controls.spec.DeviceBySpec
import space.kscience.controls.spec.DeviceSpec import space.kscience.controls.spec.DeviceSpec
@ -11,19 +12,19 @@ import space.kscience.dataforge.names.NameToken
/** /**
* A variant of [DeviceBySpec] that includes Modbus RTU/TCP/UDP client * A variant of [DeviceBySpec] that includes Modbus RTU/TCP/UDP client
*/ */
public open class ModbusDeviceBySpec( public open class ModbusDeviceBySpec<D: Device>(
context: Context, context: Context,
spec: DeviceSpec<ModbusDeviceBySpec>, spec: DeviceSpec<D>,
override val clientId: Int, override val clientId: Int,
override val master: AbstractModbusMaster, override val master: AbstractModbusMaster,
meta: Meta = Meta.EMPTY, meta: Meta = Meta.EMPTY,
) : ModbusDevice, DeviceBySpec<ModbusDeviceBySpec>(spec, context, meta) ) : ModbusDevice, DeviceBySpec<D>(spec, context, meta)
public class ModbusHub( public class ModbusHub(
public val context: Context, public val context: Context,
public val masterBuilder: () -> AbstractModbusMaster, public val masterBuilder: () -> AbstractModbusMaster,
public val specs: Map<NameToken, Pair<Int, DeviceSpec<ModbusDeviceBySpec>>>, public val specs: Map<NameToken, Pair<Int, DeviceSpec<*>>>,
) : DeviceHub, AutoCloseable { ) : DeviceHub, AutoCloseable {
public val master: AbstractModbusMaster by lazy(masterBuilder) public val master: AbstractModbusMaster by lazy(masterBuilder)

View File

@ -12,7 +12,6 @@ dependencies {
api("org.eclipse.milo:sdk-client:$miloVersion") api("org.eclipse.milo:sdk-client:$miloVersion")
api("org.eclipse.milo:bsd-parser:$miloVersion") api("org.eclipse.milo:bsd-parser:$miloVersion")
api("org.eclipse.milo:sdk-server:$miloVersion") api("org.eclipse.milo:sdk-server:$miloVersion")
testImplementation(spclibs.kotlinx.coroutines.test) testImplementation(spclibs.kotlinx.coroutines.test)

View File

@ -18,7 +18,7 @@ import kotlin.reflect.KProperty
/** /**
* An OPC-UA device backed by Eclipse Milo client * An OPC-UA device backed by Eclipse Milo client
*/ */
public interface MiloDevice : Device { public interface OpcUaDevice : Device {
/** /**
* The OPC-UA client initialized on first use * The OPC-UA client initialized on first use
*/ */
@ -29,7 +29,7 @@ public interface MiloDevice : Device {
* Read OPC-UA value with timestamp * Read OPC-UA value with timestamp
* @param T the type of property to read. The value is coerced to it. * @param T the type of property to read. The value is coerced to it.
*/ */
public suspend inline fun <reified T: Any> MiloDevice.readOpcWithTime( public suspend inline fun <reified T: Any> OpcUaDevice.readOpcWithTime(
nodeId: NodeId, nodeId: NodeId,
converter: MetaConverter<T>, converter: MetaConverter<T>,
magAge: Double = 500.0 magAge: Double = 500.0
@ -50,7 +50,7 @@ public suspend inline fun <reified T: Any> MiloDevice.readOpcWithTime(
/** /**
* Read and coerce value from OPC-UA * Read and coerce value from OPC-UA
*/ */
public suspend inline fun <reified T> MiloDevice.readOpc( public suspend inline fun <reified T> OpcUaDevice.readOpc(
nodeId: NodeId, nodeId: NodeId,
converter: MetaConverter<T>, converter: MetaConverter<T>,
magAge: Double = 500.0 magAge: Double = 500.0
@ -72,7 +72,7 @@ public suspend inline fun <reified T> MiloDevice.readOpc(
return converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}") return converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}")
} }
public suspend inline fun <reified T> MiloDevice.writeOpc( public suspend inline fun <reified T> OpcUaDevice.writeOpc(
nodeId: NodeId, nodeId: NodeId,
converter: MetaConverter<T>, converter: MetaConverter<T>,
value: T value: T
@ -85,7 +85,7 @@ public suspend inline fun <reified T> MiloDevice.writeOpc(
/** /**
* A device-bound OPC-UA property. Does not trigger device properties change. * A device-bound OPC-UA property. Does not trigger device properties change.
*/ */
public inline fun <reified T> MiloDevice.opc( public inline fun <reified T> OpcUaDevice.opc(
nodeId: NodeId, nodeId: NodeId,
converter: MetaConverter<T>, converter: MetaConverter<T>,
magAge: Double = 500.0 magAge: Double = 500.0
@ -104,7 +104,7 @@ public inline fun <reified T> MiloDevice.opc(
/** /**
* Register a mutable OPC-UA based [Double] property in a device spec * Register a mutable OPC-UA based [Double] property in a device spec
*/ */
public fun MiloDevice.opcDouble( public fun OpcUaDevice.opcDouble(
nodeId: NodeId, nodeId: NodeId,
magAge: Double = 1.0 magAge: Double = 1.0
): ReadWriteProperty<Any?, Double> = opc<Double>(nodeId, MetaConverter.double, magAge) ): ReadWriteProperty<Any?, Double> = opc<Double>(nodeId, MetaConverter.double, magAge)
@ -112,7 +112,7 @@ public fun MiloDevice.opcDouble(
/** /**
* Register a mutable OPC-UA based [Int] property in a device spec * Register a mutable OPC-UA based [Int] property in a device spec
*/ */
public fun MiloDevice.opcInt( public fun OpcUaDevice.opcInt(
nodeId: NodeId, nodeId: NodeId,
magAge: Double = 1.0 magAge: Double = 1.0
): ReadWriteProperty<Any?, Int> = opc(nodeId, MetaConverter.int, magAge) ): ReadWriteProperty<Any?, Int> = opc(nodeId, MetaConverter.int, magAge)
@ -120,7 +120,7 @@ public fun MiloDevice.opcInt(
/** /**
* Register a mutable OPC-UA based [String] property in a device spec * Register a mutable OPC-UA based [String] property in a device spec
*/ */
public fun MiloDevice.opcString( public fun OpcUaDevice.opcString(
nodeId: NodeId, nodeId: NodeId,
magAge: Double = 1.0 magAge: Double = 1.0
): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge) ): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge)

View File

@ -4,6 +4,7 @@ import org.eclipse.milo.opcua.sdk.client.OpcUaClient
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy
import space.kscience.controls.api.Device
import space.kscience.controls.spec.DeviceBySpec import space.kscience.controls.spec.DeviceBySpec
import space.kscience.controls.spec.DeviceSpec import space.kscience.controls.spec.DeviceSpec
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
@ -40,14 +41,14 @@ public class MiloConfiguration : Scheme() {
/** /**
* A variant of [DeviceBySpec] that includes OPC-UA client * A variant of [DeviceBySpec] that includes OPC-UA client
*/ */
public open class MiloDeviceBySpec<D : MiloDeviceBySpec<D>>( public open class OpcUaDeviceBySpec<D : Device>(
spec: DeviceSpec<D>, spec: DeviceSpec<D>,
config: MiloConfiguration, config: MiloConfiguration,
context: Context = Global, context: Context = Global,
) : MiloDevice, DeviceBySpec<D>(spec, context, config.meta) { ) : OpcUaDevice, DeviceBySpec<D>(spec, context, config.meta) {
override val client: OpcUaClient by lazy { override val client: OpcUaClient by lazy {
context.createMiloClient( context.createOpcUaClient(
config.endpointUrl, config.endpointUrl,
securityPolicy = config.securityPolicy, securityPolicy = config.securityPolicy,
identityProvider = config.username?.let { identityProvider = config.username?.let {

View File

@ -18,10 +18,10 @@ import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.util.* import java.util.*
public fun <T:Any> T?.toOptional(): Optional<T> = if(this == null) Optional.empty() else Optional.of(this) internal fun <T:Any> T?.toOptional(): Optional<T> = if(this == null) Optional.empty() else Optional.of(this)
internal fun Context.createMiloClient( internal fun Context.createOpcUaClient(
endpointUrl: String, //"opc.tcp://localhost:12686/milo" endpointUrl: String, //"opc.tcp://localhost:12686/milo"
securityPolicy: SecurityPolicy = SecurityPolicy.Basic256Sha256, securityPolicy: SecurityPolicy = SecurityPolicy.Basic256Sha256,
identityProvider: IdentityProvider = AnonymousProvider(), identityProvider: IdentityProvider = AnonymousProvider(),

View File

@ -6,27 +6,31 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import space.kscience.controls.spec.DeviceSpec import space.kscience.controls.spec.DeviceSpec
import space.kscience.controls.spec.doubleProperty import space.kscience.controls.spec.doubleProperty
import space.kscience.controls.spec.read
import space.kscience.dataforge.meta.transformations.MetaConverter import space.kscience.dataforge.meta.transformations.MetaConverter
class OpcUaClientTest { class OpcUaClientTest {
class DemoMiloDevice(config: MiloConfiguration) : MiloDeviceBySpec<DemoMiloDevice>(DemoMiloDevice, config) { class DemoOpcUaDevice(config: MiloConfiguration) : OpcUaDeviceBySpec<DemoOpcUaDevice>(DemoOpcUaDevice, config) {
//val randomDouble by opcDouble(NodeId(2, "Dynamic/RandomDouble")) //val randomDouble by opcDouble(NodeId(2, "Dynamic/RandomDouble"))
suspend fun readRandomDouble() = readOpc(NodeId(2, "Dynamic/RandomDouble"), MetaConverter.double) suspend fun readRandomDouble() = readOpc(NodeId(2, "Dynamic/RandomDouble"), MetaConverter.double)
companion object : DeviceSpec<DemoMiloDevice>() { companion object : DeviceSpec<DemoOpcUaDevice>() {
fun build(): DemoMiloDevice { /**
* Build a device. This is not a part of the specification
*/
fun build(): DemoOpcUaDevice {
val config = MiloConfiguration { val config = MiloConfiguration {
endpointUrl = "opc.tcp://milo.digitalpetri.com:62541/milo" endpointUrl = "opc.tcp://milo.digitalpetri.com:62541/milo"
} }
return DemoMiloDevice(config) return DemoOpcUaDevice(config)
} }
inline fun <R> use(block: DemoMiloDevice.() -> R): R = build().use(block) inline fun <R> use(block: (DemoOpcUaDevice) -> R): R = build().use(block)
val randomDouble by doubleProperty(read = DemoMiloDevice::readRandomDouble) val randomDouble by doubleProperty(read = DemoOpcUaDevice::readRandomDouble)
} }
@ -36,7 +40,9 @@ class OpcUaClientTest {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@Test @Test
fun testReadDouble() = runTest { fun testReadDouble() = runTest {
println(DemoMiloDevice.use { DemoMiloDevice.randomDouble.read() }) DemoOpcUaDevice.use{
println(it.read(DemoOpcUaDevice.randomDouble))
}
} }
} }

View File

@ -13,11 +13,11 @@ import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import space.kscience.controls.api.DeviceMessage import space.kscience.controls.api.DeviceMessage
import space.kscience.controls.storage.DeviceMessageStorage import space.kscience.controls.storage.DeviceMessageStorage
import space.kscience.controls.storage.workDirectory
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Factory import space.kscience.dataforge.context.Factory
import space.kscience.dataforge.context.request import space.kscience.dataforge.context.request
import space.kscience.dataforge.io.IOPlugin import space.kscience.dataforge.io.IOPlugin
import space.kscience.dataforge.io.workDirectory
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string

View File

@ -1,29 +0,0 @@
package space.kscience.controls.storage
import space.kscience.dataforge.context.ContextBuilder
import space.kscience.dataforge.io.IOPlugin
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.meta.string
import java.nio.file.Path
import kotlin.io.path.Path
public val IOPlugin.workDirectory: Path
get() {
val workDirectoryPath = meta[IOPlugin.WORK_DIRECTORY_KEY].string
?: context.properties[IOPlugin.WORK_DIRECTORY_KEY].string
?: ".dataforge"
return Path(workDirectoryPath)
}
public fun ContextBuilder.workDirectory(path: String) {
properties {
set(IOPlugin.WORK_DIRECTORY_KEY, path)
}
}
public fun ContextBuilder.workDirectory(path: Path){
workDirectory(path.toAbsolutePath().toString())
}

View File

@ -17,6 +17,7 @@ import space.kscience.controls.manager.install
import space.kscience.controls.opcua.server.OpcUaServer import space.kscience.controls.opcua.server.OpcUaServer
import space.kscience.controls.opcua.server.endpoint import space.kscience.controls.opcua.server.endpoint
import space.kscience.controls.opcua.server.serveDevices import space.kscience.controls.opcua.server.serveDevices
import space.kscience.controls.spec.write
import space.kscience.dataforge.context.* import space.kscience.dataforge.context.*
import space.kscience.magix.api.MagixEndpoint import space.kscience.magix.api.MagixEndpoint
import space.kscience.magix.rsocket.rSocketWithTcp import space.kscience.magix.rsocket.rSocketWithTcp
@ -125,9 +126,9 @@ class DemoControllerView : View(title = " Demo controller remote") {
action { action {
controller.device?.run { controller.device?.run {
launch { launch {
timeScale.write(timeScaleSlider.value) write(timeScale, timeScaleSlider.value)
sinScale.write(xScaleSlider.value) write(sinScale, xScaleSlider.value)
cosScale.write(yScaleSlider.value) write(cosScale, yScaleSlider.value)
} }
} }
} }

View File

@ -62,21 +62,21 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<DemoDevice>(DemoDe
override suspend fun DemoDevice.onOpen() { override suspend fun DemoDevice.onOpen() {
launch { launch {
sinScale.read() read(sinScale)
cosScale.read() read(cosScale)
timeScale.read() read(timeScale)
} }
doRecurring(50.milliseconds) { doRecurring(50.milliseconds) {
sin.read() read(sin)
cos.read() read(cos)
coordinates.read() read(coordinates)
} }
} }
val resetScale by action(MetaConverter.meta, MetaConverter.meta) { val resetScale by action(MetaConverter.meta, MetaConverter.meta) {
timeScale.write(5000.0) write(timeScale, 5000.0)
sinScale.write(1.0) write(sinScale, 1.0)
cosScale.write(1.0) write(cosScale, 1.0)
null null
} }

View File

@ -3,6 +3,7 @@ package space.kscience.controls.demo.car
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import space.kscience.controls.api.PropertyChangedMessage import space.kscience.controls.api.PropertyChangedMessage
import space.kscience.controls.client.controlsMagixFormat import space.kscience.controls.client.controlsMagixFormat
import space.kscience.controls.spec.write
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Factory import space.kscience.dataforge.context.Factory
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
@ -21,7 +22,7 @@ class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta)
(payload as? PropertyChangedMessage)?.let { message -> (payload as? PropertyChangedMessage)?.let { message ->
if (message.sourceDevice == Name.parse("virtual-car")) { if (message.sourceDevice == Name.parse("virtual-car")) {
when (message.property) { when (message.property) {
"acceleration" -> IVirtualCar.acceleration.write(Vector2D.metaToObject(message.value)) "acceleration" -> write(IVirtualCar.acceleration, Vector2D.metaToObject(message.value))
} }
} }
} }

View File

@ -8,6 +8,7 @@ import kotlinx.datetime.Clock
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import space.kscience.controls.spec.DeviceBySpec import space.kscience.controls.spec.DeviceBySpec
import space.kscience.controls.spec.doRecurring import space.kscience.controls.spec.doRecurring
import space.kscience.controls.spec.read
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Factory import space.kscience.dataforge.context.Factory
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
@ -78,9 +79,9 @@ open class VirtualCar(context: Context, meta: Meta) : DeviceBySpec<VirtualCar>(I
//TODO apply friction. One can introduce rotation of the cabin and different friction coefficients along the axis //TODO apply friction. One can introduce rotation of the cabin and different friction coefficients along the axis
launch { launch {
//update logical states //update logical states
IVirtualCar.location.read() read(IVirtualCar.location)
IVirtualCar.speed.read() read(IVirtualCar.speed)
IVirtualCar.acceleration.read() read(IVirtualCar.acceleration)
} }
} }

View File

@ -12,6 +12,7 @@ import space.kscience.controls.client.connectToMagix
import space.kscience.controls.demo.car.IVirtualCar.Companion.acceleration import space.kscience.controls.demo.car.IVirtualCar.Companion.acceleration
import space.kscience.controls.manager.DeviceManager import space.kscience.controls.manager.DeviceManager
import space.kscience.controls.manager.install import space.kscience.controls.manager.install
import space.kscience.controls.spec.write
import space.kscience.controls.storage.storeMessages import space.kscience.controls.storage.storeMessages
import space.kscience.controls.xodus.XodusDeviceMessageStorage import space.kscience.controls.xodus.XodusDeviceMessageStorage
import space.kscience.dataforge.context.* import space.kscience.dataforge.context.*
@ -113,7 +114,8 @@ class VirtualCarControllerView : View(title = " Virtual car controller remote")
action { action {
controller.virtualCar?.run { controller.virtualCar?.run {
launch { launch {
acceleration.write( write(
acceleration,
Vector2D( Vector2D(
accelerationXProperty.get(), accelerationXProperty.get(),
accelerationYProperty.get() accelerationYProperty.get()

View File

@ -45,7 +45,7 @@ class MksPdr900Device(context: Context, meta: Meta) : DeviceBySpec<MksPdr900Devi
public suspend fun writePowerOn(powerOnValue: Boolean) { public suspend fun writePowerOn(powerOnValue: Boolean) {
error.invalidate() invalidate(error)
if (powerOnValue) { if (powerOnValue) {
val ans = talk("FP!ON") val ans = talk("FP!ON")
if (ans == "ON") { if (ans == "ON") {
@ -65,7 +65,7 @@ class MksPdr900Device(context: Context, meta: Meta) : DeviceBySpec<MksPdr900Devi
public suspend fun readChannelData(channel: Int): Double? { public suspend fun readChannelData(channel: Int): Double? {
val answer: String? = talk("PR$channel?") val answer: String? = talk("PR$channel?")
error.invalidate() invalidate(error)
return if (answer.isNullOrEmpty()) { return if (answer.isNullOrEmpty()) {
// updateState(PortSensor.CONNECTED_STATE, false) // updateState(PortSensor.CONNECTED_STATE, false)
updateLogical(error, "No connection") updateLogical(error, "No connection")
@ -94,7 +94,7 @@ class MksPdr900Device(context: Context, meta: Meta) : DeviceBySpec<MksPdr900Devi
val channel by logicalProperty(MetaConverter.int) val channel by logicalProperty(MetaConverter.int)
val value by doubleProperty(read = { val value by doubleProperty(read = {
readChannelData(channel.get() ?: DEFAULT_CHANNEL) readChannelData(get(channel) ?: DEFAULT_CHANNEL)
}) })
val error by logicalProperty(MetaConverter.string) val error by logicalProperty(MetaConverter.string)

View File

@ -16,6 +16,7 @@ import ru.mipt.npm.devices.pimotionmaster.PiMotionMasterDevice.Axis.Companion.mi
import ru.mipt.npm.devices.pimotionmaster.PiMotionMasterDevice.Axis.Companion.position import ru.mipt.npm.devices.pimotionmaster.PiMotionMasterDevice.Axis.Companion.position
import space.kscience.controls.manager.DeviceManager import space.kscience.controls.manager.DeviceManager
import space.kscience.controls.manager.installing import space.kscience.controls.manager.installing
import space.kscience.controls.spec.read
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request import space.kscience.dataforge.context.request
import tornadofx.* import tornadofx.*
@ -44,10 +45,10 @@ fun VBox.piMotionMasterAxis(
label(axisName) label(axisName)
coroutineScope.launch { coroutineScope.launch {
with(axis) { with(axis) {
val min: Double = minPosition.read() val min: Double = read(minPosition)
val max: Double = maxPosition.read() val max: Double = read(maxPosition)
val positionProperty = fxProperty(position) val positionProperty = fxProperty(position)
val startPosition = position.read() val startPosition = read(position)
runLater { runLater {
vbox { vbox {
hgrow = Priority.ALWAYS hgrow = Priority.ALWAYS

View File

@ -39,7 +39,7 @@ class PiMotionMasterDevice(
fun disconnect() { fun disconnect() {
runBlocking { runBlocking {
disconnect.invoke() execute(disconnect)
} }
} }
@ -60,7 +60,7 @@ class PiMotionMasterDevice(
fun connect(host: String, port: Int) { fun connect(host: String, port: Int) {
runBlocking { runBlocking {
connect(Meta { execute(connect, Meta {
"host" put host "host" put host
"port" put port "port" put port
}) })
@ -176,7 +176,7 @@ class PiMotionMasterDevice(
// connector.open() // connector.open()
//Initialize axes //Initialize axes
if (portSpec != null) { if (portSpec != null) {
val idn = identity.read() val idn = read(identity)
failIfError { "Can't connect to $portSpec. Error code: $it" } failIfError { "Can't connect to $portSpec. Error code: $it" }
logger.info { "Connected to $idn on $portSpec" } logger.info { "Connected to $idn on $portSpec" }
val ids = request("SAI?").map { it.trim() } val ids = request("SAI?").map { it.trim() }
@ -185,7 +185,7 @@ class PiMotionMasterDevice(
axes = ids.associateWith { Axis(this, it) } axes = ids.associateWith { Axis(this, it) }
} }
Meta(ids.map { it.asValue() }.asValue()) Meta(ids.map { it.asValue() }.asValue())
initialize() execute(initialize)
failIfError() failIfError()
} }
null null
@ -195,7 +195,7 @@ class PiMotionMasterDevice(
info = "Disconnect the program from the device if it is connected" info = "Disconnect the program from the device if it is connected"
}) { }) {
port?.let{ port?.let{
stop() execute(stop)
it.close() it.close()
} }
port = null port = null
@ -237,7 +237,7 @@ class PiMotionMasterDevice(
} }
suspend fun move(target: Double) { suspend fun move(target: Double) {
move(target.asMeta()) execute(move, target.asMeta())
} }
companion object : DeviceSpec<Axis>() { companion object : DeviceSpec<Axis>() {
@ -336,15 +336,15 @@ class PiMotionMasterDevice(
val move by metaAction { val move by metaAction {
val target = it.double ?: it?.get("target").double ?: error("Unacceptable target value $it") val target = it.double ?: it?.get("target").double ?: error("Unacceptable target value $it")
closedLoop.write(true) write(closedLoop, true)
//optionally set velocity //optionally set velocity
it?.get("velocity").double?.let { v -> it?.get("velocity").double?.let { v ->
velocity.write(v) write(velocity, v)
} }
targetPosition.write(target) write(targetPosition, target)
//read `onTarget` and `position` properties in a cycle until movement is complete //read `onTarget` and `position` properties in a cycle until movement is complete
while (!onTarget.read()) { while (!read(onTarget)) {
position.read() read(position)
delay(200) delay(200)
} }
null null

View File

@ -59,7 +59,7 @@ fun <D : Device, T : Any> D.fxProperty(spec: WritableDevicePropertySpec<D, T>):
onChange { newValue -> onChange { newValue ->
if (newValue != null) { if (newValue != null) {
write(spec, newValue) set(spec, newValue)
} }
} }
} }