Clean up property access syntax
This commit is contained in:
parent
86273101b6
commit
691b2ae67a
46
.space.kts
46
.space.kts
@ -1,3 +1,45 @@
|
||||
job("Build and run tests") {
|
||||
gradle("gradle:8.1.1-jdk11", "build")
|
||||
import kotlin.io.path.readText
|
||||
|
||||
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\"",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,3 @@ ksciencePublish {
|
||||
}
|
||||
|
||||
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")
|
||||
|
||||
apiValidation {
|
||||
validationDisabled = true
|
||||
}
|
@ -13,6 +13,7 @@ kscience {
|
||||
useSerialization{
|
||||
json()
|
||||
}
|
||||
useContextReceivers()
|
||||
dependencies {
|
||||
api("space.kscience:dataforge-io:$dataforgeVersion")
|
||||
api(spclibs.kotlinx.datetime)
|
||||
|
@ -4,7 +4,6 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import space.kscience.controls.api.*
|
||||
@ -18,7 +17,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
* A base abstractions for [Device], introducing specifications for properties
|
||||
*/
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
public abstract class DeviceBase<D : Device>(
|
||||
override val context: Context = Global,
|
||||
override val meta: Meta = Meta.EMPTY,
|
||||
) : Device {
|
||||
@ -75,7 +74,7 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
/**
|
||||
* 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))
|
||||
}
|
||||
|
||||
@ -99,7 +98,7 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
}
|
||||
|
||||
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 {
|
||||
invalidate(propertyName)
|
||||
it.writeMeta(self, value)
|
||||
@ -111,49 +110,13 @@ public abstract class DeviceBase<D : DeviceBase<D>>(
|
||||
override suspend fun execute(action: String, argument: Meta?): Meta? =
|
||||
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
|
||||
* @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>,
|
||||
context: Context = Global,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
|
@ -91,29 +91,55 @@ public suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta(
|
||||
return res?.let { outputConverter.objectToMeta(res) }
|
||||
}
|
||||
|
||||
|
||||
public suspend fun <D : DeviceBase<D>, T : Any> D.read(
|
||||
propertySpec: DevicePropertySpec<D, T>,
|
||||
): T = propertySpec.read()
|
||||
|
||||
public suspend fun <D : Device, T : Any> D.read(
|
||||
propertySpec: DevicePropertySpec<D, T>,
|
||||
): T = propertySpec.converter.metaToObject(readProperty(propertySpec.name))
|
||||
?: error("Property meta converter returned null")
|
||||
|
||||
public fun <D : Device, T> D.write(
|
||||
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||
value: T,
|
||||
): Job = launch {
|
||||
writeProperty(propertySpec.name, propertySpec.converter.objectToMeta(value))
|
||||
/**
|
||||
* Read typed value and update/push event if needed.
|
||||
* Return null if property read is not successful or property is undefined.
|
||||
*/
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public suspend fun <T, D : Device> D.readOrNull(propertySpec: DevicePropertySpec<D, T>): T? {
|
||||
val res = propertySpec.read(this) ?: return null
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(this as? DeviceBase<D>)?.updateLogical(propertySpec, res)
|
||||
return res
|
||||
}
|
||||
|
||||
public fun <D : DeviceBase<D>, T> D.write(
|
||||
propertySpec: WritableDevicePropertySpec<D, T>,
|
||||
value: T,
|
||||
): Job = launch {
|
||||
propertySpec.write(value)
|
||||
public suspend fun <T, D : Device> D.read(propertySpec: DevicePropertySpec<D, T>): T =
|
||||
readOrNull(propertySpec) ?: error("Failed to read property ${propertySpec.name} state")
|
||||
|
||||
public operator fun <T, D : Device> D.get(propertySpec: DevicePropertySpec<D, T>): T? =
|
||||
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
|
||||
|
@ -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) }
|
@ -1,6 +1,7 @@
|
||||
package space.kscience.controls.modbus
|
||||
|
||||
import com.ghgande.j2mod.modbus.facade.AbstractModbusMaster
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.api.DeviceHub
|
||||
import space.kscience.controls.spec.DeviceBySpec
|
||||
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
|
||||
*/
|
||||
public open class ModbusDeviceBySpec(
|
||||
public open class ModbusDeviceBySpec<D: Device>(
|
||||
context: Context,
|
||||
spec: DeviceSpec<ModbusDeviceBySpec>,
|
||||
spec: DeviceSpec<D>,
|
||||
override val clientId: Int,
|
||||
override val master: AbstractModbusMaster,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) : ModbusDevice, DeviceBySpec<ModbusDeviceBySpec>(spec, context, meta)
|
||||
) : ModbusDevice, DeviceBySpec<D>(spec, context, meta)
|
||||
|
||||
|
||||
public class ModbusHub(
|
||||
public val context: Context,
|
||||
public val masterBuilder: () -> AbstractModbusMaster,
|
||||
public val specs: Map<NameToken, Pair<Int, DeviceSpec<ModbusDeviceBySpec>>>,
|
||||
public val specs: Map<NameToken, Pair<Int, DeviceSpec<*>>>,
|
||||
) : DeviceHub, AutoCloseable {
|
||||
|
||||
public val master: AbstractModbusMaster by lazy(masterBuilder)
|
||||
|
@ -12,7 +12,6 @@ dependencies {
|
||||
|
||||
api("org.eclipse.milo:sdk-client:$miloVersion")
|
||||
api("org.eclipse.milo:bsd-parser:$miloVersion")
|
||||
|
||||
api("org.eclipse.milo:sdk-server:$miloVersion")
|
||||
|
||||
testImplementation(spclibs.kotlinx.coroutines.test)
|
||||
|
@ -18,7 +18,7 @@ import kotlin.reflect.KProperty
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@ -29,7 +29,7 @@ public interface MiloDevice : Device {
|
||||
* Read OPC-UA value with timestamp
|
||||
* @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,
|
||||
converter: MetaConverter<T>,
|
||||
magAge: Double = 500.0
|
||||
@ -50,7 +50,7 @@ public suspend inline fun <reified T: Any> MiloDevice.readOpcWithTime(
|
||||
/**
|
||||
* 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,
|
||||
converter: MetaConverter<T>,
|
||||
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}")
|
||||
}
|
||||
|
||||
public suspend inline fun <reified T> MiloDevice.writeOpc(
|
||||
public suspend inline fun <reified T> OpcUaDevice.writeOpc(
|
||||
nodeId: NodeId,
|
||||
converter: MetaConverter<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.
|
||||
*/
|
||||
public inline fun <reified T> MiloDevice.opc(
|
||||
public inline fun <reified T> OpcUaDevice.opc(
|
||||
nodeId: NodeId,
|
||||
converter: MetaConverter<T>,
|
||||
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
|
||||
*/
|
||||
public fun MiloDevice.opcDouble(
|
||||
public fun OpcUaDevice.opcDouble(
|
||||
nodeId: NodeId,
|
||||
magAge: Double = 1.0
|
||||
): 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
|
||||
*/
|
||||
public fun MiloDevice.opcInt(
|
||||
public fun OpcUaDevice.opcInt(
|
||||
nodeId: NodeId,
|
||||
magAge: Double = 1.0
|
||||
): 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
|
||||
*/
|
||||
public fun MiloDevice.opcString(
|
||||
public fun OpcUaDevice.opcString(
|
||||
nodeId: NodeId,
|
||||
magAge: Double = 1.0
|
||||
): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge)
|
@ -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.UsernameProvider
|
||||
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.DeviceSpec
|
||||
import space.kscience.dataforge.context.Context
|
||||
@ -40,14 +41,14 @@ public class MiloConfiguration : Scheme() {
|
||||
/**
|
||||
* 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>,
|
||||
config: MiloConfiguration,
|
||||
context: Context = Global,
|
||||
) : MiloDevice, DeviceBySpec<D>(spec, context, config.meta) {
|
||||
) : OpcUaDevice, DeviceBySpec<D>(spec, context, config.meta) {
|
||||
|
||||
override val client: OpcUaClient by lazy {
|
||||
context.createMiloClient(
|
||||
context.createOpcUaClient(
|
||||
config.endpointUrl,
|
||||
securityPolicy = config.securityPolicy,
|
||||
identityProvider = config.username?.let {
|
@ -18,10 +18,10 @@ import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
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"
|
||||
securityPolicy: SecurityPolicy = SecurityPolicy.Basic256Sha256,
|
||||
identityProvider: IdentityProvider = AnonymousProvider(),
|
||||
|
@ -6,27 +6,31 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId
|
||||
import org.junit.jupiter.api.Test
|
||||
import space.kscience.controls.spec.DeviceSpec
|
||||
import space.kscience.controls.spec.doubleProperty
|
||||
import space.kscience.controls.spec.read
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
|
||||
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"))
|
||||
|
||||
suspend fun readRandomDouble() = readOpc(NodeId(2, "Dynamic/RandomDouble"), MetaConverter.double)
|
||||
|
||||
|
||||
companion object : DeviceSpec<DemoMiloDevice>() {
|
||||
fun build(): DemoMiloDevice {
|
||||
companion object : DeviceSpec<DemoOpcUaDevice>() {
|
||||
/**
|
||||
* Build a device. This is not a part of the specification
|
||||
*/
|
||||
fun build(): DemoOpcUaDevice {
|
||||
val config = MiloConfiguration {
|
||||
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)
|
||||
@Test
|
||||
fun testReadDouble() = runTest {
|
||||
println(DemoMiloDevice.use { DemoMiloDevice.randomDouble.read() })
|
||||
DemoOpcUaDevice.use{
|
||||
println(it.read(DemoOpcUaDevice.randomDouble))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -13,11 +13,11 @@ import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import space.kscience.controls.api.DeviceMessage
|
||||
import space.kscience.controls.storage.DeviceMessageStorage
|
||||
import space.kscience.controls.storage.workDirectory
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.Factory
|
||||
import space.kscience.dataforge.context.request
|
||||
import space.kscience.dataforge.io.IOPlugin
|
||||
import space.kscience.dataforge.io.workDirectory
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
|
@ -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())
|
||||
}
|
@ -17,6 +17,7 @@ import space.kscience.controls.manager.install
|
||||
import space.kscience.controls.opcua.server.OpcUaServer
|
||||
import space.kscience.controls.opcua.server.endpoint
|
||||
import space.kscience.controls.opcua.server.serveDevices
|
||||
import space.kscience.controls.spec.write
|
||||
import space.kscience.dataforge.context.*
|
||||
import space.kscience.magix.api.MagixEndpoint
|
||||
import space.kscience.magix.rsocket.rSocketWithTcp
|
||||
@ -125,9 +126,9 @@ class DemoControllerView : View(title = " Demo controller remote") {
|
||||
action {
|
||||
controller.device?.run {
|
||||
launch {
|
||||
timeScale.write(timeScaleSlider.value)
|
||||
sinScale.write(xScaleSlider.value)
|
||||
cosScale.write(yScaleSlider.value)
|
||||
write(timeScale, timeScaleSlider.value)
|
||||
write(sinScale, xScaleSlider.value)
|
||||
write(cosScale, yScaleSlider.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,21 +62,21 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<DemoDevice>(DemoDe
|
||||
|
||||
override suspend fun DemoDevice.onOpen() {
|
||||
launch {
|
||||
sinScale.read()
|
||||
cosScale.read()
|
||||
timeScale.read()
|
||||
read(sinScale)
|
||||
read(cosScale)
|
||||
read(timeScale)
|
||||
}
|
||||
doRecurring(50.milliseconds) {
|
||||
sin.read()
|
||||
cos.read()
|
||||
coordinates.read()
|
||||
read(sin)
|
||||
read(cos)
|
||||
read(coordinates)
|
||||
}
|
||||
}
|
||||
|
||||
val resetScale by action(MetaConverter.meta, MetaConverter.meta) {
|
||||
timeScale.write(5000.0)
|
||||
sinScale.write(1.0)
|
||||
cosScale.write(1.0)
|
||||
write(timeScale, 5000.0)
|
||||
write(sinScale, 1.0)
|
||||
write(cosScale, 1.0)
|
||||
null
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package space.kscience.controls.demo.car
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.controls.api.PropertyChangedMessage
|
||||
import space.kscience.controls.client.controlsMagixFormat
|
||||
import space.kscience.controls.spec.write
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.Factory
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
@ -21,7 +22,7 @@ class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta)
|
||||
(payload as? PropertyChangedMessage)?.let { message ->
|
||||
if (message.sourceDevice == Name.parse("virtual-car")) {
|
||||
when (message.property) {
|
||||
"acceleration" -> IVirtualCar.acceleration.write(Vector2D.metaToObject(message.value))
|
||||
"acceleration" -> write(IVirtualCar.acceleration, Vector2D.metaToObject(message.value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import space.kscience.controls.spec.DeviceBySpec
|
||||
import space.kscience.controls.spec.doRecurring
|
||||
import space.kscience.controls.spec.read
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.Factory
|
||||
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
|
||||
launch {
|
||||
//update logical states
|
||||
IVirtualCar.location.read()
|
||||
IVirtualCar.speed.read()
|
||||
IVirtualCar.acceleration.read()
|
||||
read(IVirtualCar.location)
|
||||
read(IVirtualCar.speed)
|
||||
read(IVirtualCar.acceleration)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import space.kscience.controls.client.connectToMagix
|
||||
import space.kscience.controls.demo.car.IVirtualCar.Companion.acceleration
|
||||
import space.kscience.controls.manager.DeviceManager
|
||||
import space.kscience.controls.manager.install
|
||||
import space.kscience.controls.spec.write
|
||||
import space.kscience.controls.storage.storeMessages
|
||||
import space.kscience.controls.xodus.XodusDeviceMessageStorage
|
||||
import space.kscience.dataforge.context.*
|
||||
@ -113,7 +114,8 @@ class VirtualCarControllerView : View(title = " Virtual car controller remote")
|
||||
action {
|
||||
controller.virtualCar?.run {
|
||||
launch {
|
||||
acceleration.write(
|
||||
write(
|
||||
acceleration,
|
||||
Vector2D(
|
||||
accelerationXProperty.get(),
|
||||
accelerationYProperty.get()
|
||||
|
@ -45,7 +45,7 @@ class MksPdr900Device(context: Context, meta: Meta) : DeviceBySpec<MksPdr900Devi
|
||||
|
||||
|
||||
public suspend fun writePowerOn(powerOnValue: Boolean) {
|
||||
error.invalidate()
|
||||
invalidate(error)
|
||||
if (powerOnValue) {
|
||||
val ans = talk("FP!ON")
|
||||
if (ans == "ON") {
|
||||
@ -65,7 +65,7 @@ class MksPdr900Device(context: Context, meta: Meta) : DeviceBySpec<MksPdr900Devi
|
||||
|
||||
public suspend fun readChannelData(channel: Int): Double? {
|
||||
val answer: String? = talk("PR$channel?")
|
||||
error.invalidate()
|
||||
invalidate(error)
|
||||
return if (answer.isNullOrEmpty()) {
|
||||
// updateState(PortSensor.CONNECTED_STATE, false)
|
||||
updateLogical(error, "No connection")
|
||||
@ -94,7 +94,7 @@ class MksPdr900Device(context: Context, meta: Meta) : DeviceBySpec<MksPdr900Devi
|
||||
val channel by logicalProperty(MetaConverter.int)
|
||||
|
||||
val value by doubleProperty(read = {
|
||||
readChannelData(channel.get() ?: DEFAULT_CHANNEL)
|
||||
readChannelData(get(channel) ?: DEFAULT_CHANNEL)
|
||||
})
|
||||
|
||||
val error by logicalProperty(MetaConverter.string)
|
||||
|
@ -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 space.kscience.controls.manager.DeviceManager
|
||||
import space.kscience.controls.manager.installing
|
||||
import space.kscience.controls.spec.read
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.request
|
||||
import tornadofx.*
|
||||
@ -44,10 +45,10 @@ fun VBox.piMotionMasterAxis(
|
||||
label(axisName)
|
||||
coroutineScope.launch {
|
||||
with(axis) {
|
||||
val min: Double = minPosition.read()
|
||||
val max: Double = maxPosition.read()
|
||||
val min: Double = read(minPosition)
|
||||
val max: Double = read(maxPosition)
|
||||
val positionProperty = fxProperty(position)
|
||||
val startPosition = position.read()
|
||||
val startPosition = read(position)
|
||||
runLater {
|
||||
vbox {
|
||||
hgrow = Priority.ALWAYS
|
||||
|
@ -39,7 +39,7 @@ class PiMotionMasterDevice(
|
||||
|
||||
fun disconnect() {
|
||||
runBlocking {
|
||||
disconnect.invoke()
|
||||
execute(disconnect)
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ class PiMotionMasterDevice(
|
||||
|
||||
fun connect(host: String, port: Int) {
|
||||
runBlocking {
|
||||
connect(Meta {
|
||||
execute(connect, Meta {
|
||||
"host" put host
|
||||
"port" put port
|
||||
})
|
||||
@ -176,7 +176,7 @@ class PiMotionMasterDevice(
|
||||
// connector.open()
|
||||
//Initialize axes
|
||||
if (portSpec != null) {
|
||||
val idn = identity.read()
|
||||
val idn = read(identity)
|
||||
failIfError { "Can't connect to $portSpec. Error code: $it" }
|
||||
logger.info { "Connected to $idn on $portSpec" }
|
||||
val ids = request("SAI?").map { it.trim() }
|
||||
@ -185,7 +185,7 @@ class PiMotionMasterDevice(
|
||||
axes = ids.associateWith { Axis(this, it) }
|
||||
}
|
||||
Meta(ids.map { it.asValue() }.asValue())
|
||||
initialize()
|
||||
execute(initialize)
|
||||
failIfError()
|
||||
}
|
||||
null
|
||||
@ -195,7 +195,7 @@ class PiMotionMasterDevice(
|
||||
info = "Disconnect the program from the device if it is connected"
|
||||
}) {
|
||||
port?.let{
|
||||
stop()
|
||||
execute(stop)
|
||||
it.close()
|
||||
}
|
||||
port = null
|
||||
@ -237,7 +237,7 @@ class PiMotionMasterDevice(
|
||||
}
|
||||
|
||||
suspend fun move(target: Double) {
|
||||
move(target.asMeta())
|
||||
execute(move, target.asMeta())
|
||||
}
|
||||
|
||||
companion object : DeviceSpec<Axis>() {
|
||||
@ -336,15 +336,15 @@ class PiMotionMasterDevice(
|
||||
|
||||
val move by metaAction {
|
||||
val target = it.double ?: it?.get("target").double ?: error("Unacceptable target value $it")
|
||||
closedLoop.write(true)
|
||||
write(closedLoop, true)
|
||||
//optionally set velocity
|
||||
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
|
||||
while (!onTarget.read()) {
|
||||
position.read()
|
||||
while (!read(onTarget)) {
|
||||
read(position)
|
||||
delay(200)
|
||||
}
|
||||
null
|
||||
|
@ -59,7 +59,7 @@ fun <D : Device, T : Any> D.fxProperty(spec: WritableDevicePropertySpec<D, T>):
|
||||
|
||||
onChange { newValue ->
|
||||
if (newValue != null) {
|
||||
write(spec, newValue)
|
||||
set(spec, newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user