Skip to content

Commit f10d3c7

Browse files
committed
Migrate to Milo 1.0
1 parent 8d08678 commit f10d3c7

File tree

19 files changed

+352
-328
lines changed

19 files changed

+352
-328
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
### Added
6+
- `setCachedValue` API for `CachingDevice` to directly manipulate cached value and solve circular write problem with virtual properties.
67
- Flow control simulation
78
- Value averaging plot extension
89
- PLC4X bindings
@@ -13,6 +14,8 @@
1314
- New interface `WithLifeCycle`. Change Port API to adhere to it.
1415

1516
### Changed
17+
- `getProperty` renamed to `getCachedProperty` for `CachingDevice`
18+
- Milo migrated to stable version
1619
- Constructor properties return `DeviceState` in order to be able to subscribe to them
1720
- Refactored ports. Now we have `AsynchronousPort` as well as `SynchronousPort`
1821
- `DeviceClient` now initializes property and action descriptors eagerly.

controls-core/src/commonMain/kotlin/space/kscience/controls/spec/propertySpecDelegates.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package space.kscience.controls.spec
22

3+
import space.kscience.controls.api.CachingDevice
34
import space.kscience.controls.api.Device
45
import space.kscience.controls.api.PropertyDescriptor
56
import space.kscience.controls.api.metaDescriptor
@@ -47,7 +48,7 @@ public fun <T, D : Device> DeviceSpec<D>.mutableProperty(
4748
* Register a read-only logical property
4849
* (without a corresponding physical state or with a state that is updated asynchronously) for a device
4950
*/
50-
public fun <T, D : DeviceBase<D>> DeviceSpec<D>.property(
51+
public fun <T, D : CachingDevice> DeviceSpec<D>.property(
5152
converter: MetaConverter<T>,
5253
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
5354
name: String? = null,
@@ -56,7 +57,7 @@ public fun <T, D : DeviceBase<D>> DeviceSpec<D>.property(
5657
converter,
5758
descriptorBuilder,
5859
name,
59-
read = { propertyName -> getProperty(propertyName)?.let(converter::readOrNull) },
60+
read = { propertyName -> getCachedProperty(propertyName)?.let(converter::readOrNull) },
6061
)
6162

6263
public fun <D : Device> DeviceSpec<D>.booleanProperty(
@@ -145,7 +146,8 @@ public fun <D : Device> DeviceSpec<D>.metaProperty(
145146
* Register a mutable logical property
146147
* (without a corresponding physical state or with a state that is updated asynchronously) for a device
147148
*/
148-
public fun <T, D : DeviceBase<D>> DeviceSpec<D>.mutableProperty(
149+
@OptIn(InternalDeviceAPI::class)
150+
public fun <T, D : CachingDevice> DeviceSpec<D>.mutableProperty(
149151
converter: MetaConverter<T>,
150152
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
151153
name: String? = null,
@@ -154,8 +156,8 @@ public fun <T, D : DeviceBase<D>> DeviceSpec<D>.mutableProperty(
154156
converter,
155157
descriptorBuilder,
156158
name,
157-
read = { propertyName -> getProperty(propertyName)?.let(converter::readOrNull) },
158-
write = { propertyName, value -> writeProperty(propertyName, converter.convert(value)) }
159+
read = { propertyName -> getCachedProperty(propertyName)?.let(converter::readOrNull) },
160+
write = { propertyName, value -> setCachedProperty(propertyName, converter.convert(value)) }
159161
)
160162

161163
public fun <D : Device> DeviceSpec<D>.mutableBooleanProperty(

controls-magix/src/commonMain/kotlin/space/kscience/controls/client/clientPropertyAccess.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public suspend fun <T> DeviceClient.request(propertySpec: DevicePropertySpec<*,
2323
propertySpec.converter.read(getOrReadProperty(propertySpec.name))
2424

2525
public fun <T> DeviceClient.getCached(propertySpec: DevicePropertySpec<*, T>): T? =
26-
getProperty(propertySpec.name)?.let { propertySpec.converter.read(it) }
26+
getCachedProperty(propertySpec.name)?.let { propertySpec.converter.read(it) }
2727

2828

2929
public suspend fun <T> DeviceClient.write(propertySpec: MutableDevicePropertySpec<*, T>, value: T) {

controls-opcua/build.gradle.kts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,26 @@ val ktorVersion: String by rootProject.extra
1313

1414
dependencies {
1515
api(projects.controlsCore)
16+
api(spclibs.kotlinx.coroutines.core)
1617
api(spclibs.kotlinx.coroutines.jdk8)
1718

1819
api(libs.milo.client)
19-
api(libs.milo.parser)
20+
api(libs.milo.dtd.core)
2021
api(libs.milo.server)
2122

2223
testImplementation(spclibs.kotlinx.coroutines.test)
2324
}
2425

25-
readme{
26+
readme {
2627
maturity = Maturity.EXPERIMENTAL
2728

28-
feature("opcuaClient", ref = "src/main/kotlin/space/kscience/controls/opcua/client"){
29+
feature("opcuaClient", ref = "src/main/kotlin/space/kscience/controls/opcua/client") {
2930
"""
3031
Connect a Controls-kt as a client to OPC UA server
3132
""".trimIndent()
3233
}
3334

34-
feature("opcuaServer", ref = "src/main/kotlin/space/kscience/controls/opcua/server"){
35+
feature("opcuaServer", ref = "src/main/kotlin/space/kscience/controls/opcua/server") {
3536
"""
3637
Create an OPC UA server on top of Controls-kt device (or device hub)
3738
""".trimIndent()
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//package space.kscience.controls.opcua.client
2+
//
3+
//import org.eclipse.milo.opcua.sdk.core.dtd.AbstractBsdCodec
4+
//import org.eclipse.milo.opcua.stack.core.encoding.EncodingContext
5+
//import org.eclipse.milo.opcua.stack.core.types.builtin.*
6+
//import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.*
7+
//import org.opcfoundation.opcua.binaryschema.StructuredType
8+
//import space.kscience.controls.instant
9+
//import space.kscience.controls.toMeta
10+
//import space.kscience.dataforge.meta.*
11+
//import space.kscience.dataforge.names.Name
12+
//import space.kscience.dataforge.names.asName
13+
//import java.util.*
14+
//import kotlin.time.toJavaInstant
15+
//import kotlin.time.toKotlinInstant
16+
//
17+
//
18+
////public class MetaBsdParser : BsdParser() {
19+
//// override fun getEnumCodec(enumeratedType: EnumeratedType): OpcUaBinaryDataTypeCodec<*> {
20+
//// return MetaEnumCodec()
21+
//// }
22+
////
23+
//// override fun getStructCodec(structuredType: StructuredType): OpcUaBinaryDataTypeCodec<*> {
24+
//// return MetaStructureCodec(structuredType)
25+
//// }
26+
////}
27+
////
28+
////internal class MetaEnumCodec : OpcUaBinaryDataTypeCodec<Number> {
29+
//// override fun getType(): Class<Number> {
30+
//// return Number::class.java
31+
//// }
32+
////
33+
//// @Throws(UaSerializationException::class)
34+
//// override fun encode(
35+
//// context: SerializationContext,
36+
//// encoder: OpcUaBinaryStreamEncoder,
37+
//// value: Number
38+
//// ) {
39+
//// encoder.writeInt32(value.toInt())
40+
//// }
41+
////
42+
//// @Throws(UaSerializationException::class)
43+
//// override fun decode(
44+
//// context: SerializationContext,
45+
//// decoder: OpcUaBinaryStreamDecoder
46+
//// ): Number {
47+
//// return decoder.readInt32()
48+
//// }
49+
////}
50+
//
51+
//
52+
///**
53+
// * based on https://github.com/eclipse/milo/blob/master/opc-ua-stack/bsd-parser-gson/src/main/java/org/eclipse/milo/opcua/binaryschema/gson/JsonStructureCodec.java
54+
// */
55+
//internal class BinaryMetaCodec(
56+
// structuredType: StructuredType?
57+
//) : AbstractBsdCodec<Meta, Meta>(structuredType) {
58+
//
59+
// override fun createStructure(name: String, members: LinkedHashMap<String, Meta>): Meta = Meta {
60+
// members.forEach { (property: String, value: Meta?) ->
61+
// set(Name.parse(property), value)
62+
// }
63+
// }
64+
//
65+
// override fun opcUaToMemberTypeScalar(name: String, value: Any?, typeName: String): Meta = opcToMeta(value)
66+
//
67+
// override fun opcUaToMemberTypeArray(name: String, values: Any?, typeName: String): Meta = if (values == null) {
68+
// Meta(Null)
69+
// } else {
70+
// // This is a bit array...
71+
// when (values) {
72+
// is DoubleArray -> Meta(values.asValue())
73+
// is FloatArray -> Meta(values.asValue())
74+
// is IntArray -> Meta(values.asValue())
75+
// is ByteArray -> Meta(values.asValue())
76+
// is ShortArray -> Meta(values.asValue())
77+
// is Array<*> -> Meta {
78+
// setIndexed(Name.parse(name), values.map { opcUaToMemberTypeScalar(name, it, typeName) })
79+
// }
80+
// is Number -> Meta(values.asValue())
81+
// else -> error("Could not create Meta for value: $values")
82+
// }
83+
// }
84+
//
85+
// override fun memberTypeToOpcUaScalar(member: Meta?, typeName: String): Any? =
86+
// if (member == null || member.isEmpty()) {
87+
// null
88+
// } else when (typeName) {
89+
// "Boolean" -> member.boolean
90+
// "SByte" -> member.value?.numberOrNull?.toByte()
91+
// "Int16" -> member.value?.numberOrNull?.toShort()
92+
// "Int32" -> member.value?.numberOrNull?.toInt()
93+
// "Int64" -> member.value?.numberOrNull?.toLong()
94+
// "Byte" -> member.value?.numberOrNull?.toShort()?.let { Unsigned.ubyte(it) }
95+
// "UInt16" -> member.value?.numberOrNull?.toInt()?.let { Unsigned.ushort(it) }
96+
// "UInt32" -> member.value?.numberOrNull?.toLong()?.let { Unsigned.uint(it) }
97+
// "UInt64" -> member.value?.numberOrNull?.toLong()?.let { Unsigned.ulong(it) }
98+
// "Float" -> member.value?.numberOrNull?.toFloat()
99+
// "Double" -> member.value?.numberOrNull?.toDouble()
100+
// "String" -> member.string
101+
// "DateTime" -> member.instant?.toJavaInstant()?.let { DateTime(it) }
102+
// "Guid" -> member.string?.let { UUID.fromString(it) }
103+
// "ByteString" -> member.value?.list?.let { list ->
104+
// ByteString(list.map { it.number.toByte() }.toByteArray())
105+
// }
106+
// "XmlElement" -> member.string?.let { XmlElement(it) }
107+
// "NodeId" -> member.string?.let { NodeId.parse(it) }
108+
// "ExpandedNodeId" -> member.string?.let { ExpandedNodeId.parse(it) }
109+
// "StatusCode" -> member.long?.let { StatusCode(it) }
110+
// "QualifiedName" -> QualifiedName(
111+
// member["namespaceIndex"].int ?: 0,
112+
// member["name"].string
113+
// )
114+
// "LocalizedText" -> LocalizedText(
115+
// member["locale"].string,
116+
// member["text"].string
117+
// )
118+
// else -> member.toString()
119+
// }
120+
//
121+
// override fun memberTypeToOpcUaArray(member: Meta, typeName: String): Any = if ("Bit" == typeName) {
122+
// member.value?.int ?: error("Meta node does not contain int value")
123+
// } else {
124+
// when (typeName) {
125+
// "SByte" -> member.value?.list?.map { it.number.toByte() }?.toByteArray() ?: emptyArray<Byte>()
126+
// "Int16" -> member.value?.list?.map { it.number.toShort() }?.toShortArray() ?: emptyArray<Short>()
127+
// "Int32" -> member.value?.list?.map { it.number.toInt() }?.toIntArray() ?: emptyArray<Int>()
128+
// "Int64" -> member.value?.list?.map { it.number.toLong() }?.toLongArray() ?: emptyArray<Long>()
129+
// "Byte" -> member.value?.list?.map {
130+
// Unsigned.ubyte(it.number.toShort())
131+
// }?.toTypedArray() ?: emptyArray<UByte>()
132+
// "UInt16" -> member.value?.list?.map {
133+
// Unsigned.ushort(it.number.toInt())
134+
// }?.toTypedArray() ?: emptyArray<UShort>()
135+
// "UInt32" -> member.value?.list?.map {
136+
// Unsigned.uint(it.number.toLong())
137+
// }?.toTypedArray() ?: emptyArray<UInteger>()
138+
// "UInt64" -> member.value?.list?.map {
139+
// Unsigned.ulong(it.number.toLong())
140+
// }?.toTypedArray() ?: emptyArray<kotlin.ULong>()
141+
// "Float" -> member.value?.list?.map { it.number.toFloat() }?.toFloatArray() ?: emptyArray<Float>()
142+
// "Double" -> member.value?.list?.map { it.number.toDouble() }?.toDoubleArray() ?: emptyArray<Double>()
143+
// else -> member.getIndexed(Meta.JSON_ARRAY_KEY.asName()).map {
144+
// memberTypeToOpcUaScalar(it.value, typeName)
145+
// }.toTypedArray()
146+
// }
147+
// }
148+
//
149+
// override fun getMembers(value: Meta): Map<String, Meta> = value.items.mapKeys { it.toString() }
150+
//}
151+
//
152+
//
153+
////public fun Meta.toVariant(): Variant = if (items.isEmpty()) {
154+
//// Variant(value?.value)
155+
////} else {
156+
//// TODO()
157+
////}

0 commit comments

Comments
 (0)