Add modbus client

This commit is contained in:
Alexander Nozik 2023-06-04 21:18:42 +03:00
parent 2aa26ea802
commit 166cc03fe2
3 changed files with 149 additions and 24 deletions

View File

@ -44,25 +44,6 @@ public abstract class DeviceSpec<D : Device> {
return deviceProperty return deviceProperty
} }
// public fun <T> registerProperty(
// converter: MetaConverter<T>,
// readOnlyProperty: KProperty1<D, T>,
// descriptorBuilder: PropertyDescriptor.() -> Unit = {},
// ): DevicePropertySpec<D, T> {
// val deviceProperty = object : DevicePropertySpec<D, T> {
//
// override val descriptor: PropertyDescriptor = PropertyDescriptor(readOnlyProperty.name)
// .apply(descriptorBuilder)
//
// override val converter: MetaConverter<T> = converter
//
// override suspend fun read(device: D): T = withContext(device.coroutineContext) {
// readOnlyProperty.get(device)
// }
// }
// return registerProperty(deviceProperty)
// }
public fun <T> property( public fun <T> property(
converter: MetaConverter<T>, converter: MetaConverter<T>,
readOnlyProperty: KProperty1<D, T>, readOnlyProperty: KProperty1<D, T>,
@ -96,7 +77,7 @@ public abstract class DeviceSpec<D : Device> {
val deviceProperty = object : WritableDevicePropertySpec<D, T> { val deviceProperty = object : WritableDevicePropertySpec<D, T> {
override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply { override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply {
//TODO add type from converter //TODO add the type from converter
writable = true writable = true
}.apply(descriptorBuilder) }.apply(descriptorBuilder)

View File

@ -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<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 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 <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 {
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))
}
}
}
}
}
return image
}

View File

@ -1,7 +1,6 @@
package space.kscience.controls.modbus package space.kscience.controls.modbus
import space.kscience.dataforge.io.IOFormat import space.kscience.dataforge.io.IOFormat
import space.kscience.dataforge.io.IOReader
public sealed class ModbusRegistryKey { public sealed class ModbusRegistryKey {
@ -38,7 +37,7 @@ public sealed class ModbusRegistryKey {
public data class InputRange<T>( public data class InputRange<T>(
override val address: Int, override val address: Int,
override val count: Int, override val count: Int,
public val format: IOReader<T>, public val format: IOFormat<T>,
) : ModbusRegistryKey() { ) : ModbusRegistryKey() {
public val endAddress: Int get() = address + count public val endAddress: Int get() = address + count
@ -97,7 +96,7 @@ public abstract class ModbusRegistryMap {
protected fun <T> input( protected fun <T> input(
address: Int, address: Int,
count: Int, count: Int,
reader: IOReader<T>, reader: IOFormat<T>,
description: String = "", description: String = "",
): ModbusRegistryKey.InputRange<T> = ): ModbusRegistryKey.InputRange<T> =
register(ModbusRegistryKey.InputRange(address, count, reader), description) register(ModbusRegistryKey.InputRange(address, count, reader), description)
@ -108,7 +107,7 @@ public abstract class ModbusRegistryMap {
protected fun <T> inputByOffset( protected fun <T> inputByOffset(
offset: Int, offset: Int,
count: Int, count: Int,
reader: IOReader<T>, reader: IOFormat<T>,
description: String = "", description: String = "",
): ModbusRegistryKey.InputRange<T> = ): ModbusRegistryKey.InputRange<T> =
register(ModbusRegistryKey.InputRange(20000 + offset, count, reader), description) register(ModbusRegistryKey.InputRange(20000 + offset, count, reader), description)