Modbus revision acording to modern standards
This commit is contained in:
parent
33e1aafa01
commit
20951a0b0f
@ -9,10 +9,21 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import space.kscience.controls.api.Device.Companion.DEVICE_TARGET
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.context.info
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
|
||||
/**
|
||||
* A lifecycle state of a device
|
||||
*/
|
||||
public enum class DeviceLifecycleState{
|
||||
INIT,
|
||||
OPEN,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
/**
|
||||
* General interface describing a managed Device.
|
||||
@ -79,12 +90,16 @@ public interface Device : AutoCloseable, ContextAware, CoroutineScope {
|
||||
public suspend fun open(): Unit = Unit
|
||||
|
||||
/**
|
||||
* Close and terminate the device. This function does not wait for device to be closed.
|
||||
* Close and terminate the device. This function does not wait for the device to be closed.
|
||||
*/
|
||||
override fun close() {
|
||||
logger.info { "Device $this is closed" }
|
||||
cancel("The device is closed")
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public val lifecycleState: DeviceLifecycleState
|
||||
|
||||
public companion object {
|
||||
public const val DEVICE_TARGET: String = "device"
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
package space.kscience.controls.spec
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
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.Global
|
||||
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
|
||||
|
||||
|
||||
@ -37,7 +38,7 @@ private suspend fun <D : Device, I, O> DeviceActionSpec<D, I, O>.executeWithMeta
|
||||
* A base abstractions for [Device], introducing specifications for properties
|
||||
*/
|
||||
public abstract class DeviceBase<D : Device>(
|
||||
override val context: Context = Global,
|
||||
final override val context: Context,
|
||||
override val meta: Meta = Meta.EMPTY,
|
||||
) : Device {
|
||||
|
||||
@ -58,9 +59,16 @@ public abstract class DeviceBase<D : Device>(
|
||||
get() = actions.values.map { it.descriptor }
|
||||
|
||||
override val coroutineContext: CoroutineContext by lazy {
|
||||
context.coroutineContext + SupervisorJob(context.coroutineContext[Job])
|
||||
context.newCoroutineContext(
|
||||
SupervisorJob(context.coroutineContext[Job]) +
|
||||
CoroutineName("Device $this") +
|
||||
CoroutineExceptionHandler { _, throwable ->
|
||||
logger.error(throwable) { "Exception in device $this job" }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logical state store
|
||||
*/
|
||||
@ -149,5 +157,23 @@ public abstract class DeviceBase<D : Device>(
|
||||
return spec.executeWithMeta(self, argument ?: Meta.EMPTY)
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.INIT
|
||||
protected set
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
override suspend fun open() {
|
||||
super.open()
|
||||
lifecycleState = DeviceLifecycleState.OPEN
|
||||
}
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
override fun close() {
|
||||
lifecycleState = DeviceLifecycleState.CLOSED
|
||||
super.close()
|
||||
}
|
||||
|
||||
abstract override fun toString(): String
|
||||
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
|
||||
/**
|
||||
@ -11,7 +10,7 @@ import space.kscience.dataforge.meta.Meta
|
||||
*/
|
||||
public open class DeviceBySpec<D : Device>(
|
||||
public val spec: DeviceSpec<in D>,
|
||||
context: Context = Global,
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) : DeviceBase<D>(context, meta) {
|
||||
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
||||
@ -26,4 +25,6 @@ public open class DeviceBySpec<D : Device>(
|
||||
self.onClose()
|
||||
super.close()
|
||||
}
|
||||
|
||||
override fun toString(): String = "Device(spec=$spec)"
|
||||
}
|
@ -4,6 +4,7 @@ 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 kotlinx.coroutines.launch
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.controls.spec.DevicePropertySpec
|
||||
import space.kscience.controls.spec.WritableDevicePropertySpec
|
||||
@ -11,138 +12,206 @@ import space.kscience.controls.spec.set
|
||||
import space.kscience.controls.spec.useProperty
|
||||
|
||||
|
||||
public class DeviceToModbusMapping<D : Device> private constructor(
|
||||
private val mapping: Map<DevicePropertySpec<D, *>, ModbusRegistryKey>,
|
||||
) : Map<DevicePropertySpec<D, *>, ModbusRegistryKey> by mapping {
|
||||
public class Builder<D : Device> {
|
||||
private val mapping = HashMap<DevicePropertySpec<D, *>, ModbusRegistryKey>()
|
||||
public class DeviceProcessImageBuilder<D : Device>(
|
||||
private val device: D,
|
||||
public val image: ProcessImageImplementation,
|
||||
) {
|
||||
|
||||
public fun bind(propertySpec: DevicePropertySpec<D, Boolean>, key: ModbusRegistryKey.DiscreteInput) {
|
||||
mapping[propertySpec] = key
|
||||
}
|
||||
|
||||
public fun bind(propertySpec: WritableDevicePropertySpec<D, Boolean>, key: ModbusRegistryKey.Coil) {
|
||||
mapping[propertySpec] = key
|
||||
}
|
||||
|
||||
public fun bind(propertySpec: DevicePropertySpec<D, Short>, key: ModbusRegistryKey.InputRegister) {
|
||||
mapping[propertySpec] = key
|
||||
}
|
||||
|
||||
public fun bind(propertySpec: WritableDevicePropertySpec<D, Short>, key: ModbusRegistryKey.HoldingRegister) {
|
||||
mapping[propertySpec] = key
|
||||
}
|
||||
|
||||
public fun <T> bind(propertySpec: DevicePropertySpec<D, T>, key: ModbusRegistryKey.InputRange<T>) {
|
||||
mapping[propertySpec] = key
|
||||
}
|
||||
|
||||
public fun <T> bind(propertySpec: WritableDevicePropertySpec<D, T>, key: ModbusRegistryKey.HoldingRange<T>) {
|
||||
mapping[propertySpec] = key
|
||||
}
|
||||
|
||||
public fun build(): DeviceToModbusMapping<D> = DeviceToModbusMapping(mapping)
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.Coil,
|
||||
block: D.(ObservableDigitalOut) -> Unit = {},
|
||||
): ObservableDigitalOut {
|
||||
val coil = ObservableDigitalOut()
|
||||
device.block(coil)
|
||||
image.addDigitalOut(key.address, coil)
|
||||
return coil
|
||||
}
|
||||
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.Coil,
|
||||
propertySpec: WritableDevicePropertySpec<D, Boolean>,
|
||||
): ObservableDigitalOut = bind(key) { coil ->
|
||||
coil.addObserver { _, _ ->
|
||||
device[propertySpec] = coil.isSet
|
||||
}
|
||||
device.useProperty(propertySpec) { value ->
|
||||
coil.set(value)
|
||||
}
|
||||
}
|
||||
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.DiscreteInput,
|
||||
block: D.(SimpleDigitalIn) -> Unit = {},
|
||||
): DigitalIn {
|
||||
val input = SimpleDigitalIn()
|
||||
device.block(input)
|
||||
image.addDigitalIn(key.address, input)
|
||||
return input
|
||||
}
|
||||
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.DiscreteInput,
|
||||
propertySpec: DevicePropertySpec<D, Boolean>,
|
||||
): DigitalIn = bind(key) { input ->
|
||||
device.useProperty(propertySpec) { value ->
|
||||
input.set(value)
|
||||
}
|
||||
}
|
||||
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.InputRegister,
|
||||
block: D.(SimpleInputRegister) -> Unit = {},
|
||||
): SimpleInputRegister {
|
||||
val input = SimpleInputRegister()
|
||||
device.block(input)
|
||||
image.addInputRegister(key.address, input)
|
||||
return input
|
||||
}
|
||||
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.InputRegister,
|
||||
propertySpec: DevicePropertySpec<D, Short>,
|
||||
): SimpleInputRegister = bind(key) { input ->
|
||||
device.useProperty(propertySpec) { value ->
|
||||
input.setValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.HoldingRegister,
|
||||
block: D.(ObservableRegister) -> Unit = {},
|
||||
): ObservableRegister {
|
||||
val register = ObservableRegister()
|
||||
device.block(register)
|
||||
image.addRegister(key.address, register)
|
||||
return register
|
||||
}
|
||||
|
||||
public fun bind(
|
||||
key: ModbusRegistryKey.HoldingRegister,
|
||||
propertySpec: WritableDevicePropertySpec<D, Short>,
|
||||
): ObservableRegister = bind(key) { register ->
|
||||
register.addObserver { _, _ ->
|
||||
device[propertySpec] = register.toShort()
|
||||
}
|
||||
device.useProperty(propertySpec) { value ->
|
||||
register.setValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T> bind(key: ModbusRegistryKey.InputRange<T>, propertySpec: DevicePropertySpec<D, T>) {
|
||||
val registers = List(key.count) {
|
||||
SimpleInputRegister()
|
||||
}
|
||||
|
||||
registers.forEachIndexed { index, register ->
|
||||
image.addInputRegister(key.address + index, register)
|
||||
}
|
||||
|
||||
device.useProperty(propertySpec) { value ->
|
||||
val packet = buildPacket {
|
||||
key.format.writeObject(this, value)
|
||||
}.readByteBuffer()
|
||||
registers.forEachIndexed { index, register ->
|
||||
register.setValue(packet.getShort(index * 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T> bind(key: ModbusRegistryKey.HoldingRange<T>, propertySpec: WritableDevicePropertySpec<D, T>) {
|
||||
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)
|
||||
}
|
||||
|
||||
device.useProperty(propertySpec) { value ->
|
||||
val packet = buildPacket {
|
||||
key.format.writeObject(this, value)
|
||||
}.readByteBuffer()
|
||||
registers.forEachIndexed { index, observableRegister ->
|
||||
observableRegister.setValue(packet.getShort(index * 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun bindAction(
|
||||
key: ModbusRegistryKey.Coil,
|
||||
action: suspend D.(Boolean) -> Unit,
|
||||
): ObservableDigitalOut {
|
||||
val coil = ObservableDigitalOut()
|
||||
coil.addObserver { _, _ ->
|
||||
device.launch {
|
||||
device.action(coil.isSet)
|
||||
}
|
||||
}
|
||||
image.addDigitalOut(key.address, coil)
|
||||
return coil
|
||||
}
|
||||
|
||||
public fun bindAction(
|
||||
key: ModbusRegistryKey.HoldingRegister,
|
||||
action: suspend D.(Short) -> Unit,
|
||||
): ObservableRegister {
|
||||
val register = ObservableRegister()
|
||||
register.addObserver { _, _ ->
|
||||
|
||||
with(device) {
|
||||
launch {
|
||||
action(register.toShort())
|
||||
}
|
||||
}
|
||||
}
|
||||
image.addRegister(key.address, register)
|
||||
return register
|
||||
}
|
||||
|
||||
public fun <T> bindAction(
|
||||
key: ModbusRegistryKey.HoldingRange<T>,
|
||||
action: suspend D.(T) -> Unit,
|
||||
): List<ObservableRegister> {
|
||||
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)
|
||||
}
|
||||
|
||||
return registers
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public inline fun <D: Device> DeviceToModbusMapping(block: DeviceToModbusMapping.Builder<D>.()->Unit): DeviceToModbusMapping<D> =
|
||||
DeviceToModbusMapping.Builder<D>().apply(block).build()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <D : Device> D.toProcessImage(mapping: DeviceToModbusMapping<D>): ProcessImage {
|
||||
public fun <D : Device> D.bindProcessImage(
|
||||
openOnBind: Boolean = true,
|
||||
binding: DeviceProcessImageBuilder<D>.() -> Unit,
|
||||
): ProcessImage {
|
||||
val image = SimpleProcessImage()
|
||||
mapping.forEach { (spec, key) ->
|
||||
when (key) {
|
||||
is ModbusRegistryKey.Coil -> {
|
||||
spec as WritableDevicePropertySpec<D, Boolean>
|
||||
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<D, Boolean>
|
||||
val input = SimpleDigitalIn()
|
||||
image.setDigitalIn(key.address, input)
|
||||
useProperty(spec) { value ->
|
||||
input.set(value)
|
||||
}
|
||||
}
|
||||
|
||||
is ModbusRegistryKey.HoldingRegister -> {
|
||||
spec as WritableDevicePropertySpec<D, Short>
|
||||
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<D, Short>
|
||||
val input = SimpleInputRegister()
|
||||
image.setRegister(key.address, input)
|
||||
useProperty(spec) { value ->
|
||||
input.setValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
is ModbusRegistryKey.HoldingRange<*> -> {
|
||||
spec as WritableDevicePropertySpec<D, Any?>
|
||||
key as ModbusRegistryKey.HoldingRange<Any?>
|
||||
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<D, Any?>
|
||||
key as ModbusRegistryKey.InputRange<Any?>
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
DeviceProcessImageBuilder(this, image).apply(binding)
|
||||
if (openOnBind) {
|
||||
launch {
|
||||
open()
|
||||
}
|
||||
}
|
||||
return image
|
||||
}
|
||||
|
||||
public inline fun <D : Device> D.toProcessImage(block: DeviceToModbusMapping.Builder<D>.()->Unit): ProcessImage =
|
||||
toProcessImage(DeviceToModbusMapping(block))
|
@ -3,7 +3,7 @@ package space.kscience.controls.modbus
|
||||
import com.ghgande.j2mod.modbus.facade.AbstractModbusMaster
|
||||
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.procimg.SimpleInputRegister
|
||||
import com.ghgande.j2mod.modbus.util.BitVector
|
||||
import io.ktor.utils.io.core.ByteReadPacket
|
||||
import io.ktor.utils.io.core.buildPacket
|
||||
@ -73,7 +73,7 @@ public interface ModbusDevice : Device {
|
||||
val buffer = buildPacket {
|
||||
format.writeObject(this, value)
|
||||
}.readByteBuffer()
|
||||
writeHoldingRegisters(address,buffer)
|
||||
writeHoldingRegisters(address, buffer)
|
||||
}
|
||||
|
||||
}
|
||||
@ -81,36 +81,36 @@ public interface ModbusDevice : Device {
|
||||
/**
|
||||
* Read multiple sequential modbus coils (bit-values)
|
||||
*/
|
||||
public fun ModbusDevice.readCoils(ref: Int, count: Int): BitVector =
|
||||
master.readCoils(clientId, ref, count)
|
||||
public fun ModbusDevice.readCoils(address: Int, count: Int): BitVector =
|
||||
master.readCoils(clientId, address, count)
|
||||
|
||||
public fun ModbusDevice.readCoil(ref: Int): Boolean =
|
||||
master.readCoils(clientId, ref, 1).getBit(0)
|
||||
public fun ModbusDevice.readCoil(address: Int): Boolean =
|
||||
master.readCoils(clientId, address, 1).getBit(0)
|
||||
|
||||
public fun ModbusDevice.writeCoils(ref: Int, values: BooleanArray) {
|
||||
public fun ModbusDevice.writeCoils(address: Int, values: BooleanArray) {
|
||||
val bitVector = BitVector(values.size)
|
||||
values.forEachIndexed { index, value ->
|
||||
bitVector.setBit(index, value)
|
||||
}
|
||||
master.writeMultipleCoils(clientId, ref, bitVector)
|
||||
master.writeMultipleCoils(clientId, address, bitVector)
|
||||
}
|
||||
|
||||
public fun ModbusDevice.writeCoil(ref: Int, value: Boolean) {
|
||||
master.writeCoil(clientId, ref, value)
|
||||
public fun ModbusDevice.writeCoil(address: Int, value: Boolean) {
|
||||
master.writeCoil(clientId, address, 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.readInputDiscretes(address: Int, count: Int): BitVector =
|
||||
master.readInputDiscretes(clientId, address, count)
|
||||
|
||||
public fun ModbusDevice.readInputDiscrete(ref: Int): Boolean =
|
||||
master.readInputDiscretes(clientId, ref, 1).getBit(0)
|
||||
public fun ModbusDevice.readInputDiscrete(address: Int): Boolean =
|
||||
master.readInputDiscretes(clientId, address, 1).getBit(0)
|
||||
|
||||
public fun ModbusDevice.readInputRegisters(ref: Int, count: Int): List<InputRegister> =
|
||||
master.readInputRegisters(clientId, ref, count).toList()
|
||||
public fun ModbusDevice.readInputRegisters(address: Int, count: Int): List<InputRegister> =
|
||||
master.readInputRegisters(clientId, address, count).toList()
|
||||
|
||||
private fun Array<out InputRegister>.toBuffer(): ByteBuffer {
|
||||
val buffer: ByteBuffer = ByteBuffer.allocate(size * 2)
|
||||
@ -128,79 +128,82 @@ private fun Array<out InputRegister>.toPacket(): ByteReadPacket = buildPacket {
|
||||
}
|
||||
}
|
||||
|
||||
public fun ModbusDevice.readInputRegistersToBuffer(ref: Int, count: Int): ByteBuffer =
|
||||
master.readInputRegisters(clientId, ref, count).toBuffer()
|
||||
public fun ModbusDevice.readInputRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
||||
master.readInputRegisters(clientId, address, count).toBuffer()
|
||||
|
||||
public fun ModbusDevice.readInputRegistersToPacket(ref: Int, count: Int): ByteReadPacket =
|
||||
master.readInputRegisters(clientId, ref, count).toPacket()
|
||||
public fun ModbusDevice.readInputRegistersToPacket(address: Int, count: Int): ByteReadPacket =
|
||||
master.readInputRegisters(clientId, address, count).toPacket()
|
||||
|
||||
public fun ModbusDevice.readDoubleInput(ref: Int): Double =
|
||||
readInputRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble()
|
||||
public fun ModbusDevice.readDoubleInput(address: Int): Double =
|
||||
readInputRegistersToBuffer(address, Double.SIZE_BYTES).getDouble()
|
||||
|
||||
public fun ModbusDevice.readInputRegister(ref: Int): Short =
|
||||
readInputRegisters(ref, 1).first().toShort()
|
||||
public fun ModbusDevice.readInputRegister(address: Int): Short =
|
||||
readInputRegisters(address, 1).first().toShort()
|
||||
|
||||
public fun ModbusDevice.readHoldingRegisters(ref: Int, count: Int): List<Register> =
|
||||
master.readMultipleRegisters(clientId, ref, count).toList()
|
||||
public fun ModbusDevice.readHoldingRegisters(address: Int, count: Int): List<Register> =
|
||||
master.readMultipleRegisters(clientId, address, count).toList()
|
||||
|
||||
/**
|
||||
* Read a number of registers to a [ByteBuffer]
|
||||
* @param ref address of a register
|
||||
* @param 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.readHoldingRegistersToBuffer(address: Int, count: Int): ByteBuffer =
|
||||
master.readMultipleRegisters(clientId, address, count).toBuffer()
|
||||
|
||||
public fun ModbusDevice.readHoldingRegistersToPacket(ref: Int, count: Int): ByteReadPacket =
|
||||
master.readMultipleRegisters(clientId, ref, count).toPacket()
|
||||
public fun ModbusDevice.readHoldingRegistersToPacket(address: Int, count: Int): ByteReadPacket =
|
||||
master.readMultipleRegisters(clientId, address, count).toPacket()
|
||||
|
||||
public fun ModbusDevice.readDoubleRegister(ref: Int): Double =
|
||||
readHoldingRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble()
|
||||
public fun ModbusDevice.readDoubleRegister(address: Int): Double =
|
||||
readHoldingRegistersToBuffer(address, Double.SIZE_BYTES).getDouble()
|
||||
|
||||
public fun ModbusDevice.readHoldingRegister(ref: Int): Short =
|
||||
readHoldingRegisters(ref, 1).first().toShort()
|
||||
public fun ModbusDevice.readHoldingRegister(address: Int): Short =
|
||||
readHoldingRegisters(address, 1).first().toShort()
|
||||
|
||||
public fun ModbusDevice.writeHoldingRegisters(ref: Int, values: ShortArray): Int =
|
||||
public fun ModbusDevice.writeHoldingRegisters(address: Int, values: ShortArray): Int =
|
||||
master.writeMultipleRegisters(
|
||||
clientId,
|
||||
ref,
|
||||
Array<Register>(values.size) { SimpleRegister().apply { setValue(values[it]) } }
|
||||
address,
|
||||
Array<Register>(values.size) { SimpleInputRegister(values[it].toInt()) }
|
||||
)
|
||||
|
||||
public fun ModbusDevice.writeHoldingRegister(ref: Int, value: Short): Int =
|
||||
public fun ModbusDevice.writeHoldingRegister(address: Int, value: Short): Int =
|
||||
master.writeSingleRegister(
|
||||
clientId,
|
||||
ref,
|
||||
SimpleRegister().apply { setValue(value) }
|
||||
address,
|
||||
SimpleInputRegister(value.toInt())
|
||||
)
|
||||
|
||||
public fun ModbusDevice.writeHoldingRegisters(ref: Int, buffer: ByteBuffer): Int {
|
||||
public fun ModbusDevice.writeHoldingRegister(key: ModbusRegistryKey.HoldingRegister, value: Short): Int =
|
||||
writeHoldingRegister(key.address, value)
|
||||
|
||||
public fun ModbusDevice.writeHoldingRegisters(address: Int, buffer: ByteBuffer): Int {
|
||||
val array: ShortArray = ShortArray(buffer.limit().floorDiv(2)) { buffer.getShort(it * 2) }
|
||||
|
||||
return writeHoldingRegisters(ref, array)
|
||||
return writeHoldingRegisters(address, array)
|
||||
}
|
||||
|
||||
public fun ModbusDevice.writeShortRegister(ref: Int, value: Short) {
|
||||
master.writeSingleRegister(ref, SimpleRegister().apply { setValue(value) })
|
||||
public fun ModbusDevice.writeShortRegister(address: Int, value: Short) {
|
||||
master.writeSingleRegister(address, SimpleInputRegister(value.toInt()))
|
||||
}
|
||||
|
||||
public fun ModbusDevice.modbusRegister(
|
||||
ref: Int,
|
||||
address: Int,
|
||||
): ReadWriteProperty<ModbusDevice, Short> = object : ReadWriteProperty<ModbusDevice, Short> {
|
||||
override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Short = readHoldingRegister(ref)
|
||||
override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Short = readHoldingRegister(address)
|
||||
|
||||
override fun setValue(thisRef: ModbusDevice, property: KProperty<*>, value: Short) {
|
||||
writeHoldingRegister(ref, value)
|
||||
writeHoldingRegister(address, value)
|
||||
}
|
||||
}
|
||||
|
||||
public fun ModbusDevice.modbusDoubleRegister(
|
||||
ref: Int,
|
||||
address: Int,
|
||||
): 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(address)
|
||||
|
||||
override fun setValue(thisRef: ModbusDevice, property: KProperty<*>, value: Double) {
|
||||
val buffer = ByteBuffer.allocate(Double.SIZE_BYTES).apply { putDouble(value) }
|
||||
writeHoldingRegisters(ref, buffer)
|
||||
writeHoldingRegisters(address, buffer)
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,21 @@ public open class ModbusDeviceBySpec<D: Device>(
|
||||
spec: DeviceSpec<D>,
|
||||
override val clientId: Int,
|
||||
override val master: AbstractModbusMaster,
|
||||
private val disposeMasterOnClose: Boolean = true,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) : ModbusDevice, DeviceBySpec<D>(spec, context, meta)
|
||||
) : ModbusDevice, DeviceBySpec<D>(spec, context, meta){
|
||||
override suspend fun open() {
|
||||
master.connect()
|
||||
super<ModbusDevice>.open()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if(disposeMasterOnClose){
|
||||
master.disconnect()
|
||||
}
|
||||
super<ModbusDevice>.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ModbusHub(
|
||||
|
@ -7,63 +7,48 @@ public sealed class ModbusRegistryKey {
|
||||
public abstract val address: Int
|
||||
public open val count: Int = 1
|
||||
|
||||
|
||||
/**
|
||||
* Read-only boolean value
|
||||
*/
|
||||
public data class Coil(override val address: Int) : ModbusRegistryKey() {
|
||||
init {
|
||||
require(address in 1..9999) { "Coil address must be in 1..9999 range" }
|
||||
}
|
||||
}
|
||||
public data class Coil(override val address: Int) : ModbusRegistryKey()
|
||||
|
||||
/**
|
||||
* Read-write boolean value
|
||||
*/
|
||||
public data class DiscreteInput(override val address: Int) : ModbusRegistryKey() {
|
||||
init {
|
||||
require(address in 10001..19999) { "DiscreteInput address must be in 10001..19999 range" }
|
||||
}
|
||||
}
|
||||
public data class DiscreteInput(override val address: Int) : ModbusRegistryKey()
|
||||
|
||||
/**
|
||||
* Read-only binary value
|
||||
*/
|
||||
public data class InputRegister(override val address: Int) : ModbusRegistryKey() {
|
||||
init {
|
||||
require(address in 20001..29999) { "InputRegister address must be in 20001..29999 range" }
|
||||
}
|
||||
public open class InputRegister(override val address: Int) : ModbusRegistryKey() {
|
||||
override fun toString(): String = "InputRegister(address=$address)"
|
||||
}
|
||||
|
||||
public data class InputRange<T>(
|
||||
override val address: Int,
|
||||
public class InputRange<T>(
|
||||
address: Int,
|
||||
override val count: Int,
|
||||
public val format: IOFormat<T>,
|
||||
) : ModbusRegistryKey() {
|
||||
) : InputRegister(address) {
|
||||
public val endAddress: Int get() = address + count
|
||||
override fun toString(): String = "InputRange(count=$count, format=$format)"
|
||||
|
||||
|
||||
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 data class HoldingRegister(override val address: Int) : ModbusRegistryKey() {
|
||||
init {
|
||||
require(address in 30001..39999) { "HoldingRegister address must be in 30001..39999 range" }
|
||||
}
|
||||
public open class HoldingRegister(override val address: Int) : ModbusRegistryKey() {
|
||||
override fun toString(): String = "HoldingRegister(address=$address)"
|
||||
}
|
||||
|
||||
public data class HoldingRange<T>(
|
||||
override val address: Int,
|
||||
public class HoldingRange<T>(
|
||||
address: Int,
|
||||
override val count: Int,
|
||||
public val format: IOFormat<T>,
|
||||
) : ModbusRegistryKey() {
|
||||
) : HoldingRegister(address) {
|
||||
public val endAddress: Int get() = address + count
|
||||
override fun toString(): String = "HoldingRange(count=$count, format=$format)"
|
||||
|
||||
|
||||
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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,15 +66,10 @@ public abstract class ModbusRegistryMap {
|
||||
protected fun coil(address: Int, description: String = ""): ModbusRegistryKey.Coil =
|
||||
register(ModbusRegistryKey.Coil(address), description)
|
||||
|
||||
protected fun coilByOffset(offset: Int, description: String = ""): ModbusRegistryKey.Coil =
|
||||
register(ModbusRegistryKey.Coil(offset), description)
|
||||
|
||||
protected fun discrete(address: Int, description: String = ""): ModbusRegistryKey.DiscreteInput =
|
||||
register(ModbusRegistryKey.DiscreteInput(address), description)
|
||||
|
||||
protected fun discreteByOffset(offset: Int, description: String = ""): ModbusRegistryKey.DiscreteInput =
|
||||
register(ModbusRegistryKey.DiscreteInput(10000 + offset), description)
|
||||
|
||||
protected fun input(address: Int, description: String = ""): ModbusRegistryKey.InputRegister =
|
||||
register(ModbusRegistryKey.InputRegister(address), description)
|
||||
|
||||
@ -101,17 +81,6 @@ public abstract class ModbusRegistryMap {
|
||||
): ModbusRegistryKey.InputRange<T> =
|
||||
register(ModbusRegistryKey.InputRange(address, count, reader), description)
|
||||
|
||||
protected fun inputByOffset(offset: Int, description: String = ""): ModbusRegistryKey.InputRegister =
|
||||
register(ModbusRegistryKey.InputRegister(20000 + offset), description)
|
||||
|
||||
protected fun <T> inputByOffset(
|
||||
offset: Int,
|
||||
count: Int,
|
||||
reader: IOFormat<T>,
|
||||
description: String = "",
|
||||
): ModbusRegistryKey.InputRange<T> =
|
||||
register(ModbusRegistryKey.InputRange(20000 + offset, count, reader), description)
|
||||
|
||||
protected fun register(address: Int, description: String = ""): ModbusRegistryKey.HoldingRegister =
|
||||
register(ModbusRegistryKey.HoldingRegister(address), description)
|
||||
|
||||
@ -123,40 +92,70 @@ public abstract class ModbusRegistryMap {
|
||||
): ModbusRegistryKey.HoldingRange<T> =
|
||||
register(ModbusRegistryKey.HoldingRange(address, count, format), description)
|
||||
|
||||
protected fun registerByOffset(offset: Int, description: String = ""): ModbusRegistryKey.HoldingRegister =
|
||||
register(ModbusRegistryKey.HoldingRegister(30000 + offset), description)
|
||||
|
||||
protected fun <T> registerByOffset(
|
||||
offset: Int,
|
||||
count: Int,
|
||||
format: IOFormat<T>,
|
||||
description: String = "",
|
||||
): ModbusRegistryKey.HoldingRange<T> =
|
||||
register(ModbusRegistryKey.HoldingRange(offset + 30000, count, format), description)
|
||||
|
||||
public companion object {
|
||||
public fun validate(map: ModbusRegistryMap) {
|
||||
map.entries.keys.sortedBy { it.address }.zipWithNext().forEach { (l, r) ->
|
||||
if (l.address + l.count > r.address) error("Key $l overlaps with key $r")
|
||||
var lastCoil: ModbusRegistryKey.Coil? = null
|
||||
var lastDiscreteInput: ModbusRegistryKey.DiscreteInput? = null
|
||||
var lastInput: ModbusRegistryKey.InputRegister? = null
|
||||
var lastRegister: ModbusRegistryKey.HoldingRegister? = null
|
||||
map.entries.keys.sortedBy { it.address }.forEach { key ->
|
||||
when (key) {
|
||||
is ModbusRegistryKey.Coil -> if (lastCoil?.let { key.address >= it.address + it.count } != false) {
|
||||
lastCoil = key
|
||||
} else {
|
||||
error("Key $lastCoil overlaps with key $key")
|
||||
}
|
||||
|
||||
is ModbusRegistryKey.DiscreteInput -> if (lastDiscreteInput?.let { key.address >= it.address + it.count } != false) {
|
||||
lastDiscreteInput = key
|
||||
} else {
|
||||
error("Key $lastDiscreteInput overlaps with key $key")
|
||||
}
|
||||
|
||||
is ModbusRegistryKey.InputRegister -> if (lastInput?.let { key.address >= it.address + it.count } != false) {
|
||||
lastInput = key
|
||||
} else {
|
||||
error("Key $lastInput overlaps with key $key")
|
||||
}
|
||||
|
||||
is ModbusRegistryKey.HoldingRegister -> if (lastRegister?.let { key.address >= it.address + it.count } != false) {
|
||||
lastRegister = key
|
||||
} else {
|
||||
error("Key $lastRegister overlaps with key $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ModbusRegistryKey.sectionNumber
|
||||
get() = when (this) {
|
||||
is ModbusRegistryKey.Coil -> 1
|
||||
is ModbusRegistryKey.DiscreteInput -> 2
|
||||
is ModbusRegistryKey.HoldingRegister -> 4
|
||||
is ModbusRegistryKey.InputRegister -> 3
|
||||
}
|
||||
|
||||
public fun print(map: ModbusRegistryMap, to: Appendable = System.out) {
|
||||
validate(map)
|
||||
map.entries.entries.sortedBy { it.key.address }.forEach { (key, description) ->
|
||||
val typeString = when (key) {
|
||||
is ModbusRegistryKey.Coil -> "Coil"
|
||||
is ModbusRegistryKey.DiscreteInput -> "Discrete"
|
||||
is ModbusRegistryKey.HoldingRange<*>, is ModbusRegistryKey.HoldingRegister -> "Register"
|
||||
is ModbusRegistryKey.InputRange<*>, is ModbusRegistryKey.InputRegister -> "Input"
|
||||
map.entries.entries
|
||||
.sortedWith(
|
||||
Comparator.comparingInt<Map.Entry<ModbusRegistryKey, String>?> { it.key.sectionNumber }
|
||||
.thenComparingInt { it.key.address }
|
||||
)
|
||||
.forEach { (key, description) ->
|
||||
val typeString = when (key) {
|
||||
is ModbusRegistryKey.Coil -> "Coil"
|
||||
is ModbusRegistryKey.DiscreteInput -> "Discrete"
|
||||
is ModbusRegistryKey.HoldingRegister -> "Register"
|
||||
is ModbusRegistryKey.InputRegister -> "Input"
|
||||
}
|
||||
val rangeString = if (key.count == 1) {
|
||||
key.address.toString()
|
||||
} else {
|
||||
"${key.address} - ${key.address + key.count}"
|
||||
}
|
||||
to.appendLine("${typeString}\t$rangeString\t$description")
|
||||
}
|
||||
val rangeString = if (key.count == 1) {
|
||||
key.address.toString()
|
||||
} else {
|
||||
"${key.address} - ${key.address + key.count}"
|
||||
}
|
||||
to.appendLine("${typeString}\t$rangeString\t$description")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user