Fixex in modbus and write-protection for same meta

This commit is contained in:
Alexander Nozik 2023-10-02 21:24:01 +03:00
parent 34e7dd2c6d
commit efe9a2e842
5 changed files with 65 additions and 27 deletions

View File

@ -11,6 +11,8 @@
### Removed ### Removed
### Fixed ### Fixed
- Property writing does not trigger change if logical state already is the same as value to be set.
- Modbus-slave triggers only once for multi-register write.
### Security ### Security

View File

@ -13,7 +13,7 @@ val xodusVersion by extra("2.0.1")
allprojects { allprojects {
group = "space.kscience" group = "space.kscience"
version = "0.2.2-dev-1" version = "0.2.2-dev-2"
repositories{ repositories{
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
} }

View File

@ -7,18 +7,24 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import space.kscience.controls.api.* import space.kscience.controls.api.*
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.debug
import space.kscience.dataforge.context.error import space.kscience.dataforge.context.error
import space.kscience.dataforge.context.logger import space.kscience.dataforge.context.logger
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/**
* Write a meta [item] to [device]
*/
@OptIn(InternalDeviceAPI::class) @OptIn(InternalDeviceAPI::class)
private suspend fun <D : Device, T> WritableDevicePropertySpec<D, T>.writeMeta(device: D, item: Meta) { private suspend fun <D : Device, T> WritableDevicePropertySpec<D, T>.writeMeta(device: D, item: Meta) {
write(device, converter.metaToObject(item) ?: error("Meta $item could not be read with $converter")) write(device, converter.metaToObject(item) ?: error("Meta $item could not be read with $converter"))
} }
/**
* Read Meta item from the [device]
*/
@OptIn(InternalDeviceAPI::class) @OptIn(InternalDeviceAPI::class)
private suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta? = private suspend fun <D : Device, T> DevicePropertySpec<D, T>.readMeta(device: D): Meta? =
read(device)?.let(converter::objectToMeta) read(device)?.let(converter::objectToMeta)
@ -135,9 +141,14 @@ public abstract class DeviceBase<D : Device>(
} }
override suspend fun writeProperty(propertyName: String, value: Meta): Unit { override suspend fun writeProperty(propertyName: String, value: Meta): Unit {
//bypass property setting if it already has that value
if (logicalState[propertyName] == value) {
logger.debug { "Skipping setting $propertyName to $value because value is already set" }
return
}
when (val property = properties[propertyName]) { when (val property = properties[propertyName]) {
null -> { null -> {
//If there are no physical properties with given name, write a logical one. //If there are no registered physical properties with given name, write a logical one.
propertyChanged(propertyName, value) propertyChanged(propertyName, value)
} }
@ -145,7 +156,7 @@ public abstract class DeviceBase<D : Device>(
//if there is a writeable property with a given name, invalidate logical and write physical //if there is a writeable property with a given name, invalidate logical and write physical
invalidate(propertyName) invalidate(propertyName)
property.writeMeta(self, value) property.writeMeta(self, value)
// perform read after writing if the writer did not set the value // perform read after writing if the writer did not set the value and the value is still in invalid state
if (logicalState[propertyName] == null) { if (logicalState[propertyName] == null) {
val meta = property.readMeta(self) val meta = property.readMeta(self)
propertyChanged(propertyName, meta) propertyChanged(propertyName, meta)

View File

@ -1,9 +1,9 @@
package space.kscience.controls.modbus package space.kscience.controls.modbus
import com.ghgande.j2mod.modbus.procimg.* import com.ghgande.j2mod.modbus.procimg.*
import io.ktor.utils.io.core.buildPacket import io.ktor.utils.io.core.*
import io.ktor.utils.io.core.readByteBuffer import kotlinx.coroutines.delay
import io.ktor.utils.io.core.writeShort import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import space.kscience.controls.api.Device import space.kscience.controls.api.Device
import space.kscience.controls.spec.DevicePropertySpec import space.kscience.controls.spec.DevicePropertySpec
@ -118,22 +118,48 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
} }
} }
/**
* Trigger [block] if one of register changes.
*/
private fun List<ObservableRegister>.onChange(block: (ByteReadPacket) -> Unit) {
var ready = false
forEach { register ->
register.addObserver { _, _ ->
ready = true
}
}
device.launch {
val builder = BytePacketBuilder()
while (isActive) {
delay(1)
if (ready) {
val packet = builder.apply {
forEach { value ->
writeShort(value.toShort())
}
}.build()
block(packet)
ready = false
}
}
}
}
public fun <T> bind(key: ModbusRegistryKey.HoldingRange<T>, propertySpec: WritableDevicePropertySpec<D, T>) { public fun <T> bind(key: ModbusRegistryKey.HoldingRange<T>, propertySpec: WritableDevicePropertySpec<D, T>) {
val registers = List(key.count) { val registers = List(key.count) {
ObservableRegister() ObservableRegister()
} }
registers.forEachIndexed { index, register -> 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) image.addRegister(key.address + index, register)
} }
registers.onChange { packet ->
device[propertySpec] = key.format.readObject(packet)
}
device.useProperty(propertySpec) { value -> device.useProperty(propertySpec) { value ->
val packet = buildPacket { val packet = buildPacket {
key.format.writeObject(this, value) key.format.writeObject(this, value)
@ -182,19 +208,16 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
val registers = List(key.count) { val registers = List(key.count) {
ObservableRegister() ObservableRegister()
} }
registers.forEachIndexed { index, register -> registers.forEachIndexed { index, register ->
register.addObserver { _, _ -> image.addRegister(key.address + index, register)
val packet = buildPacket {
registers.forEach { value ->
writeShort(value.toShort())
}
} }
registers.onChange { packet ->
device.launch { device.launch {
device.action(key.format.readObject(packet)) device.action(key.format.readObject(packet))
} }
} }
image.addRegister(key.address + index, register)
}
return registers return registers
} }
@ -205,11 +228,13 @@ public class DeviceProcessImageBuilder<D : Device> internal constructor(
* Bind the device to Modbus slave (server) image. * Bind the device to Modbus slave (server) image.
*/ */
public fun <D : Device> D.bindProcessImage( public fun <D : Device> D.bindProcessImage(
unitId: Int = 0,
openOnBind: Boolean = true, openOnBind: Boolean = true,
binding: DeviceProcessImageBuilder<D>.() -> Unit, binding: DeviceProcessImageBuilder<D>.() -> Unit,
): ProcessImage { ): ProcessImage {
val image = SimpleProcessImage() val image = SimpleProcessImage(unitId)
DeviceProcessImageBuilder(this, image).apply(binding) DeviceProcessImageBuilder(this, image).apply(binding)
image.setLocked(true)
if (openOnBind) { if (openOnBind) {
launch { launch {
open() open()

View File

@ -61,7 +61,7 @@ public interface ModbusDevice : Device {
} }
public operator fun <T> ModbusRegistryKey.HoldingRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T { public operator fun <T> ModbusRegistryKey.HoldingRange<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
val packet = readInputRegistersToPacket(address, count) val packet = readHoldingRegistersToPacket(address, count)
return format.readObject(packet) return format.readObject(packet)
} }