From 166cc03fe26e8c8bdc64d24e5d3867b11b5df5bb Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 4 Jun 2023 21:18:42 +0300 Subject: [PATCH] Add modbus client --- .../kscience/controls/spec/DeviceSpec.kt | 21 +-- .../controls/modbus/DeviceProcessImage.kt | 145 ++++++++++++++++++ .../controls/modbus/ModbusRegistryMap.kt | 7 +- 3 files changed, 149 insertions(+), 24 deletions(-) create mode 100644 controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt 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 ff7dddd..d05bf30 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 @@ -44,25 +44,6 @@ public abstract class DeviceSpec { return deviceProperty } -// public fun registerProperty( -// converter: MetaConverter, -// readOnlyProperty: KProperty1, -// descriptorBuilder: PropertyDescriptor.() -> Unit = {}, -// ): DevicePropertySpec { -// val deviceProperty = object : DevicePropertySpec { -// -// override val descriptor: PropertyDescriptor = PropertyDescriptor(readOnlyProperty.name) -// .apply(descriptorBuilder) -// -// override val converter: MetaConverter = converter -// -// override suspend fun read(device: D): T = withContext(device.coroutineContext) { -// readOnlyProperty.get(device) -// } -// } -// return registerProperty(deviceProperty) -// } - public fun property( converter: MetaConverter, readOnlyProperty: KProperty1, @@ -96,7 +77,7 @@ public abstract class DeviceSpec { val deviceProperty = object : WritableDevicePropertySpec { override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply { - //TODO add type from converter + //TODO add the type from converter writable = true }.apply(descriptorBuilder) 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 new file mode 100644 index 0000000..581d228 --- /dev/null +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt @@ -0,0 +1,145 @@ +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 space.kscience.controls.api.Device +import space.kscience.controls.spec.DevicePropertySpec +import space.kscience.controls.spec.WritableDevicePropertySpec +import space.kscience.controls.spec.set +import space.kscience.controls.spec.useProperty + + +public class DeviceToModbusMapping private constructor( + private val mapping: Map, ModbusRegistryKey>, +) : Map, ModbusRegistryKey> by mapping { + public class Builder { + private val mapping = HashMap, ModbusRegistryKey>() + + public fun bind(propertySpec: DevicePropertySpec, key: ModbusRegistryKey.DiscreteInput) { + mapping[propertySpec] = key + } + + public fun bind(propertySpec: WritableDevicePropertySpec, key: ModbusRegistryKey.Coil) { + mapping[propertySpec] = key + } + + public fun bind(propertySpec: DevicePropertySpec, key: ModbusRegistryKey.InputRegister) { + mapping[propertySpec] = key + } + + public fun bind(propertySpec: WritableDevicePropertySpec, key: ModbusRegistryKey.HoldingRegister) { + mapping[propertySpec] = key + } + + public fun bind(propertySpec: DevicePropertySpec, key: ModbusRegistryKey.InputRange) { + mapping[propertySpec] = key + } + + public fun bind(propertySpec: WritableDevicePropertySpec, key: ModbusRegistryKey.HoldingRange) { + mapping[propertySpec] = key + } + + public fun build(): DeviceToModbusMapping = DeviceToModbusMapping(mapping) + } +} + +public fun DeviceToModbusMapping(block: DeviceToModbusMapping.Builder.()->Unit): DeviceToModbusMapping = + DeviceToModbusMapping.Builder().apply(block).build() + +@Suppress("UNCHECKED_CAST") +public fun D.toProcessImage(mapping: DeviceToModbusMapping): ProcessImage { + val image = SimpleProcessImage() + mapping.forEach { (spec, key) -> + when (key) { + is ModbusRegistryKey.Coil -> { + spec as WritableDevicePropertySpec + val coil = ObservableDigitalOut() + coil.addObserver { _, _ -> + set(spec, coil.isSet) + } + image.setDigitalOut(key.address, coil) + useProperty(spec) { value -> + coil.set(value) + } + } + + is ModbusRegistryKey.DiscreteInput -> { + spec as DevicePropertySpec + val input = SimpleDigitalIn() + image.setDigitalIn(key.address, input) + useProperty(spec) { value -> + input.set(value) + } + } + + is ModbusRegistryKey.HoldingRegister -> { + spec as WritableDevicePropertySpec + val register = ObservableRegister() + register.addObserver { _, _ -> + set(spec, register.toShort()) + } + image.setRegister(key.address, register) + useProperty(spec) { value -> + register.setValue(value) + } + } + + is ModbusRegistryKey.InputRegister -> { + spec as DevicePropertySpec + val input = SimpleInputRegister() + image.setRegister(key.address, input) + useProperty(spec) { value -> + input.setValue(value) + } + } + + is ModbusRegistryKey.HoldingRange<*> -> { + spec as WritableDevicePropertySpec + key as ModbusRegistryKey.HoldingRange + val registers = List(key.count) { + ObservableRegister() + } + registers.forEachIndexed { index, register -> + register.addObserver { _, _ -> + val packet = buildPacket { + registers.forEach { value -> + writeShort(value.toShort()) + } + } + set(spec, key.format.readObject(packet)) + } + image.setRegister(key.address + index, register) + } + + useProperty(spec) { value -> + val packet = buildPacket { + key.format.writeObject(this, value) + }.readByteBuffer() + registers.forEachIndexed { index, observableRegister -> + observableRegister.setValue(packet.getShort(index * 2)) + } + } + } + + is ModbusRegistryKey.InputRange<*> -> { + spec as DevicePropertySpec + key as ModbusRegistryKey.InputRange + val registers = List(key.count) { + SimpleInputRegister() + } + + useProperty(spec) { value -> + val packet = buildPacket { + key.format.writeObject(this, value) + }.readByteBuffer() + registers.forEachIndexed { index, register -> + register.setValue(packet.getShort(index * 2)) + } + } + } + } + } + return image +} \ No newline at end of file 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 d367397..80e997a 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 @@ -1,7 +1,6 @@ package space.kscience.controls.modbus import space.kscience.dataforge.io.IOFormat -import space.kscience.dataforge.io.IOReader public sealed class ModbusRegistryKey { @@ -38,7 +37,7 @@ public sealed class ModbusRegistryKey { public data class InputRange( override val address: Int, override val count: Int, - public val format: IOReader, + public val format: IOFormat, ) : ModbusRegistryKey() { public val endAddress: Int get() = address + count @@ -97,7 +96,7 @@ public abstract class ModbusRegistryMap { protected fun input( address: Int, count: Int, - reader: IOReader, + reader: IOFormat, description: String = "", ): ModbusRegistryKey.InputRange = register(ModbusRegistryKey.InputRange(address, count, reader), description) @@ -108,7 +107,7 @@ public abstract class ModbusRegistryMap { protected fun inputByOffset( offset: Int, count: Int, - reader: IOReader, + reader: IOFormat, description: String = "", ): ModbusRegistryKey.InputRange = register(ModbusRegistryKey.InputRange(20000 + offset, count, reader), description)