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") {
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\"",
)
}
}
}

View File

@ -36,7 +36,3 @@ ksciencePublish {
}
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")
apiValidation {
validationDisabled = true
}

View File

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

View File

@ -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,

View File

@ -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

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
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)

View File

@ -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)

View File

@ -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)

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.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 {

View File

@ -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(),

View File

@ -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))
}
}
}

View File

@ -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

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.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)
}
}
}

View File

@ -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
}

View File

@ -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))
}
}
}

View File

@ -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)
}
}

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.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()

View File

@ -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)

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 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

View File

@ -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

View File

@ -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)
}
}
}