243 lines
6.8 KiB
Kotlin
243 lines
6.8 KiB
Kotlin
package space.kscience.controls.modbus
|
|
|
|
import com.ghgande.j2mod.modbus.procimg.*
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.isActive
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.io.Buffer
|
|
import space.kscience.controls.api.Device
|
|
import space.kscience.controls.ports.readShort
|
|
import space.kscience.controls.spec.*
|
|
import space.kscience.dataforge.io.Binary
|
|
|
|
|
|
public class DeviceProcessImageBuilder<D : Device> internal constructor(
|
|
private val device: D,
|
|
public val image: ProcessImageImplementation,
|
|
) {
|
|
|
|
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: MutableDevicePropertySpec<D, Boolean>,
|
|
): ObservableDigitalOut = bind(key) { coil ->
|
|
coil.addObserver { _, _ ->
|
|
device.writeAsync(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: MutableDevicePropertySpec<D, Short>,
|
|
): ObservableRegister = bind(key) { register ->
|
|
register.addObserver { _, _ ->
|
|
device.writeAsync(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 binary = Binary {
|
|
key.format.writeTo(this, value)
|
|
}
|
|
registers.forEachIndexed { index, register ->
|
|
register.setValue(binary.readShort(index * 2))
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trigger [block] if one of register changes.
|
|
*/
|
|
private fun List<ObservableRegister>.onChange(block: suspend (Buffer) -> Unit) {
|
|
var ready = false
|
|
|
|
forEach { register ->
|
|
register.addObserver { _, _ ->
|
|
ready = true
|
|
}
|
|
}
|
|
|
|
device.launch {
|
|
val builder = Buffer()
|
|
while (isActive) {
|
|
delay(1)
|
|
if (ready) {
|
|
val packet = builder.apply {
|
|
forEach { value ->
|
|
writeShort(value.toShort())
|
|
}
|
|
}
|
|
block(packet)
|
|
ready = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public fun <T> bind(key: ModbusRegistryKey.HoldingRange<T>, propertySpec: MutableDevicePropertySpec<D, T>) {
|
|
val registers = List(key.count) {
|
|
ObservableRegister()
|
|
}
|
|
|
|
registers.forEachIndexed { index, register ->
|
|
image.addRegister(key.address + index, register)
|
|
}
|
|
|
|
registers.onChange { packet ->
|
|
device.write(propertySpec, key.format.readFrom(packet))
|
|
}
|
|
|
|
device.useProperty(propertySpec) { value ->
|
|
val binary = Binary {
|
|
key.format.writeTo(this, value)
|
|
}
|
|
registers.forEachIndexed { index, observableRegister ->
|
|
observableRegister.setValue(binary.readShort(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 ->
|
|
image.addRegister(key.address + index, register)
|
|
}
|
|
|
|
registers.onChange { packet ->
|
|
device.launch {
|
|
device.action(key.format.readFrom(packet))
|
|
}
|
|
}
|
|
|
|
return registers
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Bind the device to Modbus slave (server) image.
|
|
*/
|
|
public fun <D : Device> D.bindProcessImage(
|
|
unitId: Int = 0,
|
|
openOnBind: Boolean = true,
|
|
binding: DeviceProcessImageBuilder<D>.() -> Unit,
|
|
): ProcessImage {
|
|
val image = SimpleProcessImage(unitId)
|
|
DeviceProcessImageBuilder(this, image).apply(binding)
|
|
image.setLocked(true)
|
|
if (openOnBind) {
|
|
launch {
|
|
start()
|
|
}
|
|
}
|
|
return image
|
|
} |