Fixex in modbus and write-protection for same meta
This commit is contained in:
parent
34e7dd2c6d
commit
efe9a2e842
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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,20 +208,17 @@ 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 { _, _ ->
|
|
||||||
val packet = buildPacket {
|
|
||||||
registers.forEach { value ->
|
|
||||||
writeShort(value.toShort())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
device.launch {
|
|
||||||
device.action(key.format.readObject(packet))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
image.addRegister(key.address + index, register)
|
image.addRegister(key.address + index, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registers.onChange { packet ->
|
||||||
|
device.launch {
|
||||||
|
device.action(key.format.readObject(packet))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user