Compare commits
No commits in common. "a12cf440e8a7bd686f2ae69d6f526f3863c61d09" and "cf129b6242ac53395a972dd5e9a859d64e186c13" have entirely different histories.
a12cf440e8
...
cf129b6242
@ -13,7 +13,7 @@ val xodusVersion by extra("2.0.1")
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "space.kscience"
|
group = "space.kscience"
|
||||||
version = "0.3.0-dev-4"
|
version = "0.3.0-dev-3"
|
||||||
repositories{
|
repositories{
|
||||||
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
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
|
public var <T : Any> MutableDeviceState<T>.valueAsMeta: Meta
|
||||||
get() = converter.objectToMeta(value)
|
get() = converter.objectToMeta(value)
|
||||||
set(arg) {
|
set(arg) {
|
||||||
value = converter.metaToObject(arg)
|
value = converter.metaToObject(arg) ?: error("Conversion for meta $arg to property type with $converter failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,14 +20,10 @@ kscience {
|
|||||||
json()
|
json()
|
||||||
}
|
}
|
||||||
useContextReceivers()
|
useContextReceivers()
|
||||||
commonMain {
|
dependencies {
|
||||||
api("space.kscience:dataforge-io:$dataforgeVersion")
|
api("space.kscience:dataforge-io:$dataforgeVersion")
|
||||||
api(spclibs.kotlinx.datetime)
|
api(spclibs.kotlinx.datetime)
|
||||||
}
|
}
|
||||||
|
|
||||||
jvmTest{
|
|
||||||
implementation(spclibs.logback.classic)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ public abstract class AbstractPort(
|
|||||||
protected val scope: CoroutineScope = CoroutineScope(coroutineContext + SupervisorJob(coroutineContext[Job]))
|
protected val scope: CoroutineScope = CoroutineScope(coroutineContext + SupervisorJob(coroutineContext[Job]))
|
||||||
|
|
||||||
private val outgoing = Channel<ByteArray>(100)
|
private val outgoing = Channel<ByteArray>(100)
|
||||||
private val incoming = Channel<ByteArray>(100)
|
private val incoming = Channel<ByteArray>(Channel.CONFLATED)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.coroutineContext[Job]?.invokeOnCompletion {
|
scope.coroutineContext[Job]?.invokeOnCompletion {
|
||||||
@ -88,6 +88,7 @@ public abstract class AbstractPort(
|
|||||||
override fun close() {
|
override fun close() {
|
||||||
outgoing.close()
|
outgoing.close()
|
||||||
incoming.close()
|
incoming.close()
|
||||||
|
sendJob.cancel()
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package space.kscience.controls.ports
|
|
||||||
|
|
||||||
import space.kscience.dataforge.io.Binary
|
|
||||||
|
|
||||||
public fun Binary.readShort(position: Int): Short = read(position) { readShort() }
|
|
@ -5,7 +5,6 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.transform
|
import kotlinx.coroutines.flow.transform
|
||||||
import kotlinx.io.Buffer
|
import kotlinx.io.Buffer
|
||||||
import kotlinx.io.readByteArray
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform byte fragments into complete phrases using given delimiter. Not thread safe.
|
* Transform byte fragments into complete phrases using given delimiter. Not thread safe.
|
||||||
@ -28,8 +27,9 @@ public fun Flow<ByteArray>.withDelimiter(delimiter: ByteArray): Flow<ByteArray>
|
|||||||
matcherPosition++
|
matcherPosition++
|
||||||
if (matcherPosition == delimiter.size) {
|
if (matcherPosition == delimiter.size) {
|
||||||
//full match achieved, sending result
|
//full match achieved, sending result
|
||||||
emit(output.readByteArray())
|
val bytes = output.build()
|
||||||
output.clear()
|
emit(bytes.readBytes())
|
||||||
|
output.reset()
|
||||||
matcherPosition = 0
|
matcherPosition = 0
|
||||||
}
|
}
|
||||||
} else if (matcherPosition > 0) {
|
} else if (matcherPosition > 0) {
|
||||||
|
@ -8,7 +8,6 @@ import space.kscience.dataforge.context.logger
|
|||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.channels.AsynchronousCloseException
|
|
||||||
import java.nio.channels.ByteChannel
|
import java.nio.channels.ByteChannel
|
||||||
import java.nio.channels.DatagramChannel
|
import java.nio.channels.DatagramChannel
|
||||||
import java.nio.channels.SocketChannel
|
import java.nio.channels.SocketChannel
|
||||||
@ -31,7 +30,7 @@ public class ChannelPort(
|
|||||||
channelBuilder: suspend () -> ByteChannel,
|
channelBuilder: suspend () -> ByteChannel,
|
||||||
) : AbstractPort(context, coroutineContext), AutoCloseable {
|
) : AbstractPort(context, coroutineContext), AutoCloseable {
|
||||||
|
|
||||||
private val futureChannel: Deferred<ByteChannel> = scope.async(Dispatchers.IO) {
|
private val futureChannel: Deferred<ByteChannel> = this.scope.async(Dispatchers.IO) {
|
||||||
channelBuilder()
|
channelBuilder()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,10 +39,10 @@ public class ChannelPort(
|
|||||||
*/
|
*/
|
||||||
public val startJob: Job get() = futureChannel
|
public val startJob: Job get() = futureChannel
|
||||||
|
|
||||||
private val listenerJob = scope.launch(Dispatchers.IO) {
|
private val listenerJob = this.scope.launch(Dispatchers.IO) {
|
||||||
val channel = futureChannel.await()
|
val channel = futureChannel.await()
|
||||||
val buffer = ByteBuffer.allocate(1024)
|
val buffer = ByteBuffer.allocate(1024)
|
||||||
while (isActive && channel.isOpen) {
|
while (isActive) {
|
||||||
try {
|
try {
|
||||||
val num = channel.read(buffer)
|
val num = channel.read(buffer)
|
||||||
if (num > 0) {
|
if (num > 0) {
|
||||||
@ -51,15 +50,11 @@ public class ChannelPort(
|
|||||||
}
|
}
|
||||||
if (num < 0) cancel("The input channel is exhausted")
|
if (num < 0) cancel("The input channel is exhausted")
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
if (ex is AsynchronousCloseException) {
|
logger.error(ex) { "Channel read error" }
|
||||||
logger.info { "Channel $channel closed" }
|
|
||||||
} else {
|
|
||||||
logger.error(ex) { "Channel read error, retrying in 1 second" }
|
|
||||||
delay(1000)
|
delay(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) {
|
override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) {
|
||||||
futureChannel.await().write(ByteBuffer.wrap(data))
|
futureChannel.await().write(ByteBuffer.wrap(data))
|
||||||
@ -67,8 +62,11 @@ public class ChannelPort(
|
|||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
listenerJob.cancel()
|
||||||
if (futureChannel.isCompleted) {
|
if (futureChannel.isCompleted) {
|
||||||
futureChannel.getCompleted().close()
|
futureChannel.getCompleted().close()
|
||||||
|
} else {
|
||||||
|
futureChannel.cancel()
|
||||||
}
|
}
|
||||||
super.close()
|
super.close()
|
||||||
}
|
}
|
||||||
@ -108,7 +106,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.
|
* 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 openChannel(
|
public fun open(
|
||||||
context: Context,
|
context: Context,
|
||||||
remoteHost: String,
|
remoteHost: String,
|
||||||
remotePort: Int,
|
remotePort: Int,
|
||||||
@ -130,6 +128,6 @@ public object UdpPort : PortFactory {
|
|||||||
val remotePort by meta.number { error("Remote port is not specified") }
|
val remotePort by meta.number { error("Remote port is not specified") }
|
||||||
val localHost: String? by meta.string()
|
val localHost: String? by meta.string()
|
||||||
val localPort: Int? by meta.int()
|
val localPort: Int? by meta.int()
|
||||||
return openChannel(context, remoteHost, remotePort.toInt(), localPort, localHost ?: "localhost")
|
return open(context, remoteHost, remotePort.toInt(), localPort, localHost ?: "localhost")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,50 +1,25 @@
|
|||||||
package space.kscience.controls.ports
|
package space.kscience.controls.ports
|
||||||
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.take
|
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import space.kscience.dataforge.context.Global
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
|
||||||
internal class PortIOTest {
|
internal class PortIOTest{
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDelimiteredByteArrayFlow() {
|
fun testDelimiteredByteArrayFlow(){
|
||||||
val flow = flowOf("bb?b", "ddd?", ":defgb?:ddf", "34fb?:--").map { it.encodeToByteArray() }
|
val flow = flowOf("bb?b","ddd?",":defgb?:ddf","34fb?:--").map { it.encodeToByteArray() }
|
||||||
val chunked = flow.withDelimiter("?:".encodeToByteArray())
|
val chunked = flow.withDelimiter("?:".encodeToByteArray())
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val result = chunked.toList()
|
val result = chunked.toList()
|
||||||
assertEquals(3, result.size)
|
assertEquals(3, result.size)
|
||||||
assertEquals("bb?bddd?:", result[0].decodeToString())
|
assertEquals("bb?bddd?:",result[0].decodeToString())
|
||||||
assertEquals("defgb?:", result[1].decodeToString())
|
assertEquals("defgb?:", result[1].decodeToString())
|
||||||
assertEquals("ddf34fb?:", result[2].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,14 +1,12 @@
|
|||||||
package space.kscience.controls.modbus
|
package space.kscience.controls.modbus
|
||||||
|
|
||||||
import com.ghgande.j2mod.modbus.procimg.*
|
import com.ghgande.j2mod.modbus.procimg.*
|
||||||
|
import io.ktor.utils.io.core.*
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.io.Buffer
|
|
||||||
import space.kscience.controls.api.Device
|
import space.kscience.controls.api.Device
|
||||||
import space.kscience.controls.ports.readShort
|
|
||||||
import space.kscience.controls.spec.*
|
import space.kscience.controls.spec.*
|
||||||
import space.kscience.dataforge.io.Binary
|
|
||||||
|
|
||||||
|
|
||||||
public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
||||||
@ -108,11 +106,11 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
device.useProperty(propertySpec) { value ->
|
device.useProperty(propertySpec) { value ->
|
||||||
val binary = Binary {
|
val packet = buildPacket {
|
||||||
key.format.writeTo(this, value)
|
key.format.writeObject(this, value)
|
||||||
}
|
}.readByteBuffer()
|
||||||
registers.forEachIndexed { index, register ->
|
registers.forEachIndexed { index, register ->
|
||||||
register.setValue(binary.readShort(index * 2))
|
register.setValue(packet.getShort(index * 2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,7 +118,7 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
|||||||
/**
|
/**
|
||||||
* Trigger [block] if one of register changes.
|
* Trigger [block] if one of register changes.
|
||||||
*/
|
*/
|
||||||
private fun List<ObservableRegister>.onChange(block: suspend (Buffer) -> Unit) {
|
private fun List<ObservableRegister>.onChange(block: suspend (ByteReadPacket) -> Unit) {
|
||||||
var ready = false
|
var ready = false
|
||||||
|
|
||||||
forEach { register ->
|
forEach { register ->
|
||||||
@ -130,7 +128,7 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
device.launch {
|
device.launch {
|
||||||
val builder = Buffer()
|
val builder = BytePacketBuilder()
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(1)
|
delay(1)
|
||||||
if (ready) {
|
if (ready) {
|
||||||
@ -138,7 +136,7 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
|||||||
forEach { value ->
|
forEach { value ->
|
||||||
writeShort(value.toShort())
|
writeShort(value.toShort())
|
||||||
}
|
}
|
||||||
}
|
}.build()
|
||||||
block(packet)
|
block(packet)
|
||||||
ready = false
|
ready = false
|
||||||
}
|
}
|
||||||
@ -156,15 +154,15 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
registers.onChange { packet ->
|
registers.onChange { packet ->
|
||||||
device.write(propertySpec, key.format.readFrom(packet))
|
device.write(propertySpec, key.format.readObject(packet))
|
||||||
}
|
}
|
||||||
|
|
||||||
device.useProperty(propertySpec) { value ->
|
device.useProperty(propertySpec) { value ->
|
||||||
val binary = Binary {
|
val packet = buildPacket {
|
||||||
key.format.writeTo(this, value)
|
key.format.writeObject(this, value)
|
||||||
}
|
}.readByteBuffer()
|
||||||
registers.forEachIndexed { index, observableRegister ->
|
registers.forEachIndexed { index, observableRegister ->
|
||||||
observableRegister.setValue(binary.readShort(index * 2))
|
observableRegister.setValue(packet.getShort(index * 2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,7 +212,7 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
|||||||
|
|
||||||
registers.onChange { packet ->
|
registers.onChange { packet ->
|
||||||
device.launch {
|
device.launch {
|
||||||
device.action(key.format.readFrom(packet))
|
device.action(key.format.readObject(packet))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,11 @@ import com.ghgande.j2mod.modbus.procimg.InputRegister
|
|||||||
import com.ghgande.j2mod.modbus.procimg.Register
|
import com.ghgande.j2mod.modbus.procimg.Register
|
||||||
import com.ghgande.j2mod.modbus.procimg.SimpleInputRegister
|
import com.ghgande.j2mod.modbus.procimg.SimpleInputRegister
|
||||||
import com.ghgande.j2mod.modbus.util.BitVector
|
import com.ghgande.j2mod.modbus.util.BitVector
|
||||||
import kotlinx.io.Buffer
|
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 space.kscience.controls.api.Device
|
import space.kscience.controls.api.Device
|
||||||
import space.kscience.dataforge.io.Buffer
|
|
||||||
import space.kscience.dataforge.io.ByteArray
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
@ -44,7 +45,7 @@ public interface ModbusDevice : Device {
|
|||||||
|
|
||||||
public operator fun <T> ModbusRegistryKey.InputRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
|
public operator fun <T> ModbusRegistryKey.InputRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
val packet = readInputRegistersToPacket(address, count)
|
val packet = readInputRegistersToPacket(address, count)
|
||||||
return format.readFrom(packet)
|
return format.readObject(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ public interface ModbusDevice : Device {
|
|||||||
|
|
||||||
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
|
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
val packet = readHoldingRegistersToPacket(address, count)
|
val packet = readHoldingRegistersToPacket(address, count)
|
||||||
return format.readFrom(packet)
|
return format.readObject(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.setValue(
|
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.setValue(
|
||||||
@ -69,9 +70,9 @@ public interface ModbusDevice : Device {
|
|||||||
property: KProperty<*>,
|
property: KProperty<*>,
|
||||||
value: T,
|
value: T,
|
||||||
) {
|
) {
|
||||||
val buffer = ByteArray {
|
val buffer = buildPacket {
|
||||||
format.writeTo(this, value)
|
format.writeObject(this, value)
|
||||||
}
|
}.readByteBuffer()
|
||||||
writeHoldingRegisters(address, buffer)
|
writeHoldingRegisters(address, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ private fun Array<out InputRegister>.toBuffer(): ByteBuffer {
|
|||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Array<out InputRegister>.toPacket(): Buffer = Buffer {
|
private fun Array<out InputRegister>.toPacket(): ByteReadPacket = buildPacket {
|
||||||
forEach { value ->
|
forEach { value ->
|
||||||
writeShort(value.toShort())
|
writeShort(value.toShort())
|
||||||
}
|
}
|
||||||
@ -130,7 +131,7 @@ private fun Array<out InputRegister>.toPacket(): Buffer = Buffer {
|
|||||||
public fun ModbusDevice.readInputRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
public fun ModbusDevice.readInputRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
||||||
master.readInputRegisters(unitId, address, count).toBuffer()
|
master.readInputRegisters(unitId, address, count).toBuffer()
|
||||||
|
|
||||||
public fun ModbusDevice.readInputRegistersToPacket(address: Int, count: Int): Buffer =
|
public fun ModbusDevice.readInputRegistersToPacket(address: Int, count: Int): ByteReadPacket =
|
||||||
master.readInputRegisters(unitId, address, count).toPacket()
|
master.readInputRegisters(unitId, address, count).toPacket()
|
||||||
|
|
||||||
public fun ModbusDevice.readDoubleInput(address: Int): Double =
|
public fun ModbusDevice.readDoubleInput(address: Int): Double =
|
||||||
@ -150,7 +151,7 @@ public fun ModbusDevice.readHoldingRegisters(address: Int, count: Int): List<Reg
|
|||||||
public fun ModbusDevice.readHoldingRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
public fun ModbusDevice.readHoldingRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
||||||
master.readMultipleRegisters(unitId, address, count).toBuffer()
|
master.readMultipleRegisters(unitId, address, count).toBuffer()
|
||||||
|
|
||||||
public fun ModbusDevice.readHoldingRegistersToPacket(address: Int, count: Int): Buffer =
|
public fun ModbusDevice.readHoldingRegistersToPacket(address: Int, count: Int): ByteReadPacket =
|
||||||
master.readMultipleRegisters(unitId, address, count).toPacket()
|
master.readMultipleRegisters(unitId, address, count).toPacket()
|
||||||
|
|
||||||
public fun ModbusDevice.readDoubleRegister(address: Int): Double =
|
public fun ModbusDevice.readDoubleRegister(address: Int): Double =
|
||||||
@ -182,13 +183,6 @@ public fun ModbusDevice.writeHoldingRegisters(address: Int, buffer: ByteBuffer):
|
|||||||
return writeHoldingRegisters(address, array)
|
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(
|
public fun ModbusDevice.modbusRegister(
|
||||||
address: Int,
|
address: Int,
|
||||||
): ReadWriteProperty<ModbusDevice, Short> = object : ReadWriteProperty<ModbusDevice, Short> {
|
): 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 {
|
override fun createStructure(name: String, members: LinkedHashMap<String, Meta>): Meta = Meta {
|
||||||
members.forEach { (property: String, value: Meta?) ->
|
members.forEach { (property: String, value: Meta?) ->
|
||||||
set(Name.parse(property), value)
|
setMeta(Name.parse(property), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public suspend inline fun <reified T: Any> OpcUaDevice.readOpcWithTime(
|
|||||||
else -> error("Incompatible OPC property value $content")
|
else -> error("Incompatible OPC property value $content")
|
||||||
}
|
}
|
||||||
|
|
||||||
val res: T = converter.metaToObject(meta)
|
val res: T = converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}")
|
||||||
return res to time
|
return res to time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import space.kscience.dataforge.meta.Meta
|
|||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.visionforge.ElementVisionRenderer
|
import space.kscience.visionforge.ElementVisionRenderer
|
||||||
import space.kscience.visionforge.Vision
|
import space.kscience.visionforge.Vision
|
||||||
import space.kscience.visionforge.VisionClient
|
|
||||||
import space.kscience.visionforge.VisionPlugin
|
import space.kscience.visionforge.VisionPlugin
|
||||||
|
|
||||||
public actual class ControlVisionPlugin : VisionPlugin(), ElementVisionRenderer {
|
public actual class ControlVisionPlugin : VisionPlugin(), ElementVisionRenderer {
|
||||||
@ -21,7 +20,7 @@ public actual class ControlVisionPlugin : VisionPlugin(), ElementVisionRenderer
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(element: Element, client: VisionClient, name: Name, vision: Vision, meta: Meta) {
|
override fun render(element: Element, name: Name, vision: Vision, meta: Meta) {
|
||||||
TODO("Not yet implemented")
|
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
|
// register virtual properties based on actual object state
|
||||||
val timeScale by mutableProperty(MetaConverter.double, IDemoDevice::timeScaleState) {
|
val timeScale by mutableProperty(MetaConverter.double, IDemoDevice::timeScaleState) {
|
||||||
metaDescriptor {
|
metaDescriptor {
|
||||||
valueType(ValueType.NUMBER)
|
type(ValueType.NUMBER)
|
||||||
}
|
}
|
||||||
description = "Real to virtual time scale"
|
description = "Real to virtual time scale"
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,6 @@ import space.kscience.dataforge.meta.double
|
|||||||
import space.kscience.dataforge.meta.get
|
import space.kscience.dataforge.meta.get
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.reflect.KType
|
|
||||||
import kotlin.reflect.typeOf
|
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
@ -30,10 +28,7 @@ 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)
|
operator fun div(arg: Double): Vector2D = Vector2D(x / arg, y / arg)
|
||||||
|
|
||||||
companion object CoordinatesMetaConverter : MetaConverter<Vector2D> {
|
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["x"].double ?: 0.0,
|
||||||
meta["y"].double ?: 0.0
|
meta["y"].double ?: 0.0
|
||||||
)
|
)
|
||||||
@ -45,8 +40,7 @@ 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),
|
open class VirtualCar(context: Context, meta: Meta) : DeviceBySpec<VirtualCar>(IVirtualCar, context, meta), IVirtualCar {
|
||||||
IVirtualCar {
|
|
||||||
private val clock = context.clock
|
private val clock = context.clock
|
||||||
|
|
||||||
private val timeScale = 1e-3
|
private val timeScale = 1e-3
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
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 {}
|
||||||
|
}
|
@ -17,10 +17,6 @@ kscience {
|
|||||||
useSerialization{
|
useSerialization{
|
||||||
json()
|
json()
|
||||||
}
|
}
|
||||||
|
|
||||||
commonMain{
|
|
||||||
implementation(spclibs.atomicfu)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readme{
|
readme{
|
||||||
|
Loading…
Reference in New Issue
Block a user