From b8a82feed09102033d1b827ddf5c02f695459758 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 21 May 2023 16:53:42 +0300 Subject: [PATCH] Modbus-based implementation of controller --- .../kscience/controls/modbus/ModbusDevice.kt | 104 ++++++++++++++---- .../controls/modbus/ModbusRegistryMap.kt | 48 ++++++++ 2 files changed, 130 insertions(+), 22 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 04bb6a0..1c442aa 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 @@ -5,6 +5,10 @@ import com.ghgande.j2mod.modbus.procimg.InputRegister import com.ghgande.j2mod.modbus.procimg.Register import com.ghgande.j2mod.modbus.procimg.SimpleRegister 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 space.kscience.controls.api.Device import java.nio.ByteBuffer import kotlin.properties.ReadWriteProperty @@ -22,9 +26,56 @@ public interface ModbusDevice : Device { public val clientId: Int /** - * The OPC-UA client initialized on first use + * The modubus master connector */ public val master: AbstractModbusMaster + + public operator fun ModbusRegistryKey.Coil.getValue(thisRef: Any?, property: KProperty<*>): Boolean = + readCoil(address) + + public operator fun ModbusRegistryKey.Coil.setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { + writeCoil(address, value) + } + + public operator fun ModbusRegistryKey.DiscreteInput.getValue(thisRef: Any?, property: KProperty<*>): Boolean = + readInputDiscrete(address) + + public operator fun ModbusRegistryKey.InputRegister.getValue(thisRef: Any?, property: KProperty<*>): Short = + readInputRegister(address) + + public operator fun ModbusRegistryKey.InputRange.getValue(thisRef: Any?, property: KProperty<*>): T { + val packet = readInputRegistersToPacket(address, count) + return format.readObject(packet) + } + + + public operator fun ModbusRegistryKey.HoldingRegister.getValue(thisRef: Any?, property: KProperty<*>): Short = + readHoldingRegister(address) + + public operator fun ModbusRegistryKey.HoldingRegister.setValue( + thisRef: Any?, + property: KProperty<*>, + value: Short, + ) { + writeHoldingRegister(address, value) + } + + public operator fun ModbusRegistryKey.HoldingRange.getValue(thisRef: Any?, property: KProperty<*>): T { + val packet = readInputRegistersToPacket(address, count) + return format.readObject(packet) + } + + public operator fun ModbusRegistryKey.HoldingRange.setValue( + thisRef: Any?, + property: KProperty<*>, + value: T, + ) { + val buffer = buildPacket { + format.writeObject(this, value) + }.readByteBuffer() + writeHoldingRegisters(address,buffer) + } + } /** @@ -48,9 +99,16 @@ public fun ModbusDevice.writeCoil(ref: Int, value: Boolean) { master.writeCoil(clientId, ref, value) } +public fun ModbusDevice.writeCoil(key: ModbusRegistryKey.Coil, value: Boolean) { + master.writeCoil(clientId, key.address, value) +} + public fun ModbusDevice.readInputDiscretes(ref: Int, count: Int): BitVector = master.readInputDiscretes(clientId, ref, count) +public fun ModbusDevice.readInputDiscrete(ref: Int): Boolean = + master.readInputDiscretes(clientId, ref, 1).getBit(0) + public fun ModbusDevice.readInputRegisters(ref: Int, count: Int): List = master.readInputRegisters(clientId, ref, count).toList() @@ -64,25 +122,42 @@ private fun Array.toBuffer(): ByteBuffer { return buffer } +private fun Array.toPacket(): ByteReadPacket = buildPacket { + forEach { value -> + writeShort(value.toShort()) + } +} + public fun ModbusDevice.readInputRegistersToBuffer(ref: Int, count: Int): ByteBuffer = master.readInputRegisters(clientId, ref, count).toBuffer() +public fun ModbusDevice.readInputRegistersToPacket(ref: Int, count: Int): ByteReadPacket = + master.readInputRegisters(clientId, ref, count).toPacket() + public fun ModbusDevice.readDoubleInput(ref: Int): Double = readInputRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble() -public fun ModbusDevice.readShortInput(ref: Int): Short = +public fun ModbusDevice.readInputRegister(ref: Int): Short = readInputRegisters(ref, 1).first().toShort() public fun ModbusDevice.readHoldingRegisters(ref: Int, count: Int): List = master.readMultipleRegisters(clientId, ref, count).toList() +/** + * Read a number of registers to a [ByteBuffer] + * @param ref address of a register + * @param count number of 2-bytes registers to read. Buffer size is 2*[count] + */ public fun ModbusDevice.readHoldingRegistersToBuffer(ref: Int, count: Int): ByteBuffer = master.readMultipleRegisters(clientId, ref, count).toBuffer() +public fun ModbusDevice.readHoldingRegistersToPacket(ref: Int, count: Int): ByteReadPacket = + master.readMultipleRegisters(clientId, ref, count).toPacket() + public fun ModbusDevice.readDoubleRegister(ref: Int): Double = readHoldingRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble() -public fun ModbusDevice.readShortRegister(ref: Int): Short = +public fun ModbusDevice.readHoldingRegister(ref: Int): Short = readHoldingRegisters(ref, 1).first().toShort() public fun ModbusDevice.writeHoldingRegisters(ref: Int, values: ShortArray): Int = @@ -100,7 +175,7 @@ public fun ModbusDevice.writeHoldingRegister(ref: Int, value: Short): Int = ) public fun ModbusDevice.writeHoldingRegisters(ref: Int, buffer: ByteBuffer): Int { - val array = ShortArray(buffer.limit().floorDiv(2)) { buffer.getShort(it * 2) } + val array: ShortArray = ShortArray(buffer.limit().floorDiv(2)) { buffer.getShort(it * 2) } return writeHoldingRegisters(ref, array) } @@ -109,17 +184,17 @@ public fun ModbusDevice.writeShortRegister(ref: Int, value: Short) { master.writeSingleRegister(ref, SimpleRegister().apply { setValue(value) }) } -public fun ModbusDevice.modBusRegister( +public fun ModbusDevice.modbusRegister( ref: Int, ): ReadWriteProperty = object : ReadWriteProperty { - override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Short = readShortRegister(ref) + override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Short = readHoldingRegister(ref) override fun setValue(thisRef: ModbusDevice, property: KProperty<*>, value: Short) { writeHoldingRegister(ref, value) } } -public fun ModbusDevice.modBusDoubleRegister( +public fun ModbusDevice.modbusDoubleRegister( ref: Int, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Double = readDoubleRegister(ref) @@ -129,18 +204,3 @@ public fun ModbusDevice.modBusDoubleRegister( writeHoldingRegisters(ref, buffer) } } - - -// -//public inline fun ModbusDevice.opcDouble( -//): ReadWriteProperty = ma -// -//public inline fun ModbusDeviceBySpec<*>.opcInt( -// nodeId: NodeId, -// magAge: Double = 1.0, -//): ReadWriteProperty = opc(nodeId, MetaConverter.int, magAge) -// -//public inline fun ModbusDeviceBySpec<*>.opcString( -// nodeId: NodeId, -// magAge: Double = 1.0, -//): ReadWriteProperty = opc(nodeId, MetaConverter.string, magAge) 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 e365a88..ee7b8c3 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,5 +1,8 @@ package space.kscience.controls.modbus +import space.kscience.dataforge.io.IOFormat +import space.kscience.dataforge.io.IOReader + public sealed class ModbusRegistryKey { /** @@ -20,25 +23,70 @@ public sealed class ModbusRegistryKey { } } + /** + * Read-only binary value + */ public class InputRegister(public val address: Int) : ModbusRegistryKey() { init { require(address in 20001..29999) { "InputRegister address must be in 20001..29999 range" } } } + public class InputRange(public val address: Int, public val count: Int, public val format: IOReader) { + public val endAddress: Int get() = address + count + + init { + require(address in 20001..29999) { "InputRange begin address is $address, but must be in 20001..29999 range" } + require(endAddress in 20001..29999) { "InputRange end address is ${endAddress}, but must be in 20001..29999 range" } + } + } + public class HoldingRegister(public val address: Int) : ModbusRegistryKey() { init { require(address in 30001..39999) { "HoldingRegister address must be in 30001..39999 range" } } } + + public class HoldingRange(public val address: Int, public val count: Int, public val format: IOFormat) { + public val endAddress: Int get() = address + count + + init { + require(address in 30001..39999) { "HoldingRange begin address is $address, but must be in 30001..39999 range" } + require(endAddress in 30001..39999) { "HoldingRange end address is ${endAddress}, but must be in 30001..39999 range" } + } + } } public abstract class ModbusRegistryMap { protected fun coil(address: Int): ModbusRegistryKey.Coil = ModbusRegistryKey.Coil(address) + protected fun coilByOffset(offset: Int): ModbusRegistryKey.Coil = ModbusRegistryKey.Coil(offset) + protected fun discrete(address: Int): ModbusRegistryKey.DiscreteInput = ModbusRegistryKey.DiscreteInput(address) + protected fun discreteByOffset(offset: Int): ModbusRegistryKey.DiscreteInput = + ModbusRegistryKey.DiscreteInput(10000 + offset) + protected fun input(address: Int): ModbusRegistryKey.InputRegister = ModbusRegistryKey.InputRegister(address) + protected fun input(address: Int, count: Int, reader: IOReader): ModbusRegistryKey.InputRange = + ModbusRegistryKey.InputRange(address, count, reader) + + + protected fun inputByOffset(offset: Int): ModbusRegistryKey.InputRegister = + ModbusRegistryKey.InputRegister(20000 + offset) + + protected fun inputByOffset(offset: Int, count: Int, reader: IOReader): ModbusRegistryKey.InputRange = + ModbusRegistryKey.InputRange(20000 + offset, count, reader) + protected fun register(address: Int): ModbusRegistryKey.HoldingRegister = ModbusRegistryKey.HoldingRegister(address) + + protected fun register(address: Int, count: Int, format: IOFormat): ModbusRegistryKey.HoldingRange = + ModbusRegistryKey.HoldingRange(address, count, format) + + protected fun registerByOffset(offset: Int): ModbusRegistryKey.HoldingRegister = + ModbusRegistryKey.HoldingRegister(30000 + offset) + + protected fun registerByOffset(offset: Int, count: Int, format: IOFormat): ModbusRegistryKey.HoldingRange = + ModbusRegistryKey.HoldingRange(offset + 30000, count, format) }