Modbus-based implementation of controller

This commit is contained in:
Alexander Nozik 2023-05-21 16:53:42 +03:00
parent e7d02be849
commit b8a82feed0
2 changed files with 130 additions and 22 deletions

View File

@ -5,6 +5,10 @@ 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.SimpleRegister import com.ghgande.j2mod.modbus.procimg.SimpleRegister
import com.ghgande.j2mod.modbus.util.BitVector 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 space.kscience.controls.api.Device
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlin.properties.ReadWriteProperty import kotlin.properties.ReadWriteProperty
@ -22,9 +26,56 @@ public interface ModbusDevice : Device {
public val clientId: Int public val clientId: Int
/** /**
* The OPC-UA client initialized on first use * The modubus master connector
*/ */
public val master: AbstractModbusMaster 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 <T> ModbusRegistryKey.InputRange<T>.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 <T> ModbusRegistryKey.HoldingRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
val packet = readInputRegistersToPacket(address, count)
return format.readObject(packet)
}
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.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) 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 = public fun ModbusDevice.readInputDiscretes(ref: Int, count: Int): BitVector =
master.readInputDiscretes(clientId, ref, count) 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<InputRegister> = public fun ModbusDevice.readInputRegisters(ref: Int, count: Int): List<InputRegister> =
master.readInputRegisters(clientId, ref, count).toList() master.readInputRegisters(clientId, ref, count).toList()
@ -64,25 +122,42 @@ private fun Array<out InputRegister>.toBuffer(): ByteBuffer {
return buffer return buffer
} }
private fun Array<out InputRegister>.toPacket(): ByteReadPacket = buildPacket {
forEach { value ->
writeShort(value.toShort())
}
}
public fun ModbusDevice.readInputRegistersToBuffer(ref: Int, count: Int): ByteBuffer = public fun ModbusDevice.readInputRegistersToBuffer(ref: Int, count: Int): ByteBuffer =
master.readInputRegisters(clientId, ref, count).toBuffer() 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 = public fun ModbusDevice.readDoubleInput(ref: Int): Double =
readInputRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble() 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() readInputRegisters(ref, 1).first().toShort()
public fun ModbusDevice.readHoldingRegisters(ref: Int, count: Int): List<Register> = public fun ModbusDevice.readHoldingRegisters(ref: Int, count: Int): List<Register> =
master.readMultipleRegisters(clientId, ref, count).toList() 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 = public fun ModbusDevice.readHoldingRegistersToBuffer(ref: Int, count: Int): ByteBuffer =
master.readMultipleRegisters(clientId, ref, count).toBuffer() 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 = public fun ModbusDevice.readDoubleRegister(ref: Int): Double =
readHoldingRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble() 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() readHoldingRegisters(ref, 1).first().toShort()
public fun ModbusDevice.writeHoldingRegisters(ref: Int, values: ShortArray): Int = 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 { 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) return writeHoldingRegisters(ref, array)
} }
@ -109,17 +184,17 @@ public fun ModbusDevice.writeShortRegister(ref: Int, value: Short) {
master.writeSingleRegister(ref, SimpleRegister().apply { setValue(value) }) master.writeSingleRegister(ref, SimpleRegister().apply { setValue(value) })
} }
public fun ModbusDevice.modBusRegister( public fun ModbusDevice.modbusRegister(
ref: Int, ref: Int,
): ReadWriteProperty<ModbusDevice, Short> = object : ReadWriteProperty<ModbusDevice, Short> { ): ReadWriteProperty<ModbusDevice, Short> = object : ReadWriteProperty<ModbusDevice, Short> {
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) { override fun setValue(thisRef: ModbusDevice, property: KProperty<*>, value: Short) {
writeHoldingRegister(ref, value) writeHoldingRegister(ref, value)
} }
} }
public fun ModbusDevice.modBusDoubleRegister( public fun ModbusDevice.modbusDoubleRegister(
ref: Int, ref: Int,
): ReadWriteProperty<ModbusDevice, Double> = object : ReadWriteProperty<ModbusDevice, Double> { ): ReadWriteProperty<ModbusDevice, Double> = object : ReadWriteProperty<ModbusDevice, Double> {
override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Double = readDoubleRegister(ref) override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Double = readDoubleRegister(ref)
@ -129,18 +204,3 @@ public fun ModbusDevice.modBusDoubleRegister(
writeHoldingRegisters(ref, buffer) writeHoldingRegisters(ref, buffer)
} }
} }
//
//public inline fun <reified T> ModbusDevice.opcDouble(
//): ReadWriteProperty<Any?, Double> = ma
//
//public inline fun <reified T> ModbusDeviceBySpec<*>.opcInt(
// nodeId: NodeId,
// magAge: Double = 1.0,
//): ReadWriteProperty<Any?, Int> = opc(nodeId, MetaConverter.int, magAge)
//
//public inline fun <reified T> ModbusDeviceBySpec<*>.opcString(
// nodeId: NodeId,
// magAge: Double = 1.0,
//): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge)

View File

@ -1,5 +1,8 @@
package space.kscience.controls.modbus package space.kscience.controls.modbus
import space.kscience.dataforge.io.IOFormat
import space.kscience.dataforge.io.IOReader
public sealed class ModbusRegistryKey { public sealed class ModbusRegistryKey {
/** /**
@ -20,25 +23,70 @@ public sealed class ModbusRegistryKey {
} }
} }
/**
* Read-only binary value
*/
public class InputRegister(public val address: Int) : ModbusRegistryKey() { public class InputRegister(public val address: Int) : ModbusRegistryKey() {
init { init {
require(address in 20001..29999) { "InputRegister address must be in 20001..29999 range" } require(address in 20001..29999) { "InputRegister address must be in 20001..29999 range" }
} }
} }
public class InputRange<T>(public val address: Int, public val count: Int, public val format: IOReader<T>) {
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() { public class HoldingRegister(public val address: Int) : ModbusRegistryKey() {
init { init {
require(address in 30001..39999) { "HoldingRegister address must be in 30001..39999 range" } require(address in 30001..39999) { "HoldingRegister address must be in 30001..39999 range" }
} }
} }
public class HoldingRange<T>(public val address: Int, public val count: Int, public val format: IOFormat<T>) {
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 { public abstract class ModbusRegistryMap {
protected fun coil(address: Int): ModbusRegistryKey.Coil = ModbusRegistryKey.Coil(address) 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 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): ModbusRegistryKey.InputRegister = ModbusRegistryKey.InputRegister(address)
protected fun <T> input(address: Int, count: Int, reader: IOReader<T>): ModbusRegistryKey.InputRange<T> =
ModbusRegistryKey.InputRange(address, count, reader)
protected fun inputByOffset(offset: Int): ModbusRegistryKey.InputRegister =
ModbusRegistryKey.InputRegister(20000 + offset)
protected fun <T> inputByOffset(offset: Int, count: Int, reader: IOReader<T>): ModbusRegistryKey.InputRange<T> =
ModbusRegistryKey.InputRange(20000 + offset, count, reader)
protected fun register(address: Int): ModbusRegistryKey.HoldingRegister = ModbusRegistryKey.HoldingRegister(address) protected fun register(address: Int): ModbusRegistryKey.HoldingRegister = ModbusRegistryKey.HoldingRegister(address)
protected fun <T> register(address: Int, count: Int, format: IOFormat<T>): ModbusRegistryKey.HoldingRange<T> =
ModbusRegistryKey.HoldingRange(address, count, format)
protected fun registerByOffset(offset: Int): ModbusRegistryKey.HoldingRegister =
ModbusRegistryKey.HoldingRegister(30000 + offset)
protected fun <T> registerByOffset(offset: Int, count: Int, format: IOFormat<T>): ModbusRegistryKey.HoldingRange<T> =
ModbusRegistryKey.HoldingRange(offset + 30000, count, format)
} }