From a51510606f2c5681a34d9137535e302f539a0b21 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 24 Sep 2023 13:02:52 +0300 Subject: [PATCH 1/7] add customizeable scopes to property listeners --- .../commonMain/kotlin/space/kscience/controls/api/Device.kt | 4 ++-- .../space/kscience/controls/spec/DevicePropertySpec.kt | 6 ++++-- .../kotlin/space/kscience/controls/modbus/ModbusDevice.kt | 4 ---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt index 4fc6365..8a19e68 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt @@ -124,5 +124,5 @@ public fun Device.getAllProperties(): Meta = Meta { /** * Subscribe on property changes for the whole device */ -public fun Device.onPropertyChange(callback: suspend PropertyChangedMessage.() -> Unit): Job = - messageFlow.filterIsInstance().onEach(callback).launchIn(this) +public fun Device.onPropertyChange(scope: CoroutineScope = this, callback: suspend PropertyChangedMessage.() -> Unit): Job = + messageFlow.filterIsInstance().onEach(callback).launchIn(scope) diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt index cf8c741..7f3aa06 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt @@ -115,6 +115,7 @@ public fun D.propertyFlow(spec: DevicePropertySpec): Flow< */ public fun D.onPropertyChange( spec: DevicePropertySpec, + scope: CoroutineScope = this, callback: suspend PropertyChangedMessage.(T) -> Unit, ): Job = messageFlow .filterIsInstance() @@ -124,15 +125,16 @@ public fun D.onPropertyChange( if (newValue != null) { change.callback(newValue) } - }.launchIn(this) + }.launchIn(scope) /** * Call [callback] on initial property value and each value change */ public fun D.useProperty( spec: DevicePropertySpec, + scope: CoroutineScope = this, callback: suspend (T) -> Unit, -): Job = launch { +): Job = scope.launch { callback(read(spec)) messageFlow .filterIsInstance() diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt index ea4330c..3522f15 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt @@ -183,10 +183,6 @@ public fun ModbusDevice.writeHoldingRegisters(address: Int, buffer: ByteBuffer): return writeHoldingRegisters(address, array) } -public fun ModbusDevice.writeShortRegister(address: Int, value: Short) { - master.writeSingleRegister(address, SimpleInputRegister(value.toInt())) -} - public fun ModbusDevice.modbusRegister( address: Int, ): ReadWriteProperty = object : ReadWriteProperty { From a337daee9319ab2de541dbe9caa0f9ba0e64be76 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 24 Sep 2023 13:21:01 +0300 Subject: [PATCH 2/7] Add read-after-write for DeviceBase property writers --- .../kscience/controls/spec/DeviceBase.kt | 21 ++++++++++++------- .../kscience/controls/spec/DeviceSpec.kt | 2 +- .../sciprog/devices/mks/MksPdr900Device.kt | 14 ++++++------- .../pimotionmaster/PiMotionMasterDevice.kt | 4 ++-- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt index 56f5aa9..dc43637 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt @@ -87,7 +87,7 @@ public abstract class DeviceBase( /** * Update logical property state and notify listeners */ - protected suspend fun updateLogical(propertyName: String, value: Meta?) { + protected suspend fun propertyChanged(propertyName: String, value: Meta?) { if (value != logicalState[propertyName]) { stateLock.withLock { logicalState[propertyName] = value @@ -99,10 +99,10 @@ public abstract class DeviceBase( } /** - * Update logical state using given [spec] and its convertor + * Notify the device that a property with [spec] value is changed */ - public suspend fun updateLogical(spec: DevicePropertySpec, value: T) { - updateLogical(spec.name, spec.converter.objectToMeta(value)) + protected suspend fun propertyChanged(spec: DevicePropertySpec, value: T) { + propertyChanged(spec.name, spec.converter.objectToMeta(value)) } /** @@ -112,7 +112,7 @@ public abstract class DeviceBase( override suspend fun readProperty(propertyName: String): Meta { val spec = properties[propertyName] ?: error("Property with name $propertyName not found") val meta = spec.readMeta(self) ?: error("Failed to read property $propertyName") - updateLogical(propertyName, meta) + propertyChanged(propertyName, meta) return meta } @@ -122,7 +122,7 @@ public abstract class DeviceBase( public suspend fun readPropertyOrNull(propertyName: String): Meta? { val spec = properties[propertyName] ?: return null val meta = spec.readMeta(self) ?: return null - updateLogical(propertyName, meta) + propertyChanged(propertyName, meta) return meta } @@ -137,13 +137,18 @@ public abstract class DeviceBase( override suspend fun writeProperty(propertyName: String, value: Meta): Unit { when (val property = properties[propertyName]) { null -> { - //If there is a physical property with a given name, invalidate logical property and write physical one - updateLogical(propertyName, value) + //If there are no physical properties with given name, write a logical one. + propertyChanged(propertyName, value) } is WritableDevicePropertySpec -> { + //if there is a writeable property with a given name, invalidate logical and write physical invalidate(propertyName) property.writeMeta(self, value) + // perform read after writing if the writer did not set the value + if (logicalState[propertyName] == null) { + readPropertyOrNull(propertyName) + } } else -> { diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt index d05bf30..17c3ecb 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt @@ -22,7 +22,7 @@ public val MetaConverter.Companion.unit: MetaConverter get() = UnitMetaCon @OptIn(InternalDeviceAPI::class) public abstract class DeviceSpec { - //initializing meta property for everyone + //initializing the metadata property for everyone private val _properties = hashMapOf>( DeviceMetaPropertySpec.name to DeviceMetaPropertySpec ) diff --git a/demo/mks-pdr900/src/main/kotlin/center/sciprog/devices/mks/MksPdr900Device.kt b/demo/mks-pdr900/src/main/kotlin/center/sciprog/devices/mks/MksPdr900Device.kt index 949ced9..7771709 100644 --- a/demo/mks-pdr900/src/main/kotlin/center/sciprog/devices/mks/MksPdr900Device.kt +++ b/demo/mks-pdr900/src/main/kotlin/center/sciprog/devices/mks/MksPdr900Device.kt @@ -49,16 +49,16 @@ class MksPdr900Device(context: Context, meta: Meta) : DeviceBySpec Date: Sun, 24 Sep 2023 13:29:15 +0300 Subject: [PATCH 3/7] Add read-after-write for DeviceBase property writers --- CHANGELOG.md | 30 ++++++++++++------- .../kscience/controls/spec/DeviceBase.kt | 3 +- demo/all-things/build.gradle.kts | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f9d13..93d9645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ ## Unreleased +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security + +## 0.2.2-dev-1 - 2023-09-24 + +### Changed +- updating logical state in `DeviceBase` is now protected and called `propertyChanged()` +- `DeviceBase` tries to read property after write if the writer does not set the value. + +## 0.2.1 - 2023-09-24 + ### Added - Core interfaces for building a device server - Magix service for binding controls devices (both as RPC client and server) @@ -20,13 +40,3 @@ - A magix event loop implementation in Kotlin. Includes HTTP/SSE and RSocket routes. - Magix history database API - ZMQ client endpoint for Magix - -### Changed - -### Deprecated - -### Removed - -### Fixed - -### Security diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt index dc43637..82a4d4f 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt @@ -147,7 +147,8 @@ public abstract class DeviceBase( property.writeMeta(self, value) // perform read after writing if the writer did not set the value if (logicalState[propertyName] == null) { - readPropertyOrNull(propertyName) + val meta = property.readMeta(self) + propertyChanged(propertyName, meta) } } diff --git a/demo/all-things/build.gradle.kts b/demo/all-things/build.gradle.kts index 05d7395..33b654e 100644 --- a/demo/all-things/build.gradle.kts +++ b/demo/all-things/build.gradle.kts @@ -24,7 +24,7 @@ dependencies { implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("no.tornado:tornadofx:1.7.20") - implementation("space.kscience:plotlykt-server:0.5.3") + implementation("space.kscience:plotlykt-server:0.6.0") // implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") implementation(spclibs.logback.classic) } From efe9a2e842f0c26193f43bac0ac3f4e3d7779231 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 2 Oct 2023 21:24:01 +0300 Subject: [PATCH 4/7] Fixex in modbus and write-protection for same meta --- CHANGELOG.md | 2 + build.gradle.kts | 2 +- .../kscience/controls/spec/DeviceBase.kt | 17 ++++- .../controls/modbus/DeviceProcessImage.kt | 69 +++++++++++++------ .../kscience/controls/modbus/ModbusDevice.kt | 2 +- 5 files changed, 65 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93d9645..b7673a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ ### Removed ### Fixed +- Property writing does not trigger change if logical state already is the same as value to be set. +- Modbus-slave triggers only once for multi-register write. ### Security diff --git a/build.gradle.kts b/build.gradle.kts index d188449..c851997 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ val xodusVersion by extra("2.0.1") allprojects { group = "space.kscience" - version = "0.2.2-dev-1" + version = "0.2.2-dev-2" repositories{ maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt index 82a4d4f..2a6773e 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt @@ -7,18 +7,24 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import space.kscience.controls.api.* import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.debug import space.kscience.dataforge.context.error import space.kscience.dataforge.context.logger import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.misc.DFExperimental import kotlin.coroutines.CoroutineContext - +/** + * Write a meta [item] to [device] + */ @OptIn(InternalDeviceAPI::class) private suspend fun WritableDevicePropertySpec.writeMeta(device: D, item: Meta) { write(device, converter.metaToObject(item) ?: error("Meta $item could not be read with $converter")) } +/** + * Read Meta item from the [device] + */ @OptIn(InternalDeviceAPI::class) private suspend fun DevicePropertySpec.readMeta(device: D): Meta? = read(device)?.let(converter::objectToMeta) @@ -135,9 +141,14 @@ public abstract class DeviceBase( } override suspend fun writeProperty(propertyName: String, value: Meta): Unit { + //bypass property setting if it already has that value + if (logicalState[propertyName] == value) { + logger.debug { "Skipping setting $propertyName to $value because value is already set" } + return + } when (val property = properties[propertyName]) { null -> { - //If there are no physical properties with given name, write a logical one. + //If there are no registered physical properties with given name, write a logical one. propertyChanged(propertyName, value) } @@ -145,7 +156,7 @@ public abstract class DeviceBase( //if there is a writeable property with a given name, invalidate logical and write physical invalidate(propertyName) property.writeMeta(self, value) - // perform read after writing if the writer did not set the value + // perform read after writing if the writer did not set the value and the value is still in invalid state if (logicalState[propertyName] == null) { val meta = property.readMeta(self) propertyChanged(propertyName, meta) diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt index 2f56a8a..32adfb3 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt @@ -1,9 +1,9 @@ package space.kscience.controls.modbus import com.ghgande.j2mod.modbus.procimg.* -import io.ktor.utils.io.core.buildPacket -import io.ktor.utils.io.core.readByteBuffer -import io.ktor.utils.io.core.writeShort +import io.ktor.utils.io.core.* +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import space.kscience.controls.api.Device import space.kscience.controls.spec.DevicePropertySpec @@ -118,22 +118,48 @@ public class DeviceProcessImageBuilder internal constructor( } } + /** + * Trigger [block] if one of register changes. + */ + private fun List.onChange(block: (ByteReadPacket) -> Unit) { + var ready = false + + forEach { register -> + register.addObserver { _, _ -> + ready = true + } + } + + device.launch { + val builder = BytePacketBuilder() + while (isActive) { + delay(1) + if (ready) { + val packet = builder.apply { + forEach { value -> + writeShort(value.toShort()) + } + }.build() + block(packet) + ready = false + } + } + } + } + public fun bind(key: ModbusRegistryKey.HoldingRange, propertySpec: WritableDevicePropertySpec) { val registers = List(key.count) { ObservableRegister() } + registers.forEachIndexed { index, register -> - register.addObserver { _, _ -> - val packet = buildPacket { - registers.forEach { value -> - writeShort(value.toShort()) - } - } - device[propertySpec] = key.format.readObject(packet) - } image.addRegister(key.address + index, register) } + registers.onChange { packet -> + device[propertySpec] = key.format.readObject(packet) + } + device.useProperty(propertySpec) { value -> val packet = buildPacket { key.format.writeObject(this, value) @@ -182,20 +208,17 @@ public class DeviceProcessImageBuilder internal constructor( val registers = List(key.count) { ObservableRegister() } + registers.forEachIndexed { index, register -> - register.addObserver { _, _ -> - val packet = buildPacket { - registers.forEach { value -> - writeShort(value.toShort()) - } - } - device.launch { - device.action(key.format.readObject(packet)) - } - } image.addRegister(key.address + index, register) } + registers.onChange { packet -> + device.launch { + device.action(key.format.readObject(packet)) + } + } + return registers } @@ -205,11 +228,13 @@ public class DeviceProcessImageBuilder internal constructor( * Bind the device to Modbus slave (server) image. */ public fun D.bindProcessImage( + unitId: Int = 0, openOnBind: Boolean = true, binding: DeviceProcessImageBuilder.() -> Unit, ): ProcessImage { - val image = SimpleProcessImage() + val image = SimpleProcessImage(unitId) DeviceProcessImageBuilder(this, image).apply(binding) + image.setLocked(true) if (openOnBind) { launch { open() diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt index 3522f15..2f9e923 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt @@ -61,7 +61,7 @@ public interface ModbusDevice : Device { } public operator fun ModbusRegistryKey.HoldingRange.getValue(thisRef: Any?, property: KProperty<*>): T { - val packet = readInputRegistersToPacket(address, count) + val packet = readHoldingRegistersToPacket(address, count) return format.readObject(packet) } From 2cc0a5bcbca0cd43f77e7bd93242ad805bdd72e5 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 2 Oct 2023 22:12:11 +0300 Subject: [PATCH 5/7] Fixex in modbus and write-protection for same meta --- .../space/kscience/controls/spec/DeviceBase.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt index 2a6773e..5e07880 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt @@ -1,14 +1,16 @@ package space.kscience.controls.spec -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.newCoroutineContext import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import space.kscience.controls.api.* import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.debug -import space.kscience.dataforge.context.error import space.kscience.dataforge.context.logger import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.misc.DFExperimental @@ -65,13 +67,7 @@ public abstract class DeviceBase( get() = actions.values.map { it.descriptor } override val coroutineContext: CoroutineContext by lazy { - context.newCoroutineContext( - SupervisorJob(context.coroutineContext[Job]) + - CoroutineName("Device $this") + - CoroutineExceptionHandler { _, throwable -> - logger.error(throwable) { "Exception in device $this job" } - } - ) + context.newCoroutineContext(SupervisorJob(context.coroutineContext[Job]) + CoroutineName("Device $this")) } From 01606af30799d719472b7cc91125a73afccc890c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 5 Oct 2023 07:43:49 +0300 Subject: [PATCH 6/7] clientId -> unitId --- .../kscience/controls/modbus/ModbusDevice.kt | 34 +++++++++---------- .../controls/modbus/ModbusDeviceBySpec.kt | 2 +- .../controls/modbus/ModbusRegistryMap.kt | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt index 2f9e923..7297616 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt @@ -21,9 +21,9 @@ import kotlin.reflect.KProperty public interface ModbusDevice : Device { /** - * Client id for this specific device + * Unit id for this specific device */ - public val clientId: Int + public val unitId: Int /** * The modubus master connector @@ -82,35 +82,35 @@ public interface ModbusDevice : Device { * Read multiple sequential modbus coils (bit-values) */ public fun ModbusDevice.readCoils(address: Int, count: Int): BitVector = - master.readCoils(clientId, address, count) + master.readCoils(unitId, address, count) public fun ModbusDevice.readCoil(address: Int): Boolean = - master.readCoils(clientId, address, 1).getBit(0) + master.readCoils(unitId, address, 1).getBit(0) public fun ModbusDevice.writeCoils(address: Int, values: BooleanArray) { val bitVector = BitVector(values.size) values.forEachIndexed { index, value -> bitVector.setBit(index, value) } - master.writeMultipleCoils(clientId, address, bitVector) + master.writeMultipleCoils(unitId, address, bitVector) } public fun ModbusDevice.writeCoil(address: Int, value: Boolean) { - master.writeCoil(clientId, address, value) + master.writeCoil(unitId, address, value) } public fun ModbusDevice.writeCoil(key: ModbusRegistryKey.Coil, value: Boolean) { - master.writeCoil(clientId, key.address, value) + master.writeCoil(unitId, key.address, value) } public fun ModbusDevice.readInputDiscretes(address: Int, count: Int): BitVector = - master.readInputDiscretes(clientId, address, count) + master.readInputDiscretes(unitId, address, count) public fun ModbusDevice.readInputDiscrete(address: Int): Boolean = - master.readInputDiscretes(clientId, address, 1).getBit(0) + master.readInputDiscretes(unitId, address, 1).getBit(0) public fun ModbusDevice.readInputRegisters(address: Int, count: Int): List = - master.readInputRegisters(clientId, address, count).toList() + master.readInputRegisters(unitId, address, count).toList() private fun Array.toBuffer(): ByteBuffer { val buffer: ByteBuffer = ByteBuffer.allocate(size * 2) @@ -129,10 +129,10 @@ private fun Array.toPacket(): ByteReadPacket = buildPacket { } public fun ModbusDevice.readInputRegistersToBuffer(address: Int, count: Int): ByteBuffer = - master.readInputRegisters(clientId, address, count).toBuffer() + master.readInputRegisters(unitId, address, count).toBuffer() public fun ModbusDevice.readInputRegistersToPacket(address: Int, count: Int): ByteReadPacket = - master.readInputRegisters(clientId, address, count).toPacket() + master.readInputRegisters(unitId, address, count).toPacket() public fun ModbusDevice.readDoubleInput(address: Int): Double = readInputRegistersToBuffer(address, Double.SIZE_BYTES).getDouble() @@ -141,7 +141,7 @@ public fun ModbusDevice.readInputRegister(address: Int): Short = readInputRegisters(address, 1).first().toShort() public fun ModbusDevice.readHoldingRegisters(address: Int, count: Int): List = - master.readMultipleRegisters(clientId, address, count).toList() + master.readMultipleRegisters(unitId, address, count).toList() /** * Read a number of registers to a [ByteBuffer] @@ -149,10 +149,10 @@ public fun ModbusDevice.readHoldingRegisters(address: Int, count: Int): List(values.size) { SimpleInputRegister(values[it].toInt()) } ) public fun ModbusDevice.writeHoldingRegister(address: Int, value: Short): Int = master.writeSingleRegister( - clientId, + unitId, address, SimpleInputRegister(value.toInt()) ) diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt index 916187f..48f48a4 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt @@ -15,7 +15,7 @@ import space.kscience.dataforge.names.NameToken public open class ModbusDeviceBySpec( context: Context, spec: DeviceSpec, - override val clientId: Int, + override val unitId: Int, override val master: AbstractModbusMaster, private val disposeMasterOnClose: Boolean = true, meta: Meta = Meta.EMPTY, diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt index a6fc894..6e3c1da 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt @@ -152,7 +152,7 @@ public abstract class ModbusRegistryMap { val rangeString = if (key.count == 1) { key.address.toString() } else { - "${key.address} - ${key.address + key.count}" + "${key.address} - ${key.address + key.count - 1}" } to.appendLine("${typeString}\t$rangeString\t$description") } From f1b63c3951c5da04a4e4ee7c836702dbc1086c02 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 7 Oct 2023 18:34:44 +0300 Subject: [PATCH 7/7] Add buffer to device messages --- .../kotlin/space/kscience/controls/spec/DeviceBase.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt index 5e07880..38aa20a 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt @@ -3,6 +3,7 @@ package space.kscience.controls.spec import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.newCoroutineContext @@ -13,6 +14,8 @@ import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.debug import space.kscience.dataforge.context.logger import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.int import space.kscience.dataforge.misc.DFExperimental import kotlin.coroutines.CoroutineContext @@ -47,7 +50,7 @@ private suspend fun DeviceActionSpec.executeWithMeta */ public abstract class DeviceBase( final override val context: Context, - override val meta: Meta = Meta.EMPTY, + final override val meta: Meta = Meta.EMPTY, ) : Device { /** @@ -76,7 +79,10 @@ public abstract class DeviceBase( */ private val logicalState: HashMap = HashMap() - private val sharedMessageFlow: MutableSharedFlow = MutableSharedFlow() + private val sharedMessageFlow: MutableSharedFlow = MutableSharedFlow( + replay = meta["message.buffer"].int ?: 1000, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) public override val messageFlow: SharedFlow get() = sharedMessageFlow