Add Eclipse Milo client
This commit is contained in:
parent
3606bc3a46
commit
d503f0499e
@ -18,6 +18,7 @@ import kotlin.properties.ReadWriteProperty
|
|||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A device generated from specification
|
||||||
* @param D recursive self-type for properties and actions
|
* @param D recursive self-type for properties and actions
|
||||||
*/
|
*/
|
||||||
@OptIn(InternalDeviceAPI::class)
|
@OptIn(InternalDeviceAPI::class)
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package ru.mipt.npm.controls.misc
|
||||||
|
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.get
|
||||||
|
import space.kscience.dataforge.meta.long
|
||||||
|
import space.kscience.dataforge.values.long
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
// TODO move to core
|
||||||
|
|
||||||
|
public fun Instant.toMeta(): Meta = Meta {
|
||||||
|
"seconds" put epochSecond
|
||||||
|
"nanos" put nano
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Meta.instant(): Instant = value?.long?.let { Instant.ofEpochMilli(it) } ?: Instant.ofEpochSecond(
|
||||||
|
get("seconds")?.long ?: 0L,
|
||||||
|
get("nanos")?.long ?: 0L,
|
||||||
|
)
|
14
controls-opcua/build.gradle.kts
Normal file
14
controls-opcua/build.gradle.kts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
plugins {
|
||||||
|
id("ru.mipt.npm.gradle.jvm")
|
||||||
|
}
|
||||||
|
|
||||||
|
val ktorVersion: String by rootProject.extra
|
||||||
|
|
||||||
|
val miloVersion: String = "0.6.3"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":controls-core"))
|
||||||
|
implementation("org.eclipse.milo:sdk-client:$miloVersion")
|
||||||
|
implementation("org.eclipse.milo:bsd-parser:$miloVersion")
|
||||||
|
implementation("org.eclipse.milo:dictionary-reader:$miloVersion")
|
||||||
|
}
|
@ -0,0 +1,208 @@
|
|||||||
|
package ru.mipt.npm.controls.opcua
|
||||||
|
|
||||||
|
import org.eclipse.milo.opcua.binaryschema.AbstractCodec
|
||||||
|
import org.eclipse.milo.opcua.binaryschema.parser.BsdParser
|
||||||
|
import org.eclipse.milo.opcua.stack.core.UaSerializationException
|
||||||
|
import org.eclipse.milo.opcua.stack.core.serialization.OpcUaBinaryStreamDecoder
|
||||||
|
import org.eclipse.milo.opcua.stack.core.serialization.OpcUaBinaryStreamEncoder
|
||||||
|
import org.eclipse.milo.opcua.stack.core.serialization.SerializationContext
|
||||||
|
import org.eclipse.milo.opcua.stack.core.serialization.codecs.OpcUaBinaryDataTypeCodec
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.builtin.*
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.*
|
||||||
|
import org.opcfoundation.opcua.binaryschema.EnumeratedType
|
||||||
|
import org.opcfoundation.opcua.binaryschema.StructuredType
|
||||||
|
import ru.mipt.npm.controls.misc.instant
|
||||||
|
import ru.mipt.npm.controls.misc.toMeta
|
||||||
|
import space.kscience.dataforge.meta.*
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.asName
|
||||||
|
import space.kscience.dataforge.values.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
public class MetaBsdParser : BsdParser() {
|
||||||
|
override fun getEnumCodec(enumeratedType: EnumeratedType): OpcUaBinaryDataTypeCodec<*> {
|
||||||
|
return MetaEnumCodec()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStructCodec(structuredType: StructuredType): OpcUaBinaryDataTypeCodec<*> {
|
||||||
|
return MetaStructureCodec(structuredType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class MetaEnumCodec : OpcUaBinaryDataTypeCodec<Number> {
|
||||||
|
override fun getType(): Class<Number> {
|
||||||
|
return Number::class.java
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(UaSerializationException::class)
|
||||||
|
override fun encode(
|
||||||
|
context: SerializationContext,
|
||||||
|
encoder: OpcUaBinaryStreamEncoder,
|
||||||
|
value: Number
|
||||||
|
) {
|
||||||
|
encoder.writeInt32(value.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(UaSerializationException::class)
|
||||||
|
override fun decode(
|
||||||
|
context: SerializationContext,
|
||||||
|
decoder: OpcUaBinaryStreamDecoder
|
||||||
|
): Number {
|
||||||
|
return decoder.readInt32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun opcToMeta(value: Any?): Meta = when (value) {
|
||||||
|
null -> Meta(Null)
|
||||||
|
is Meta -> value
|
||||||
|
is Value -> Meta(value)
|
||||||
|
is Number -> when (value) {
|
||||||
|
is UByte -> Meta(value.toShort().asValue())
|
||||||
|
is UShort -> Meta(value.toInt().asValue())
|
||||||
|
is UInteger -> Meta(value.toLong().asValue())
|
||||||
|
is ULong -> Meta(value.toBigInteger().asValue())
|
||||||
|
else -> Meta(value.asValue())
|
||||||
|
}
|
||||||
|
is Boolean -> Meta(value.asValue())
|
||||||
|
is String -> Meta(value.asValue())
|
||||||
|
is Char -> Meta(value.toString().asValue())
|
||||||
|
is DateTime -> value.javaInstant.toMeta()
|
||||||
|
is UUID -> Meta(value.toString().asValue())
|
||||||
|
is QualifiedName -> Meta {
|
||||||
|
"namespaceIndex" put value.namespaceIndex
|
||||||
|
"name" put value.name?.asValue()
|
||||||
|
}
|
||||||
|
is LocalizedText -> Meta {
|
||||||
|
"locale" put value.locale?.asValue()
|
||||||
|
"text" put value.text?.asValue()
|
||||||
|
}
|
||||||
|
is DataValue -> Meta {
|
||||||
|
"value" put opcToMeta(value.value) // need SerializationContext to do that properly
|
||||||
|
value.statusCode?.value?.let { "status" put Meta(it.asValue()) }
|
||||||
|
value.sourceTime?.javaInstant?.let { "sourceTime" put it.toMeta() }
|
||||||
|
value.sourcePicoseconds?.let { "sourcePicoseconds" put Meta(it.asValue()) }
|
||||||
|
value.serverTime?.javaInstant?.let { "serverTime" put it.toMeta() }
|
||||||
|
value.serverPicoseconds?.let { "serverPicoseconds" put Meta(it.asValue()) }
|
||||||
|
}
|
||||||
|
is ByteString -> Meta(value.bytesOrEmpty().asValue())
|
||||||
|
is XmlElement -> Meta(value.fragment?.asValue() ?: Null)
|
||||||
|
is NodeId -> Meta(value.toParseableString().asValue())
|
||||||
|
is ExpandedNodeId -> Meta(value.toParseableString().asValue())
|
||||||
|
is StatusCode -> Meta(value.value.asValue())
|
||||||
|
//is ExtensionObject -> value.decode(client.getDynamicSerializationContext())
|
||||||
|
else -> error("Could not create Meta for value: $value")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
internal class MetaStructureCodec(
|
||||||
|
structuredType: StructuredType?
|
||||||
|
) : AbstractCodec<Meta, Meta>(structuredType) {
|
||||||
|
|
||||||
|
override fun getType(): Class<Meta> = Meta::class.java
|
||||||
|
|
||||||
|
override fun createStructure(name: String, members: LinkedHashMap<String, Meta>): Meta = Meta {
|
||||||
|
members.forEach { (property: String, value: Meta?) ->
|
||||||
|
setMeta(Name.parse(property), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun opcUaToMemberTypeScalar(name: String, value: Any?, typeName: String): Meta = opcToMeta(value)
|
||||||
|
|
||||||
|
override fun opcUaToMemberTypeArray(name: String, values: Any?, typeName: String): Meta = if (values == null) {
|
||||||
|
Meta(Null)
|
||||||
|
} else {
|
||||||
|
// This is a bit array...
|
||||||
|
when (values) {
|
||||||
|
is DoubleArray -> Meta(values.asValue())
|
||||||
|
is FloatArray -> Meta(values.asValue())
|
||||||
|
is IntArray -> Meta(values.asValue())
|
||||||
|
is ByteArray -> Meta(values.asValue())
|
||||||
|
is ShortArray -> Meta(values.asValue())
|
||||||
|
is Array<*> -> Meta {
|
||||||
|
setIndexed(Name.parse(name), values.map { opcUaToMemberTypeScalar(name, it, typeName) })
|
||||||
|
}
|
||||||
|
is Number -> Meta(values.asValue())
|
||||||
|
else -> error("Could not create Meta for value: $values")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun memberTypeToOpcUaScalar(member: Meta?, typeName: String): Any? =
|
||||||
|
if (member == null || member.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else when (typeName) {
|
||||||
|
"Boolean" -> member.boolean
|
||||||
|
"SByte" -> member.value?.numberOrNull?.toByte()
|
||||||
|
"Int16" -> member.value?.numberOrNull?.toShort()
|
||||||
|
"Int32" -> member.value?.numberOrNull?.toInt()
|
||||||
|
"Int64" -> member.value?.numberOrNull?.toLong()
|
||||||
|
"Byte" -> member.value?.numberOrNull?.toShort()?.let { Unsigned.ubyte(it) }
|
||||||
|
"UInt16" -> member.value?.numberOrNull?.toInt()?.let { Unsigned.ushort(it) }
|
||||||
|
"UInt32" -> member.value?.numberOrNull?.toLong()?.let { Unsigned.uint(it) }
|
||||||
|
"UInt64" -> member.value?.numberOrNull?.toLong()?.let { Unsigned.ulong(it) }
|
||||||
|
"Float" -> member.value?.numberOrNull?.toFloat()
|
||||||
|
"Double" -> member.value?.numberOrNull?.toDouble()
|
||||||
|
"String" -> member.string
|
||||||
|
"DateTime" -> DateTime(member.instant())
|
||||||
|
"Guid" -> member.string?.let { UUID.fromString(it) }
|
||||||
|
"ByteString" -> member.value?.list?.let { list ->
|
||||||
|
ByteString(list.map { it.number.toByte() }.toByteArray())
|
||||||
|
}
|
||||||
|
"XmlElement" -> member.string?.let { XmlElement(it) }
|
||||||
|
"NodeId" -> member.string?.let { NodeId.parse(it) }
|
||||||
|
"ExpandedNodeId" -> member.string?.let { ExpandedNodeId.parse(it) }
|
||||||
|
"StatusCode" -> member.long?.let { StatusCode(it) }
|
||||||
|
"QualifiedName" -> QualifiedName(
|
||||||
|
member["namespaceIndex"].int ?: 0,
|
||||||
|
member["name"].string
|
||||||
|
)
|
||||||
|
"LocalizedText" -> LocalizedText(
|
||||||
|
member["locale"].string,
|
||||||
|
member["text"].string
|
||||||
|
)
|
||||||
|
else -> member.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun memberTypeToOpcUaArray(member: Meta, typeName: String): Any = if ("Bit" == typeName) {
|
||||||
|
member.value?.int ?: error("Meta node does not contain int value")
|
||||||
|
} else {
|
||||||
|
when (typeName) {
|
||||||
|
"SByte" -> member.value?.list?.map { it.number.toByte() }?.toByteArray() ?: emptyArray<Byte>()
|
||||||
|
"Int16" -> member.value?.list?.map { it.number.toShort() }?.toShortArray() ?: emptyArray<Short>()
|
||||||
|
"Int32" -> member.value?.list?.map { it.number.toInt() }?.toIntArray() ?: emptyArray<Int>()
|
||||||
|
"Int64" -> member.value?.list?.map { it.number.toLong() }?.toLongArray() ?: emptyArray<Long>()
|
||||||
|
"Byte" -> member.value?.list?.map {
|
||||||
|
Unsigned.ubyte(it.number.toShort())
|
||||||
|
}?.toTypedArray() ?: emptyArray<UByte>()
|
||||||
|
"UInt16" -> member.value?.list?.map {
|
||||||
|
Unsigned.ushort(it.number.toInt())
|
||||||
|
}?.toTypedArray() ?: emptyArray<UShort>()
|
||||||
|
"UInt32" -> member.value?.list?.map {
|
||||||
|
Unsigned.uint(it.number.toLong())
|
||||||
|
}?.toTypedArray() ?: emptyArray<UInteger>()
|
||||||
|
"UInt64" -> member.value?.list?.map {
|
||||||
|
Unsigned.ulong(it.number.toLong())
|
||||||
|
}?.toTypedArray() ?: emptyArray<kotlin.ULong>()
|
||||||
|
"Float" -> member.value?.list?.map { it.number.toFloat() }?.toFloatArray() ?: emptyArray<Float>()
|
||||||
|
"Double" -> member.value?.list?.map { it.number.toDouble() }?.toDoubleArray() ?: emptyArray<Double>()
|
||||||
|
else -> member.getIndexed(Meta.JSON_ARRAY_KEY.asName()).map {
|
||||||
|
memberTypeToOpcUaScalar(it.value, typeName)
|
||||||
|
}.toTypedArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMembers(value: Meta): Map<String, Meta> = value.items.mapKeys { it.toString() }
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Variant.toMeta(serializationContext: SerializationContext): Meta = (value as? ExtensionObject)?.let {
|
||||||
|
it.decode(serializationContext) as Meta
|
||||||
|
} ?: opcToMeta(value)
|
||||||
|
|
||||||
|
//public fun Meta.toVariant(): Variant = if (items.isEmpty()) {
|
||||||
|
// Variant(value?.value)
|
||||||
|
//} else {
|
||||||
|
// TODO()
|
||||||
|
//}
|
@ -0,0 +1,67 @@
|
|||||||
|
package ru.mipt.npm.controls.opcua
|
||||||
|
|
||||||
|
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn
|
||||||
|
import ru.mipt.npm.controls.api.Device
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An OPC-UA device backed by Eclipse Milo client
|
||||||
|
*/
|
||||||
|
public interface MiloDevice : Device {
|
||||||
|
/**
|
||||||
|
* The OPC-UA client initialized on first use
|
||||||
|
*/
|
||||||
|
public val client: OpcUaClient
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
client.disconnect()
|
||||||
|
super.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun <reified T> MiloDevice.opc(
|
||||||
|
nodeId: NodeId,
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
magAge: Double = 500.0
|
||||||
|
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
|
val data = client.readValue(magAge, TimestampsToReturn.Server, nodeId).get()
|
||||||
|
val meta: Meta = when (val content = data.value.value) {
|
||||||
|
is T -> return content
|
||||||
|
content is Meta -> content as Meta
|
||||||
|
content is ExtensionObject -> (content as ExtensionObject).decode(client.dynamicSerializationContext) as Meta
|
||||||
|
else -> error("Incompatible OPC property value $content")
|
||||||
|
}
|
||||||
|
|
||||||
|
return converter.metaToObject(meta) ?: error("Meta $meta could not be converted to ${T::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
|
val meta = converter.objectToMeta(value)
|
||||||
|
client.writeValue(nodeId, DataValue(Variant(meta)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun <reified T> MiloDevice.opcDouble(
|
||||||
|
nodeId: NodeId,
|
||||||
|
magAge: Double = 1.0
|
||||||
|
): ReadWriteProperty<Any?, Double> = opc(nodeId, MetaConverter.double, magAge)
|
||||||
|
|
||||||
|
public inline fun <reified T> MiloDevice.opcInt(
|
||||||
|
nodeId: NodeId,
|
||||||
|
magAge: Double = 1.0
|
||||||
|
): ReadWriteProperty<Any?, Int> = opc(nodeId, MetaConverter.int, magAge)
|
||||||
|
|
||||||
|
public inline fun <reified T> MiloDevice.opcString(
|
||||||
|
nodeId: NodeId,
|
||||||
|
magAge: Double = 1.0
|
||||||
|
): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge)
|
@ -0,0 +1,29 @@
|
|||||||
|
package ru.mipt.npm.controls.opcua
|
||||||
|
|
||||||
|
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
||||||
|
import ru.mipt.npm.controls.properties.DeviceBySpec
|
||||||
|
import ru.mipt.npm.controls.properties.DeviceSpec
|
||||||
|
import space.kscience.dataforge.context.Context
|
||||||
|
import space.kscience.dataforge.context.Global
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.get
|
||||||
|
import space.kscience.dataforge.meta.string
|
||||||
|
|
||||||
|
public open class MiloDeviceBySpec<D: MiloDeviceBySpec<D>>(
|
||||||
|
spec: DeviceSpec<D>,
|
||||||
|
context: Context = Global,
|
||||||
|
meta: Meta = Meta.EMPTY
|
||||||
|
): MiloDevice, DeviceBySpec<D>(spec, context, meta) {
|
||||||
|
|
||||||
|
override val client: OpcUaClient by lazy {
|
||||||
|
val endpointUrl = meta["endpointUrl"].string ?: error("Endpoint url is not defined")
|
||||||
|
context.createMiloClient(endpointUrl).apply {
|
||||||
|
connect().get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
super<MiloDevice>.close()
|
||||||
|
super<DeviceBySpec>.close()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package ru.mipt.npm.controls.opcua
|
||||||
|
|
||||||
|
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
|
||||||
|
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder
|
||||||
|
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider
|
||||||
|
import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider
|
||||||
|
import org.eclipse.milo.opcua.sdk.client.dtd.DataTypeDictionarySessionInitializer
|
||||||
|
import org.eclipse.milo.opcua.stack.client.security.DefaultClientCertificateValidator
|
||||||
|
import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager
|
||||||
|
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint
|
||||||
|
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription
|
||||||
|
import space.kscience.dataforge.context.Context
|
||||||
|
import space.kscience.dataforge.context.info
|
||||||
|
import space.kscience.dataforge.context.logger
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
|
||||||
|
internal fun Context.createMiloClient(
|
||||||
|
endpointUrl: String, //"opc.tcp://localhost:12686/milo"
|
||||||
|
securityPolicy: SecurityPolicy = SecurityPolicy.Basic256Sha256,
|
||||||
|
identityProvider: IdentityProvider = AnonymousProvider(),
|
||||||
|
endpointFilter: (EndpointDescription?) -> Boolean = { securityPolicy.uri == it?.securityPolicyUri }
|
||||||
|
): OpcUaClient {
|
||||||
|
|
||||||
|
val securityTempDir: Path = Paths.get(System.getProperty("java.io.tmpdir"), "client", "security")
|
||||||
|
Files.createDirectories(securityTempDir)
|
||||||
|
check(Files.exists(securityTempDir)) { "Unable to create security dir: $securityTempDir" }
|
||||||
|
|
||||||
|
val pkiDir: Path = securityTempDir.resolve("pki")
|
||||||
|
logger.info { "Milo client security dir: ${securityTempDir.toAbsolutePath()}" }
|
||||||
|
logger.info { "Security pki dir: ${pkiDir.toAbsolutePath()}" }
|
||||||
|
|
||||||
|
//val loader: KeyStoreLoader = KeyStoreLoader().load(securityTempDir)
|
||||||
|
val trustListManager = DefaultTrustListManager(pkiDir.toFile())
|
||||||
|
val certificateValidator = DefaultClientCertificateValidator(trustListManager)
|
||||||
|
|
||||||
|
return OpcUaClient.create(
|
||||||
|
endpointUrl,
|
||||||
|
{ endpoints: List<EndpointDescription?> ->
|
||||||
|
endpoints.stream()
|
||||||
|
.filter(endpointFilter)
|
||||||
|
.findFirst()
|
||||||
|
}
|
||||||
|
) { configBuilder: OpcUaClientConfigBuilder ->
|
||||||
|
configBuilder
|
||||||
|
.setApplicationName(LocalizedText.english("Controls.kt"))
|
||||||
|
.setApplicationUri("urn:ru.mipt:npm:controls:opcua")
|
||||||
|
// .setKeyPair(loader.getClientKeyPair())
|
||||||
|
// .setCertificate(loader.getClientCertificate())
|
||||||
|
// .setCertificateChain(loader.getClientCertificateChain())
|
||||||
|
.setCertificateValidator(certificateValidator)
|
||||||
|
.setIdentityProvider(identityProvider)
|
||||||
|
.setRequestTimeout(uint(5000))
|
||||||
|
.build()
|
||||||
|
}.apply {
|
||||||
|
addSessionInitializer(DataTypeDictionarySessionInitializer(MetaBsdParser()))
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ description = """
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val dataforgeVersion: String by rootProject.extra
|
val dataforgeVersion: String by rootProject.extra
|
||||||
val ktorVersion: String = "1.5.3"
|
val ktorVersion: String by rootProject.extra
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":controls-core"))
|
implementation(project(":controls-core"))
|
||||||
|
@ -9,6 +9,7 @@ import io.ktor.http.HttpStatusCode
|
|||||||
import io.ktor.request.receiveText
|
import io.ktor.request.receiveText
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
|
import io.ktor.response.respondText
|
||||||
import io.ktor.routing.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.routing.post
|
import io.ktor.routing.post
|
||||||
import io.ktor.routing.route
|
import io.ktor.routing.route
|
||||||
@ -157,11 +158,13 @@ public fun Application.deviceManagerModule(
|
|||||||
|
|
||||||
post("message") {
|
post("message") {
|
||||||
val body = call.receiveText()
|
val body = call.receiveText()
|
||||||
|
|
||||||
val request: DeviceMessage = MagixEndpoint.magixJson.decodeFromString(DeviceMessage.serializer(), body)
|
val request: DeviceMessage = MagixEndpoint.magixJson.decodeFromString(DeviceMessage.serializer(), body)
|
||||||
|
|
||||||
val response = manager.respondHubMessage(request)
|
val response = manager.respondHubMessage(request)
|
||||||
|
if (response != null) {
|
||||||
call.respondMessage(response)
|
call.respondMessage(response)
|
||||||
|
} else {
|
||||||
|
call.respondText("No response")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
route("{target}") {
|
route("{target}") {
|
||||||
@ -178,7 +181,11 @@ public fun Application.deviceManagerModule(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val response = manager.respondHubMessage(request)
|
val response = manager.respondHubMessage(request)
|
||||||
|
if (response != null) {
|
||||||
call.respondMessage(response)
|
call.respondMessage(response)
|
||||||
|
} else {
|
||||||
|
call.respond(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
post("set") {
|
post("set") {
|
||||||
val target: String by call.parameters
|
val target: String by call.parameters
|
||||||
@ -194,7 +201,11 @@ public fun Application.deviceManagerModule(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val response = manager.respondHubMessage(request)
|
val response = manager.respondHubMessage(request)
|
||||||
|
if (response != null) {
|
||||||
call.respondMessage(response)
|
call.respondMessage(response)
|
||||||
|
} else {
|
||||||
|
call.respond(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class DemoDevice : DeviceBySpec<DemoDevice>(DemoDevice) {
|
|||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
@OptIn(ExperimentalTime::class)
|
||||||
override fun DemoDevice.onStartup() {
|
override fun DemoDevice.onStartup() {
|
||||||
doRecurring(Duration.milliseconds(10)){
|
doRecurring(Duration.milliseconds(50)){
|
||||||
sin.read()
|
sin.read()
|
||||||
cos.read()
|
cos.read()
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ include(
|
|||||||
":controls-tcp",
|
":controls-tcp",
|
||||||
":controls-serial",
|
":controls-serial",
|
||||||
":controls-server",
|
":controls-server",
|
||||||
|
":controls-opcua",
|
||||||
":demo",
|
":demo",
|
||||||
":magix",
|
":magix",
|
||||||
":magix:magix-api",
|
":magix:magix-api",
|
||||||
|
Loading…
Reference in New Issue
Block a user