Compare commits
5 Commits
827eb6e4c1
...
5e64b79b77
Author | SHA1 | Date | |
---|---|---|---|
5e64b79b77 | |||
a12cf440e8 | |||
606c2cf5b1 | |||
fb03fcc982 | |||
cf129b6242 |
build.gradle.ktsgradle.properties
controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor
controls-core
build.gradle.kts
src
commonMain/kotlin/space/kscience/controls
api
misc
ports
spec
jvmMain/kotlin/space/kscience/controls/ports
jvmTest/kotlin/space/kscience/controls/ports
controls-modbus/src/main/kotlin/space/kscience/controls/modbus
controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client
controls-vision/src/jsMain/kotlin
demo
all-things/src/main/kotlin/space/kscience/controls/demo
car/src/main/kotlin/space/kscience/controls/demo/car
constructor
mks-pdr900/src/main/kotlin/center/sciprog/devices/mks
magix
@ -5,15 +5,15 @@ plugins {
|
||||
id("space.kscience.gradle.project")
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by extra("0.6.2")
|
||||
val visionforgeVersion by extra("0.3.0-dev-14")
|
||||
val dataforgeVersion: String by extra("0.7.1")
|
||||
val visionforgeVersion by extra("0.3.0-RC")
|
||||
val ktorVersion: String by extra(space.kscience.gradle.KScienceVersions.ktorVersion)
|
||||
val rsocketVersion by extra("0.15.4")
|
||||
val xodusVersion by extra("2.0.1")
|
||||
|
||||
allprojects {
|
||||
group = "space.kscience"
|
||||
version = "0.3.0-dev-2"
|
||||
version = "0.3.0-dev-4"
|
||||
repositories{
|
||||
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public interface MutableDeviceState<T> : DeviceState<T> {
|
||||
public var <T : Any> MutableDeviceState<T>.valueAsMeta: Meta
|
||||
get() = converter.objectToMeta(value)
|
||||
set(arg) {
|
||||
value = converter.metaToObject(arg) ?: error("Conversion for meta $arg to property type with $converter failed")
|
||||
value = converter.metaToObject(arg)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,10 +20,14 @@ kscience {
|
||||
json()
|
||||
}
|
||||
useContextReceivers()
|
||||
dependencies {
|
||||
commonMain {
|
||||
api("space.kscience:dataforge-io:$dataforgeVersion")
|
||||
api(spclibs.kotlinx.datetime)
|
||||
}
|
||||
|
||||
jvmTest{
|
||||
implementation(spclibs.logback.classic)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -11,8 +11,8 @@ import space.kscience.dataforge.context.info
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.misc.DfType
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
|
||||
/**
|
||||
* A lifecycle state of a device
|
||||
@ -46,7 +46,7 @@ public enum class DeviceLifecycleState {
|
||||
* [Device] is a supervisor scope encompassing all operations on a device.
|
||||
* When canceled, cancels all running processes.
|
||||
*/
|
||||
@Type(DEVICE_TARGET)
|
||||
@DfType(DEVICE_TARGET)
|
||||
public interface Device : ContextAware, CoroutineScope {
|
||||
|
||||
/**
|
||||
@ -144,7 +144,7 @@ public suspend fun Device.requestProperty(propertyName: String): Meta = if (this
|
||||
*/
|
||||
public fun CachingDevice.getAllProperties(): Meta = Meta {
|
||||
for (descriptor in propertyDescriptors) {
|
||||
setMeta(Name.parse(descriptor.name), getProperty(descriptor.name))
|
||||
set(descriptor.name.parseAsName(), getProperty(descriptor.name))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package space.kscience.controls.api
|
||||
|
||||
import io.ktor.utils.io.core.Closeable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -9,7 +8,7 @@ import kotlinx.coroutines.launch
|
||||
/**
|
||||
* A generic bidirectional sender/receiver object
|
||||
*/
|
||||
public interface Socket<T> : Closeable {
|
||||
public interface Socket<T> : AutoCloseable {
|
||||
/**
|
||||
* Send an object to the socket
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package space.kscience.controls.misc
|
||||
|
||||
import io.ktor.utils.io.core.Input
|
||||
import io.ktor.utils.io.core.Output
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.io.Sink
|
||||
import kotlinx.io.Source
|
||||
import space.kscience.dataforge.io.IOFormat
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
@ -38,15 +38,16 @@ public data class ValueWithTime<T>(val value: T, val time: Instant) {
|
||||
private class ValueWithTimeIOFormat<T>(val valueFormat: IOFormat<T>) : IOFormat<ValueWithTime<T>> {
|
||||
override val type: KType get() = typeOf<ValueWithTime<T>>()
|
||||
|
||||
override fun readObject(input: Input): ValueWithTime<T> {
|
||||
val timestamp = InstantIOFormat.readObject(input)
|
||||
val value = valueFormat.readObject(input)
|
||||
|
||||
override fun readFrom(source: Source): ValueWithTime<T> {
|
||||
val timestamp = InstantIOFormat.readFrom(source)
|
||||
val value = valueFormat.readFrom(source)
|
||||
return ValueWithTime(value, timestamp)
|
||||
}
|
||||
|
||||
override fun writeObject(output: Output, obj: ValueWithTime<T>) {
|
||||
InstantIOFormat.writeObject(output, obj.time)
|
||||
valueFormat.writeObject(output, obj.value)
|
||||
override fun writeTo(sink: Sink, obj: ValueWithTime<T>) {
|
||||
InstantIOFormat.writeTo(sink, obj.time)
|
||||
valueFormat.writeTo(sink, obj.value)
|
||||
}
|
||||
|
||||
}
|
||||
@ -54,7 +55,10 @@ private class ValueWithTimeIOFormat<T>(val valueFormat: IOFormat<T>) : IOFormat<
|
||||
private class ValueWithTimeMetaConverter<T>(
|
||||
val valueConverter: MetaConverter<T>,
|
||||
) : MetaConverter<ValueWithTime<T>> {
|
||||
override fun metaToObject(
|
||||
|
||||
override val type: KType = typeOf<ValueWithTime<T>>()
|
||||
|
||||
override fun metaToObjectOrNull(
|
||||
meta: Meta,
|
||||
): ValueWithTime<T>? = valueConverter.metaToObject(meta[ValueWithTime.META_VALUE_KEY] ?: Meta.EMPTY)?.let {
|
||||
ValueWithTime(it, meta[ValueWithTime.META_TIME_KEY]?.instant ?: Instant.DISTANT_PAST)
|
||||
|
@ -1,7 +1,9 @@
|
||||
package space.kscience.controls.misc
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.io.Sink
|
||||
import kotlinx.io.Source
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.io.IOFormat
|
||||
import space.kscience.dataforge.io.IOFormatFactory
|
||||
@ -23,14 +25,14 @@ public object InstantIOFormat : IOFormat<Instant>, IOFormatFactory<Instant> {
|
||||
|
||||
override val type: KType get() = typeOf<Instant>()
|
||||
|
||||
override fun writeObject(output: Output, obj: Instant) {
|
||||
output.writeLong(obj.epochSeconds)
|
||||
output.writeInt(obj.nanosecondsOfSecond)
|
||||
override fun writeTo(sink: Sink, obj: Instant) {
|
||||
sink.writeLong(obj.epochSeconds)
|
||||
sink.writeInt(obj.nanosecondsOfSecond)
|
||||
}
|
||||
|
||||
override fun readObject(input: Input): Instant {
|
||||
val seconds = input.readLong()
|
||||
val nanoseconds = input.readInt()
|
||||
override fun readFrom(source: Source): Instant {
|
||||
val seconds = source.readLong()
|
||||
val nanoseconds = source.readInt()
|
||||
return Instant.fromEpochSeconds(seconds, nanoseconds)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import space.kscience.controls.api.Socket
|
||||
import space.kscience.dataforge.context.*
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.misc.DfType
|
||||
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
@ -17,7 +18,7 @@ public interface Port : ContextAware, Socket<ByteArray>
|
||||
/**
|
||||
* A specialized factory for [Port]
|
||||
*/
|
||||
@Type(PortFactory.TYPE)
|
||||
@DfType(PortFactory.TYPE)
|
||||
public interface PortFactory : Factory<Port> {
|
||||
public val type: String
|
||||
|
||||
@ -37,7 +38,7 @@ public abstract class AbstractPort(
|
||||
protected val scope: CoroutineScope = CoroutineScope(coroutineContext + SupervisorJob(coroutineContext[Job]))
|
||||
|
||||
private val outgoing = Channel<ByteArray>(100)
|
||||
private val incoming = Channel<ByteArray>(Channel.CONFLATED)
|
||||
private val incoming = Channel<ByteArray>(100)
|
||||
|
||||
init {
|
||||
scope.coroutineContext[Job]?.invokeOnCompletion {
|
||||
@ -87,7 +88,6 @@ public abstract class AbstractPort(
|
||||
override fun close() {
|
||||
outgoing.close()
|
||||
incoming.close()
|
||||
sendJob.cancel()
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import space.kscience.dataforge.io.Binary
|
||||
|
||||
public fun Binary.readShort(position: Int): Short = read(position) { readShort() }
|
@ -1,12 +1,11 @@
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import io.ktor.utils.io.core.BytePacketBuilder
|
||||
import io.ktor.utils.io.core.readBytes
|
||||
import io.ktor.utils.io.core.reset
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.io.Buffer
|
||||
import kotlinx.io.readByteArray
|
||||
|
||||
/**
|
||||
* Transform byte fragments into complete phrases using given delimiter. Not thread safe.
|
||||
@ -14,7 +13,7 @@ import kotlinx.coroutines.flow.transform
|
||||
public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray): Flow<ByteArray> {
|
||||
require(delimiter.isNotEmpty()) { "Delimiter must not be empty" }
|
||||
|
||||
val output = BytePacketBuilder()
|
||||
val output = Buffer()
|
||||
var matcherPosition = 0
|
||||
|
||||
onCompletion {
|
||||
@ -29,9 +28,8 @@ public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray): Flow<ByteArray>
|
||||
matcherPosition++
|
||||
if (matcherPosition == delimiter.size) {
|
||||
//full match achieved, sending result
|
||||
val bytes = output.build()
|
||||
emit(bytes.readBytes())
|
||||
output.reset()
|
||||
emit(output.readByteArray())
|
||||
output.clear()
|
||||
matcherPosition = 0
|
||||
}
|
||||
} else if (matcherPosition > 0) {
|
||||
|
@ -9,9 +9,14 @@ import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import kotlin.properties.PropertyDelegateProvider
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
public object UnitMetaConverter : MetaConverter<Unit> {
|
||||
override fun metaToObject(meta: Meta): Unit = Unit
|
||||
|
||||
override val type: KType = typeOf<Unit>()
|
||||
|
||||
override fun metaToObjectOrNull(meta: Meta): Unit = Unit
|
||||
|
||||
override fun objectToMeta(obj: Unit): Meta = Meta.EMPTY
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
@ -10,7 +12,9 @@ public fun Double.asMeta(): Meta = Meta(asValue())
|
||||
|
||||
//TODO to be moved to DF
|
||||
public object DurationConverter : MetaConverter<Duration> {
|
||||
override fun metaToObject(meta: Meta): Duration = meta.value?.double?.toDuration(DurationUnit.SECONDS)
|
||||
override val type: KType = typeOf<Duration>()
|
||||
|
||||
override fun metaToObjectOrNull(meta: Meta): Duration = meta.value?.double?.toDuration(DurationUnit.SECONDS)
|
||||
?: run {
|
||||
val unit: DurationUnit = meta["unit"].enum<DurationUnit>() ?: DurationUnit.SECONDS
|
||||
val value = meta[Meta.VALUE_KEY].double ?: error("No value present for Duration")
|
||||
|
@ -68,7 +68,7 @@ public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
||||
MetaConverter.boolean,
|
||||
{
|
||||
metaDescriptor {
|
||||
type(ValueType.BOOLEAN)
|
||||
valueType(ValueType.BOOLEAN)
|
||||
}
|
||||
descriptorBuilder()
|
||||
},
|
||||
@ -80,7 +80,7 @@ private inline fun numberDescriptor(
|
||||
crossinline descriptorBuilder: PropertyDescriptor.() -> Unit = {}
|
||||
): PropertyDescriptor.() -> Unit = {
|
||||
metaDescriptor {
|
||||
type(ValueType.NUMBER)
|
||||
valueType(ValueType.NUMBER)
|
||||
}
|
||||
descriptorBuilder()
|
||||
}
|
||||
@ -115,7 +115,7 @@ public fun <D : Device> DeviceSpec<D>.stringProperty(
|
||||
MetaConverter.string,
|
||||
{
|
||||
metaDescriptor {
|
||||
type(ValueType.STRING)
|
||||
valueType(ValueType.STRING)
|
||||
}
|
||||
descriptorBuilder()
|
||||
},
|
||||
@ -131,7 +131,7 @@ public fun <D : Device> DeviceSpec<D>.metaProperty(
|
||||
MetaConverter.meta,
|
||||
{
|
||||
metaDescriptor {
|
||||
type(ValueType.STRING)
|
||||
valueType(ValueType.STRING)
|
||||
}
|
||||
descriptorBuilder()
|
||||
},
|
||||
@ -151,7 +151,7 @@ public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
||||
MetaConverter.boolean,
|
||||
{
|
||||
metaDescriptor {
|
||||
type(ValueType.BOOLEAN)
|
||||
valueType(ValueType.BOOLEAN)
|
||||
}
|
||||
descriptorBuilder()
|
||||
},
|
||||
|
@ -8,6 +8,7 @@ import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.*
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.AsynchronousCloseException
|
||||
import java.nio.channels.ByteChannel
|
||||
import java.nio.channels.DatagramChannel
|
||||
import java.nio.channels.SocketChannel
|
||||
@ -30,7 +31,7 @@ public class ChannelPort(
|
||||
channelBuilder: suspend () -> ByteChannel,
|
||||
) : AbstractPort(context, coroutineContext), AutoCloseable {
|
||||
|
||||
private val futureChannel: Deferred<ByteChannel> = this.scope.async(Dispatchers.IO) {
|
||||
private val futureChannel: Deferred<ByteChannel> = scope.async(Dispatchers.IO) {
|
||||
channelBuilder()
|
||||
}
|
||||
|
||||
@ -39,10 +40,10 @@ public class ChannelPort(
|
||||
*/
|
||||
public val startJob: Job get() = futureChannel
|
||||
|
||||
private val listenerJob = this.scope.launch(Dispatchers.IO) {
|
||||
private val listenerJob = scope.launch(Dispatchers.IO) {
|
||||
val channel = futureChannel.await()
|
||||
val buffer = ByteBuffer.allocate(1024)
|
||||
while (isActive) {
|
||||
while (isActive && channel.isOpen) {
|
||||
try {
|
||||
val num = channel.read(buffer)
|
||||
if (num > 0) {
|
||||
@ -50,8 +51,12 @@ public class ChannelPort(
|
||||
}
|
||||
if (num < 0) cancel("The input channel is exhausted")
|
||||
} catch (ex: Exception) {
|
||||
logger.error(ex) { "Channel read error" }
|
||||
delay(1000)
|
||||
if (ex is AsynchronousCloseException) {
|
||||
logger.info { "Channel $channel closed" }
|
||||
} else {
|
||||
logger.error(ex) { "Channel read error, retrying in 1 second" }
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,11 +67,8 @@ public class ChannelPort(
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun close() {
|
||||
listenerJob.cancel()
|
||||
if (futureChannel.isCompleted) {
|
||||
futureChannel.getCompleted().close()
|
||||
} else {
|
||||
futureChannel.cancel()
|
||||
}
|
||||
super.close()
|
||||
}
|
||||
@ -106,7 +108,7 @@ public object UdpPort : PortFactory {
|
||||
/**
|
||||
* Connect a datagram channel to a remote host/port. If [localPort] is provided, it is used to bind local port for receiving messages.
|
||||
*/
|
||||
public fun open(
|
||||
public fun openChannel(
|
||||
context: Context,
|
||||
remoteHost: String,
|
||||
remotePort: Int,
|
||||
@ -128,6 +130,6 @@ public object UdpPort : PortFactory {
|
||||
val remotePort by meta.number { error("Remote port is not specified") }
|
||||
val localHost: String? by meta.string()
|
||||
val localPort: Int? by meta.int()
|
||||
return open(context, remoteHost, remotePort.toInt(), localPort, localHost ?: "localhost")
|
||||
return openChannel(context, remoteHost, remotePort.toInt(), localPort, localHost ?: "localhost")
|
||||
}
|
||||
}
|
@ -1,25 +1,50 @@
|
||||
package space.kscience.controls.ports
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import space.kscience.dataforge.context.Global
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
internal class PortIOTest{
|
||||
internal class PortIOTest {
|
||||
|
||||
@Test
|
||||
fun testDelimiteredByteArrayFlow(){
|
||||
val flow = flowOf("bb?b","ddd?",":defgb?:ddf","34fb?:--").map { it.encodeToByteArray() }
|
||||
fun testDelimiteredByteArrayFlow() {
|
||||
val flow = flowOf("bb?b", "ddd?", ":defgb?:ddf", "34fb?:--").map { it.encodeToByteArray() }
|
||||
val chunked = flow.withDelimiter("?:".encodeToByteArray())
|
||||
runBlocking {
|
||||
val result = chunked.toList()
|
||||
assertEquals(3, result.size)
|
||||
assertEquals("bb?bddd?:",result[0].decodeToString())
|
||||
assertEquals("bb?bddd?:", result[0].decodeToString())
|
||||
assertEquals("defgb?:", result[1].decodeToString())
|
||||
assertEquals("ddf34fb?:", result[2].decodeToString())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUdpCommunication() = runTest {
|
||||
val receiver = UdpPort.openChannel(Global, "localhost", 8811, localPort = 8812)
|
||||
val sender = UdpPort.openChannel(Global, "localhost", 8812, localPort = 8811)
|
||||
|
||||
delay(30)
|
||||
repeat(10) {
|
||||
sender.send("Line number $it\n")
|
||||
}
|
||||
|
||||
val res = receiver
|
||||
.receiving()
|
||||
.withStringDelimiter("\n")
|
||||
.take(10)
|
||||
.toList()
|
||||
|
||||
assertEquals("Line number 3", res[3].trim())
|
||||
receiver.close()
|
||||
sender.close()
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
package space.kscience.controls.modbus
|
||||
|
||||
import com.ghgande.j2mod.modbus.procimg.*
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.io.Buffer
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.ports.readShort
|
||||
import space.kscience.controls.spec.*
|
||||
import space.kscience.dataforge.io.Binary
|
||||
|
||||
|
||||
public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
||||
@ -106,11 +108,11 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
||||
}
|
||||
|
||||
device.useProperty(propertySpec) { value ->
|
||||
val packet = buildPacket {
|
||||
key.format.writeObject(this, value)
|
||||
}.readByteBuffer()
|
||||
val binary = Binary {
|
||||
key.format.writeTo(this, value)
|
||||
}
|
||||
registers.forEachIndexed { index, register ->
|
||||
register.setValue(packet.getShort(index * 2))
|
||||
register.setValue(binary.readShort(index * 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,7 +120,7 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
||||
/**
|
||||
* Trigger [block] if one of register changes.
|
||||
*/
|
||||
private fun List<ObservableRegister>.onChange(block: suspend (ByteReadPacket) -> Unit) {
|
||||
private fun List<ObservableRegister>.onChange(block: suspend (Buffer) -> Unit) {
|
||||
var ready = false
|
||||
|
||||
forEach { register ->
|
||||
@ -128,7 +130,7 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
||||
}
|
||||
|
||||
device.launch {
|
||||
val builder = BytePacketBuilder()
|
||||
val builder = Buffer()
|
||||
while (isActive) {
|
||||
delay(1)
|
||||
if (ready) {
|
||||
@ -136,7 +138,7 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
||||
forEach { value ->
|
||||
writeShort(value.toShort())
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
block(packet)
|
||||
ready = false
|
||||
}
|
||||
@ -154,15 +156,15 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
||||
}
|
||||
|
||||
registers.onChange { packet ->
|
||||
device.write(propertySpec, key.format.readObject(packet))
|
||||
device.write(propertySpec, key.format.readFrom(packet))
|
||||
}
|
||||
|
||||
device.useProperty(propertySpec) { value ->
|
||||
val packet = buildPacket {
|
||||
key.format.writeObject(this, value)
|
||||
}.readByteBuffer()
|
||||
val binary = Binary {
|
||||
key.format.writeTo(this, value)
|
||||
}
|
||||
registers.forEachIndexed { index, observableRegister ->
|
||||
observableRegister.setValue(packet.getShort(index * 2))
|
||||
observableRegister.setValue(binary.readShort(index * 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,7 +214,7 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
||||
|
||||
registers.onChange { packet ->
|
||||
device.launch {
|
||||
device.action(key.format.readObject(packet))
|
||||
device.action(key.format.readFrom(packet))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,10 @@ import com.ghgande.j2mod.modbus.procimg.InputRegister
|
||||
import com.ghgande.j2mod.modbus.procimg.Register
|
||||
import com.ghgande.j2mod.modbus.procimg.SimpleInputRegister
|
||||
import com.ghgande.j2mod.modbus.util.BitVector
|
||||
import io.ktor.utils.io.core.ByteReadPacket
|
||||
import io.ktor.utils.io.core.buildPacket
|
||||
import io.ktor.utils.io.core.readByteBuffer
|
||||
import io.ktor.utils.io.core.writeShort
|
||||
import kotlinx.io.Buffer
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.dataforge.io.Buffer
|
||||
import space.kscience.dataforge.io.ByteArray
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
@ -45,7 +44,7 @@ public interface ModbusDevice : Device {
|
||||
|
||||
public operator fun <T> ModbusRegistryKey.InputRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val packet = readInputRegistersToPacket(address, count)
|
||||
return format.readObject(packet)
|
||||
return format.readFrom(packet)
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +61,7 @@ public interface ModbusDevice : Device {
|
||||
|
||||
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val packet = readHoldingRegistersToPacket(address, count)
|
||||
return format.readObject(packet)
|
||||
return format.readFrom(packet)
|
||||
}
|
||||
|
||||
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.setValue(
|
||||
@ -70,9 +69,9 @@ public interface ModbusDevice : Device {
|
||||
property: KProperty<*>,
|
||||
value: T,
|
||||
) {
|
||||
val buffer = buildPacket {
|
||||
format.writeObject(this, value)
|
||||
}.readByteBuffer()
|
||||
val buffer = ByteArray {
|
||||
format.writeTo(this, value)
|
||||
}
|
||||
writeHoldingRegisters(address, buffer)
|
||||
}
|
||||
|
||||
@ -122,7 +121,7 @@ private fun Array<out InputRegister>.toBuffer(): ByteBuffer {
|
||||
return buffer
|
||||
}
|
||||
|
||||
private fun Array<out InputRegister>.toPacket(): ByteReadPacket = buildPacket {
|
||||
private fun Array<out InputRegister>.toPacket(): Buffer = Buffer {
|
||||
forEach { value ->
|
||||
writeShort(value.toShort())
|
||||
}
|
||||
@ -131,7 +130,7 @@ private fun Array<out InputRegister>.toPacket(): ByteReadPacket = buildPacket {
|
||||
public fun ModbusDevice.readInputRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
||||
master.readInputRegisters(unitId, address, count).toBuffer()
|
||||
|
||||
public fun ModbusDevice.readInputRegistersToPacket(address: Int, count: Int): ByteReadPacket =
|
||||
public fun ModbusDevice.readInputRegistersToPacket(address: Int, count: Int): Buffer =
|
||||
master.readInputRegisters(unitId, address, count).toPacket()
|
||||
|
||||
public fun ModbusDevice.readDoubleInput(address: Int): Double =
|
||||
@ -151,7 +150,7 @@ public fun ModbusDevice.readHoldingRegisters(address: Int, count: Int): List<Reg
|
||||
public fun ModbusDevice.readHoldingRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
||||
master.readMultipleRegisters(unitId, address, count).toBuffer()
|
||||
|
||||
public fun ModbusDevice.readHoldingRegistersToPacket(address: Int, count: Int): ByteReadPacket =
|
||||
public fun ModbusDevice.readHoldingRegistersToPacket(address: Int, count: Int): Buffer =
|
||||
master.readMultipleRegisters(unitId, address, count).toPacket()
|
||||
|
||||
public fun ModbusDevice.readDoubleRegister(address: Int): Double =
|
||||
@ -183,6 +182,13 @@ public fun ModbusDevice.writeHoldingRegisters(address: Int, buffer: ByteBuffer):
|
||||
return writeHoldingRegisters(address, array)
|
||||
}
|
||||
|
||||
public fun ModbusDevice.writeHoldingRegisters(address: Int, byteArray: ByteArray): Int {
|
||||
val buffer = ByteBuffer.wrap(byteArray)
|
||||
val array: ShortArray = ShortArray(buffer.limit().floorDiv(2)) { buffer.getShort(it * 2) }
|
||||
|
||||
return writeHoldingRegisters(address, array)
|
||||
}
|
||||
|
||||
public fun ModbusDevice.modbusRegister(
|
||||
address: Int,
|
||||
): ReadWriteProperty<ModbusDevice, Short> = object : ReadWriteProperty<ModbusDevice, Short> {
|
||||
|
@ -107,7 +107,7 @@ internal class MetaStructureCodec(
|
||||
|
||||
override fun createStructure(name: String, members: LinkedHashMap<String, Meta>): Meta = Meta {
|
||||
members.forEach { (property: String, value: Meta?) ->
|
||||
setMeta(Name.parse(property), value)
|
||||
set(Name.parse(property), value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ public suspend inline fun <reified T: Any> OpcUaDevice.readOpcWithTime(
|
||||
else -> error("Incompatible OPC property value $content")
|
||||
}
|
||||
|
||||
val res: T = converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}")
|
||||
val res: T = converter.metaToObject(meta)
|
||||
return res to time
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.ElementVisionRenderer
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionClient
|
||||
import space.kscience.visionforge.VisionPlugin
|
||||
|
||||
public actual class ControlVisionPlugin : VisionPlugin(), ElementVisionRenderer {
|
||||
@ -20,7 +21,7 @@ public actual class ControlVisionPlugin : VisionPlugin(), ElementVisionRenderer
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun render(element: Element, name: Name, vision: Vision, meta: Meta) {
|
||||
override fun render(element: Element, client: VisionClient, name: Name, vision: Vision, meta: Meta) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec<IDemoDevice>(Compa
|
||||
// register virtual properties based on actual object state
|
||||
val timeScale by mutableProperty(MetaConverter.double, IDemoDevice::timeScaleState) {
|
||||
metaDescriptor {
|
||||
type(ValueType.NUMBER)
|
||||
valueType(ValueType.NUMBER)
|
||||
}
|
||||
description = "Real to virtual time scale"
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ import space.kscience.dataforge.meta.double
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import kotlin.math.pow
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.ExperimentalTime
|
||||
@ -28,7 +30,10 @@ data class Vector2D(var x: Double = 0.0, var y: Double = 0.0) : MetaRepr {
|
||||
operator fun div(arg: Double): Vector2D = Vector2D(x / arg, y / arg)
|
||||
|
||||
companion object CoordinatesMetaConverter : MetaConverter<Vector2D> {
|
||||
override fun metaToObject(meta: Meta): Vector2D = Vector2D(
|
||||
|
||||
override val type: KType = typeOf<Vector2D>()
|
||||
|
||||
override fun metaToObjectOrNull(meta: Meta): Vector2D = Vector2D(
|
||||
meta["x"].double ?: 0.0,
|
||||
meta["y"].double ?: 0.0
|
||||
)
|
||||
@ -40,7 +45,8 @@ data class Vector2D(var x: Double = 0.0, var y: Double = 0.0) : MetaRepr {
|
||||
}
|
||||
}
|
||||
|
||||
open class VirtualCar(context: Context, meta: Meta) : DeviceBySpec<VirtualCar>(IVirtualCar, context, meta), IVirtualCar {
|
||||
open class VirtualCar(context: Context, meta: Meta) : DeviceBySpec<VirtualCar>(IVirtualCar, context, meta),
|
||||
IVirtualCar {
|
||||
private val clock = context.clock
|
||||
|
||||
private val timeScale = 1e-3
|
||||
|
@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
id("org.jetbrains.compose") version "1.5.10"
|
||||
id("org.jetbrains.compose") version "1.5.11"
|
||||
}
|
||||
|
||||
kscience {
|
||||
|
@ -1,10 +0,0 @@
|
||||
package center.sciprog.devices.mks
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
|
||||
object NullableStringMetaConverter : MetaConverter<String?> {
|
||||
override fun metaToObject(meta: Meta): String? = meta.string
|
||||
override fun objectToMeta(obj: String?): Meta = Meta {}
|
||||
}
|
@ -7,4 +7,4 @@ org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.jvmargs=-Xmx4096m
|
||||
|
||||
toolsVersion=0.15.0-kotlin-1.9.20
|
||||
toolsVersion=0.15.2-kotlin-1.9.21
|
4
magix/README.md
Normal file
4
magix/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Module magix
|
||||
|
||||
|
||||
|
@ -17,6 +17,10 @@ kscience {
|
||||
useSerialization{
|
||||
json()
|
||||
}
|
||||
|
||||
commonMain{
|
||||
implementation(spclibs.atomicfu)
|
||||
}
|
||||
}
|
||||
|
||||
readme{
|
||||
|
Loading…
x
Reference in New Issue
Block a user