diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d6f9d13 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +## Unreleased + +### Added +- Core interfaces for building a device server +- Magix service for binding controls devices (both as RPC client and server) +- A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols +- A client and server connectors for OPC-UA via Eclipse Milo +- Implementation of byte ports on top os ktor-io asynchronous API +- Implementation of direct serial port communication with JSerialComm +- A combined Magix event loop server with web server for visualization. +- An API for stand-alone Controls-kt device or a hub. +- An implementation of controls-storage on top of JetBrains Xodus. +- A kotlin API for magix standard and some zero-dependency magix services +- Java API to work with magix endpoints without Kotlin +- MQTT client magix endpoint +- RabbitMQ client magix endpoint +- Magix endpoint (client) based on RSocket +- A magix event loop implementation in Kotlin. Includes HTTP/SSE and RSocket routes. +- Magix history database API +- ZMQ client endpoint for Magix + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security diff --git a/README.md b/README.md index 098569b..d5b40c3 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Example view of a demo: ### [controls-core](controls-core) -> +> Core interfaces for building a device server > > **Maturity**: EXPERIMENTAL > @@ -51,135 +51,150 @@ Example view of a demo: > - [device](controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt) : Device API with subscription (asynchronous and pseudo-synchronous properties) > - [deviceMessage](controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt) : Specification for messages used to communicate between Controls-kt devices. > - [deviceHub](controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt) : Grouping of devices into local tree-like hubs. +> - [deviceSpec](controls-core/src/commonMain/kotlin/space/kscience/controls/spec) : Mechanics and type-safe builders for devices. Including separation of device specification and device state. +> - [deviceManager](controls-core/src/commonMain/kotlin/space/kscience/controls/manager) : DataForge DI integration for devices. Includes device builders. +> - [ports](controls-core/src/commonMain/kotlin/space/kscience/controls/ports) : Working with asynchronous data sending and receiving raw byte arrays -### [controls-ktor-tcp](controls-ktor-tcp) -> +### [controls-magix](controls-magix) +> Magix service for binding controls devices (both as RPC client and server) > > **Maturity**: EXPERIMENTAL - -### [controls-magix-client](controls-magix-client) -> > -> **Maturity**: EXPERIMENTAL +> **Features:** +> - [controlsMagix](controls-magix/src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt) : Connect a `DeviceManage` with one or many devices to the Magix endpoint +> - [DeviceClient](controls-magix/src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt) : A remote connector to Controls-kt device via Magix + ### [controls-modbus](controls-modbus) -> +> A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols > > **Maturity**: EXPERIMENTAL +> +> **Features:** +> - [modbusRegistryMap](controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt) : Type-safe modbus registry map. Allows to define both single-register and multi-register entries (using DataForge IO). +Automatically checks consistency. +> - [modbusProcessImage](controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt) : Binding of slave (server) modbus device to Controls-kt device +> - [modbusDevice](controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt) : A device with additional methods to work with modbus registers. + ### [controls-opcua](controls-opcua) -> +> A client and server connectors for OPC-UA via Eclipse Milo +> +> **Maturity**: EXPERIMENTAL +> +> **Features:** +> - [opcuaClient](controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client) : Connect a Controls-kt as a client to OPC UA server +> - [opcuaServer](controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server) : Create an OPC UA server on top of Controls-kt device (or device hub) + + +### [controls-pi](controls-pi) +> Utils to work with controls-kt on Raspberry pi > > **Maturity**: EXPERIMENTAL +### [controls-ports-ktor](controls-ports-ktor) +> Implementation of byte ports on top os ktor-io asynchronous API +> +> **Maturity**: PROTOTYPE + ### [controls-serial](controls-serial) -> +> Implementation of direct serial port communication with JSerialComm > > **Maturity**: EXPERIMENTAL ### [controls-server](controls-server) -> +> A combined Magix event loop server with web server for visualization. > -> **Maturity**: EXPERIMENTAL +> **Maturity**: PROTOTYPE ### [controls-storage](controls-storage) -> +> An API for stand-alone Controls-kt device or a hub. > > **Maturity**: PROTOTYPE ### [demo](demo) -> > > **Maturity**: EXPERIMENTAL ### [magix](magix) -> > > **Maturity**: EXPERIMENTAL ### [controls-storage/controls-xodus](controls-storage/controls-xodus) -> +> An implementation of controls-storage on top of JetBrains Xodus. > > **Maturity**: PROTOTYPE ### [demo/all-things](demo/all-things) -> > > **Maturity**: EXPERIMENTAL ### [demo/car](demo/car) -> > > **Maturity**: EXPERIMENTAL ### [demo/echo](demo/echo) -> > > **Maturity**: EXPERIMENTAL ### [demo/magix-demo](demo/magix-demo) -> > > **Maturity**: EXPERIMENTAL ### [demo/many-devices](demo/many-devices) -> > > **Maturity**: EXPERIMENTAL ### [demo/mks-pdr900](demo/mks-pdr900) -> > > **Maturity**: EXPERIMENTAL ### [demo/motors](demo/motors) -> > > **Maturity**: EXPERIMENTAL ### [magix/magix-api](magix/magix-api) -> +> A kotlin API for magix standard and some zero-dependency magix services > > **Maturity**: EXPERIMENTAL -### [magix/magix-java-client](magix/magix-java-client) -> +### [magix/magix-java-endpoint](magix/magix-java-endpoint) +> Java API to work with magix endpoints without Kotlin > > **Maturity**: EXPERIMENTAL ### [magix/magix-mqtt](magix/magix-mqtt) -> +> MQTT client magix endpoint > > **Maturity**: PROTOTYPE ### [magix/magix-rabbit](magix/magix-rabbit) -> +> RabbitMQ client magix endpoint > > **Maturity**: PROTOTYPE ### [magix/magix-rsocket](magix/magix-rsocket) -> +> Magix endpoint (client) based on RSocket > > **Maturity**: EXPERIMENTAL ### [magix/magix-server](magix/magix-server) -> +> A magix event loop implementation in Kotlin. Includes HTTP/SSE and RSocket routes. > > **Maturity**: EXPERIMENTAL ### [magix/magix-storage](magix/magix-storage) -> +> Magix history database API > -> **Maturity**: EXPERIMENTAL +> **Maturity**: PROTOTYPE ### [magix/magix-zmq](magix/magix-zmq) -> +> ZMQ client endpoint for Magix > > **Maturity**: EXPERIMENTAL ### [magix/magix-storage/magix-storage-xodus](magix/magix-storage/magix-storage-xodus) -> > > **Maturity**: PROTOTYPE diff --git a/build.gradle.kts b/build.gradle.kts index 93a689c..df7c664 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,14 +6,14 @@ plugins { id("space.kscience.gradle.project") } -val dataforgeVersion: String by extra("0.6.1") +val dataforgeVersion: String by extra("0.6.2-dev-3") val ktorVersion: String by extra(space.kscience.gradle.KScienceVersions.ktorVersion) val rsocketVersion by extra("0.15.4") val xodusVersion by extra("2.0.1") allprojects { group = "space.kscience" - version = "0.2.0-dev-1" + version = "0.2.0" repositories{ maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") } @@ -29,10 +29,9 @@ ksciencePublish { if (isInDevelopment) { "https://maven.pkg.jetbrains.space/spc/p/sci/dev" } else { - "https://maven.pkg.jetbrains.space/spc/p/sci/release" + "https://maven.pkg.jetbrains.space/spc/p/sci/maven" } ) - space("https://maven.pkg.jetbrains.space/spc/p/controls/maven") } readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md") \ No newline at end of file diff --git a/controls-core/README.md b/controls-core/README.md new file mode 100644 index 0000000..b75961d --- /dev/null +++ b/controls-core/README.md @@ -0,0 +1,33 @@ +# Module controls-core + +Core interfaces for building a device server + +## Features + + - [device](src/commonMain/kotlin/space/kscience/controls/api/Device.kt) : Device API with subscription (asynchronous and pseudo-synchronous properties) + - [deviceMessage](src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt) : Specification for messages used to communicate between Controls-kt devices. + - [deviceHub](src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt) : Grouping of devices into local tree-like hubs. + - [deviceSpec](src/commonMain/kotlin/space/kscience/controls/spec) : Mechanics and type-safe builders for devices. Including separation of device specification and device state. + - [deviceManager](src/commonMain/kotlin/space/kscience/controls/manager) : DataForge DI integration for devices. Includes device builders. + - [ports](src/commonMain/kotlin/space/kscience/controls/ports) : Working with asynchronous data sending and receiving raw byte arrays + + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-core:0.2.0`. + +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-core:0.2.0") +} +``` diff --git a/controls-core/api/controls-core.api b/controls-core/api/controls-core.api new file mode 100644 index 0000000..17a8c3a --- /dev/null +++ b/controls-core/api/controls-core.api @@ -0,0 +1,915 @@ +public final class space/kscience/controls/api/ActionDescriptor { + public static final field Companion Lspace/kscience/controls/api/ActionDescriptor$Companion; + public synthetic fun (ILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;)V + public final fun getInfo ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun setInfo (Ljava/lang/String;)V + public static final synthetic fun write$Self (Lspace/kscience/controls/api/ActionDescriptor;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/ActionDescriptor$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/ActionDescriptor$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/ActionDescriptor; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/ActionDescriptor;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/ActionDescriptor$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/ActionExecuteMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/ActionExecuteMessage$Companion; + public synthetic fun (ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lspace/kscience/dataforge/names/Name; + public final fun component5 ()Lspace/kscience/dataforge/names/Name; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Lkotlinx/datetime/Instant; + public final fun copy (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/ActionExecuteMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/ActionExecuteMessage;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/ActionExecuteMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getAction ()Ljava/lang/String; + public final fun getArgument ()Lspace/kscience/dataforge/meta/Meta; + public fun getComment ()Ljava/lang/String; + public final fun getRequestId ()Ljava/lang/String; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/ActionExecuteMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/ActionExecuteMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/ActionExecuteMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/ActionExecuteMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/ActionExecuteMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/ActionExecuteMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/ActionResultMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/ActionResultMessage$Companion; + public synthetic fun (ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lspace/kscience/dataforge/names/Name; + public final fun component5 ()Lspace/kscience/dataforge/names/Name; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Lkotlinx/datetime/Instant; + public final fun copy (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/ActionResultMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/ActionResultMessage;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/ActionResultMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getAction ()Ljava/lang/String; + public fun getComment ()Ljava/lang/String; + public final fun getRequestId ()Ljava/lang/String; + public final fun getResult ()Lspace/kscience/dataforge/meta/Meta; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/ActionResultMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/ActionResultMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/ActionResultMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/ActionResultMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/ActionResultMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/ActionResultMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/BinaryNotificationMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/BinaryNotificationMessage$Companion; + public synthetic fun (ILjava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lspace/kscience/dataforge/names/Name; + public final fun component3 ()Lspace/kscience/dataforge/names/Name; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lkotlinx/datetime/Instant; + public final fun copy (Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/BinaryNotificationMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/BinaryNotificationMessage;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/BinaryNotificationMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getBinaryID ()Ljava/lang/String; + public fun getComment ()Ljava/lang/String; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/BinaryNotificationMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/BinaryNotificationMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/BinaryNotificationMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/BinaryNotificationMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/BinaryNotificationMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/BinaryNotificationMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/DescriptionMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/DescriptionMessage$Companion; + public synthetic fun (ILspace/kscience/dataforge/meta/Meta;Ljava/util/Collection;Ljava/util/Collection;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Lspace/kscience/dataforge/meta/Meta;Ljava/util/Collection;Ljava/util/Collection;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Lspace/kscience/dataforge/meta/Meta;Ljava/util/Collection;Ljava/util/Collection;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component2 ()Ljava/util/Collection; + public final fun component3 ()Ljava/util/Collection; + public final fun component4 ()Lspace/kscience/dataforge/names/Name; + public final fun component5 ()Lspace/kscience/dataforge/names/Name; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Lkotlinx/datetime/Instant; + public final fun copy (Lspace/kscience/dataforge/meta/Meta;Ljava/util/Collection;Ljava/util/Collection;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/DescriptionMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/DescriptionMessage;Lspace/kscience/dataforge/meta/Meta;Ljava/util/Collection;Ljava/util/Collection;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/DescriptionMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getActions ()Ljava/util/Collection; + public fun getComment ()Ljava/lang/String; + public final fun getDescription ()Lspace/kscience/dataforge/meta/Meta; + public final fun getProperties ()Ljava/util/Collection; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/DescriptionMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/DescriptionMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/DescriptionMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/DescriptionMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/DescriptionMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/DescriptionMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/DescriptorsKt { + public static final fun metaDescriptor (Lspace/kscience/controls/api/PropertyDescriptor;Lkotlin/jvm/functions/Function1;)V +} + +public abstract interface class space/kscience/controls/api/Device : java/lang/AutoCloseable, kotlinx/coroutines/CoroutineScope, space/kscience/dataforge/context/ContextAware { + public static final field Companion Lspace/kscience/controls/api/Device$Companion; + public static final field DEVICE_TARGET Ljava/lang/String; + public fun close ()V + public abstract fun execute (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun execute$default (Lspace/kscience/controls/api/Device;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public abstract fun getActionDescriptors ()Ljava/util/Collection; + public abstract fun getLifecycleState ()Lspace/kscience/controls/api/DeviceLifecycleState; + public abstract fun getMessageFlow ()Lkotlinx/coroutines/flow/Flow; + public fun getMeta ()Lspace/kscience/dataforge/meta/Meta; + public abstract fun getProperty (Ljava/lang/String;)Lspace/kscience/dataforge/meta/Meta; + public abstract fun getPropertyDescriptors ()Ljava/util/Collection; + public abstract fun invalidate (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun open (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun open$suspendImpl (Lspace/kscience/controls/api/Device;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun readProperty (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun writeProperty (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/api/Device$Companion { + public static final field DEVICE_TARGET Ljava/lang/String; +} + +public final class space/kscience/controls/api/DeviceErrorMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/DeviceErrorMessage$Companion; + public synthetic fun (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lspace/kscience/dataforge/names/Name; + public final fun component5 ()Lspace/kscience/dataforge/names/Name; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Lkotlinx/datetime/Instant; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/DeviceErrorMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/DeviceErrorMessage;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/DeviceErrorMessage; + public fun equals (Ljava/lang/Object;)Z + public fun getComment ()Ljava/lang/String; + public final fun getErrorMessage ()Ljava/lang/String; + public final fun getErrorStackTrace ()Ljava/lang/String; + public final fun getErrorType ()Ljava/lang/String; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/DeviceErrorMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/DeviceErrorMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/DeviceErrorMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/DeviceErrorMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/DeviceErrorMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/DeviceErrorMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class space/kscience/controls/api/DeviceHub : space/kscience/dataforge/provider/Provider { + public static final field Companion Lspace/kscience/controls/api/DeviceHub$Companion; + public fun content (Ljava/lang/String;)Ljava/util/Map; + public fun getDefaultChainTarget ()Ljava/lang/String; + public fun getDefaultTarget ()Ljava/lang/String; + public abstract fun getDevices ()Ljava/util/Map; +} + +public final class space/kscience/controls/api/DeviceHub$Companion { +} + +public final class space/kscience/controls/api/DeviceHubKt { + public static final fun execute (Lspace/kscience/controls/api/DeviceHub;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun get (Lspace/kscience/controls/api/DeviceHub;Ljava/lang/String;)Lspace/kscience/controls/api/Device; + public static final fun get (Lspace/kscience/controls/api/DeviceHub;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/controls/api/Device; + public static final fun get (Lspace/kscience/controls/api/DeviceHub;Lspace/kscience/dataforge/names/NameToken;)Lspace/kscience/controls/api/Device; + public static final fun getOrNull (Lspace/kscience/controls/api/DeviceHub;Ljava/lang/String;)Lspace/kscience/controls/api/Device; + public static final fun getOrNull (Lspace/kscience/controls/api/DeviceHub;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/controls/api/Device; + public static final fun readProperty (Lspace/kscience/controls/api/DeviceHub;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun writeProperty (Lspace/kscience/controls/api/DeviceHub;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/api/DeviceKt { + public static final fun getAllProperties (Lspace/kscience/controls/api/Device;)Lspace/kscience/dataforge/meta/Meta; + public static final fun getOrReadProperty (Lspace/kscience/controls/api/Device;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun onPropertyChange (Lspace/kscience/controls/api/Device;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; +} + +public final class space/kscience/controls/api/DeviceLifecycleState : java/lang/Enum { + public static final field CLOSED Lspace/kscience/controls/api/DeviceLifecycleState; + public static final field INIT Lspace/kscience/controls/api/DeviceLifecycleState; + public static final field OPEN Lspace/kscience/controls/api/DeviceLifecycleState; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lspace/kscience/controls/api/DeviceLifecycleState; + public static fun values ()[Lspace/kscience/controls/api/DeviceLifecycleState; +} + +public final class space/kscience/controls/api/DeviceLogMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/DeviceLogMessage$Companion; + public synthetic fun (ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component3 ()Lspace/kscience/dataforge/names/Name; + public final fun component4 ()Lspace/kscience/dataforge/names/Name; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lkotlinx/datetime/Instant; + public final fun copy (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/DeviceLogMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/DeviceLogMessage;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/DeviceLogMessage; + public fun equals (Ljava/lang/Object;)Z + public fun getComment ()Ljava/lang/String; + public final fun getData ()Lspace/kscience/dataforge/meta/Meta; + public final fun getMessage ()Ljava/lang/String; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/DeviceLogMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/DeviceLogMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/DeviceLogMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/DeviceLogMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/DeviceLogMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/DeviceLogMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract class space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/DeviceMessage$Companion; + public synthetic fun (ILkotlinx/serialization/internal/SerializationConstructorMarker;)V + public abstract fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public abstract fun getComment ()Ljava/lang/String; + public abstract fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public abstract fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public abstract fun getTime ()Lkotlinx/datetime/Instant; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/DeviceMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/DeviceMessage$Companion { + public final fun error (Ljava/lang/Throwable;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/controls/api/DeviceErrorMessage; + public static synthetic fun error$default (Lspace/kscience/controls/api/DeviceMessage$Companion;Ljava/lang/Throwable;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;ILjava/lang/Object;)Lspace/kscience/controls/api/DeviceErrorMessage; + public final fun fromMeta (Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/api/DeviceMessage; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/DeviceMessageKt { + public static final fun toEnvelope (Lspace/kscience/controls/api/DeviceMessage;)Lspace/kscience/dataforge/io/Envelope; + public static final fun toMeta (Lspace/kscience/controls/api/DeviceMessage;)Lspace/kscience/dataforge/meta/Meta; +} + +public final class space/kscience/controls/api/EmptyDeviceMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/EmptyDeviceMessage$Companion; + public fun ()V + public synthetic fun (ILspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Lspace/kscience/dataforge/names/Name; + public final fun component2 ()Lspace/kscience/dataforge/names/Name; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lkotlinx/datetime/Instant; + public final fun copy (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/EmptyDeviceMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/EmptyDeviceMessage;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/EmptyDeviceMessage; + public fun equals (Ljava/lang/Object;)Z + public fun getComment ()Ljava/lang/String; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/EmptyDeviceMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/EmptyDeviceMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/EmptyDeviceMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/EmptyDeviceMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/EmptyDeviceMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/EmptyDeviceMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/GetDescriptionMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/GetDescriptionMessage$Companion; + public synthetic fun (ILspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Lspace/kscience/dataforge/names/Name; + public final fun component2 ()Lspace/kscience/dataforge/names/Name; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lkotlinx/datetime/Instant; + public final fun copy (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/GetDescriptionMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/GetDescriptionMessage;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/GetDescriptionMessage; + public fun equals (Ljava/lang/Object;)Z + public fun getComment ()Ljava/lang/String; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/GetDescriptionMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/GetDescriptionMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/GetDescriptionMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/GetDescriptionMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/GetDescriptionMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/GetDescriptionMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/PropertyChangedMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/PropertyChangedMessage$Companion; + public synthetic fun (ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component3 ()Lspace/kscience/dataforge/names/Name; + public final fun component4 ()Lspace/kscience/dataforge/names/Name; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lkotlinx/datetime/Instant; + public final fun copy (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/PropertyChangedMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/PropertyChangedMessage;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/PropertyChangedMessage; + public fun equals (Ljava/lang/Object;)Z + public fun getComment ()Ljava/lang/String; + public final fun getProperty ()Ljava/lang/String; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public final fun getValue ()Lspace/kscience/dataforge/meta/Meta; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/PropertyChangedMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/PropertyChangedMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/PropertyChangedMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/PropertyChangedMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/PropertyChangedMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/PropertyChangedMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/PropertyDescriptor { + public static final field Companion Lspace/kscience/controls/api/PropertyDescriptor$Companion; + public synthetic fun (ILjava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;ZZLkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;ZZ)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getInfo ()Ljava/lang/String; + public final fun getMetaDescriptor ()Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor; + public final fun getName ()Ljava/lang/String; + public final fun getReadable ()Z + public final fun getWritable ()Z + public final fun setInfo (Ljava/lang/String;)V + public final fun setMetaDescriptor (Lspace/kscience/dataforge/meta/descriptors/MetaDescriptor;)V + public final fun setReadable (Z)V + public final fun setWritable (Z)V + public static final synthetic fun write$Self (Lspace/kscience/controls/api/PropertyDescriptor;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/PropertyDescriptor$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/PropertyDescriptor$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/PropertyDescriptor; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/PropertyDescriptor;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/PropertyDescriptor$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/PropertyGetMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/PropertyGetMessage$Companion; + public synthetic fun (ILjava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lspace/kscience/dataforge/names/Name; + public final fun component3 ()Lspace/kscience/dataforge/names/Name; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lkotlinx/datetime/Instant; + public final fun copy (Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/PropertyGetMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/PropertyGetMessage;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/PropertyGetMessage; + public fun equals (Ljava/lang/Object;)Z + public fun getComment ()Ljava/lang/String; + public final fun getProperty ()Ljava/lang/String; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/PropertyGetMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/PropertyGetMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/PropertyGetMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/PropertyGetMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/PropertyGetMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/PropertyGetMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/PropertySetMessage : space/kscience/controls/api/DeviceMessage { + public static final field Companion Lspace/kscience/controls/api/PropertySetMessage$Companion; + public synthetic fun (ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public synthetic fun (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun changeSource (Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/api/DeviceMessage; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component3 ()Lspace/kscience/dataforge/names/Name; + public final fun component4 ()Lspace/kscience/dataforge/names/Name; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lkotlinx/datetime/Instant; + public final fun copy (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lspace/kscience/controls/api/PropertySetMessage; + public static synthetic fun copy$default (Lspace/kscience/controls/api/PropertySetMessage;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lspace/kscience/controls/api/PropertySetMessage; + public fun equals (Ljava/lang/Object;)Z + public fun getComment ()Ljava/lang/String; + public final fun getProperty ()Ljava/lang/String; + public fun getSourceDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTargetDevice ()Lspace/kscience/dataforge/names/Name; + public fun getTime ()Lkotlinx/datetime/Instant; + public final fun getValue ()Lspace/kscience/dataforge/meta/Meta; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/api/PropertySetMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/api/PropertySetMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/api/PropertySetMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/api/PropertySetMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/api/PropertySetMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/api/PropertySetMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class space/kscience/controls/api/Socket : java/io/Closeable { + public abstract fun isOpen ()Z + public abstract fun receiving ()Lkotlinx/coroutines/flow/Flow; + public abstract fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/api/SocketKt { + public static final fun connectInput (Lspace/kscience/controls/api/Socket;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/Job; +} + +public final class space/kscience/controls/manager/DeviceManager : space/kscience/dataforge/context/AbstractPlugin, space/kscience/controls/api/DeviceHub { + public static final field Companion Lspace/kscience/controls/manager/DeviceManager$Companion; + public fun ()V + public fun content (Ljava/lang/String;)Ljava/util/Map; + public fun getDevices ()Ljava/util/Map; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; + public final fun registerDevice (Lspace/kscience/dataforge/names/NameToken;Lspace/kscience/controls/api/Device;)V +} + +public final class space/kscience/controls/manager/DeviceManager$Companion : space/kscience/dataforge/context/PluginFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/manager/DeviceManager; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/manager/DeviceManagerKt { + public static final fun install (Lspace/kscience/controls/manager/DeviceManager;Ljava/lang/String;Lspace/kscience/controls/api/Device;)Lspace/kscience/controls/api/Device; + public static final fun install (Lspace/kscience/controls/manager/DeviceManager;Ljava/lang/String;Lspace/kscience/dataforge/context/Factory;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/api/Device; + public static synthetic fun install$default (Lspace/kscience/controls/manager/DeviceManager;Ljava/lang/String;Lspace/kscience/dataforge/context/Factory;Lspace/kscience/dataforge/meta/Meta;ILjava/lang/Object;)Lspace/kscience/controls/api/Device; + public static final fun installing (Lspace/kscience/controls/manager/DeviceManager;Lspace/kscience/dataforge/context/Factory;Lkotlin/jvm/functions/Function1;)Lkotlin/properties/ReadOnlyProperty; + public static synthetic fun installing$default (Lspace/kscience/controls/manager/DeviceManager;Lspace/kscience/dataforge/context/Factory;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlin/properties/ReadOnlyProperty; +} + +public final class space/kscience/controls/manager/RespondMessageKt { + public static final fun hubMessageFlow (Lspace/kscience/controls/api/DeviceHub;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/flow/Flow; + public static final fun respondHubMessage (Lspace/kscience/controls/api/DeviceHub;Lspace/kscience/controls/api/DeviceMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun respondMessage (Lspace/kscience/controls/api/Device;Lspace/kscience/dataforge/names/Name;Lspace/kscience/controls/api/DeviceMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/misc/TimeMetaKt { + public static final fun instant (Lspace/kscience/dataforge/meta/Meta;)Lkotlinx/datetime/Instant; + public static final fun toMeta (Lkotlinx/datetime/Instant;)Lspace/kscience/dataforge/meta/Meta; +} + +public abstract class space/kscience/controls/ports/AbstractPort : space/kscience/controls/ports/Port { + public fun (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;)V + public synthetic fun (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public fun getContext ()Lspace/kscience/dataforge/context/Context; + protected final fun getScope ()Lkotlinx/coroutines/CoroutineScope; + public fun isOpen ()Z + protected final fun receive ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun receiving ()Lkotlinx/coroutines/flow/Flow; + public synthetic fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun send ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected abstract fun write ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/ports/ChannelPort : space/kscience/controls/ports/AbstractPort, java/lang/AutoCloseable { + public fun (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public final fun getStartJob ()Lkotlinx/coroutines/Job; +} + +public final class space/kscience/controls/ports/ChannelPortKt { + public static final fun toArray (Ljava/nio/ByteBuffer;I)[B + public static synthetic fun toArray$default (Ljava/nio/ByteBuffer;IILjava/lang/Object;)[B +} + +public final class space/kscience/controls/ports/JvmPortsPlugin : space/kscience/dataforge/context/AbstractPlugin { + public static final field Companion Lspace/kscience/controls/ports/JvmPortsPlugin$Companion; + public fun ()V + public fun content (Ljava/lang/String;)Ljava/util/Map; + public final fun getPorts ()Lspace/kscience/controls/ports/Ports; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/ports/JvmPortsPlugin$Companion : space/kscience/dataforge/context/PluginFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/JvmPortsPlugin; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/ports/PhrasesKt { + public static final fun delimitedIncoming (Lspace/kscience/controls/ports/Port;[B)Lkotlinx/coroutines/flow/Flow; + public static final fun stringsDelimitedIncoming (Lspace/kscience/controls/ports/Port;Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; + public static final fun withDelimiter (Lkotlinx/coroutines/flow/Flow;[B)Lkotlinx/coroutines/flow/Flow; + public static final fun withStringDelimiter (Lkotlinx/coroutines/flow/Flow;Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; +} + +public abstract interface class space/kscience/controls/ports/Port : space/kscience/controls/api/Socket, space/kscience/dataforge/context/ContextAware { +} + +public abstract interface class space/kscience/controls/ports/PortFactory : space/kscience/dataforge/context/Factory { + public static final field Companion Lspace/kscience/controls/ports/PortFactory$Companion; + public static final field TYPE Ljava/lang/String; + public abstract fun getType ()Ljava/lang/String; +} + +public final class space/kscience/controls/ports/PortFactory$Companion { + public static final field TYPE Ljava/lang/String; +} + +public final class space/kscience/controls/ports/PortKt { + public static final fun send (Lspace/kscience/controls/ports/Port;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/ports/PortProxy : space/kscience/controls/ports/Port, space/kscience/dataforge/context/ContextAware { + public fun (Lspace/kscience/dataforge/context/Context;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lspace/kscience/dataforge/context/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public fun getContext ()Lspace/kscience/dataforge/context/Context; + public final fun getFactory ()Lkotlin/jvm/functions/Function1; + public fun isOpen ()Z + public fun receiving ()Lkotlinx/coroutines/flow/Flow; + public synthetic fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun send ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/ports/Ports : space/kscience/dataforge/context/AbstractPlugin { + public static final field Companion Lspace/kscience/controls/ports/Ports$Companion; + public fun ()V + public final fun buildPort (Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Port; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/ports/Ports$Companion : space/kscience/dataforge/context/PluginFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Ports; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/ports/SynchronousPort : space/kscience/controls/ports/Port { + public fun (Lspace/kscience/controls/ports/Port;Lkotlinx/coroutines/sync/Mutex;)V + public fun close ()V + public fun getContext ()Lspace/kscience/dataforge/context/Context; + public final fun getPort ()Lspace/kscience/controls/ports/Port; + public fun isOpen ()Z + public fun receiving ()Lkotlinx/coroutines/flow/Flow; + public final fun respond ([BLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public synthetic fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun send ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/ports/SynchronousPortKt { + public static final fun respondStringWithDelimiter (Lspace/kscience/controls/ports/SynchronousPort;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun respondWithDelimiter (Lspace/kscience/controls/ports/SynchronousPort;[B[BLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun synchronous (Lspace/kscience/controls/ports/Port;Lkotlinx/coroutines/sync/Mutex;)Lspace/kscience/controls/ports/SynchronousPort; + public static synthetic fun synchronous$default (Lspace/kscience/controls/ports/Port;Lkotlinx/coroutines/sync/Mutex;ILjava/lang/Object;)Lspace/kscience/controls/ports/SynchronousPort; +} + +public final class space/kscience/controls/ports/TcpPort : space/kscience/controls/ports/PortFactory { + public static final field INSTANCE Lspace/kscience/controls/ports/TcpPort; + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/ChannelPort; + public fun getType ()Ljava/lang/String; + public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILkotlin/coroutines/CoroutineContext;)Lspace/kscience/controls/ports/ChannelPort; + public static synthetic fun open$default (Lspace/kscience/controls/ports/TcpPort;Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lspace/kscience/controls/ports/ChannelPort; +} + +public final class space/kscience/controls/ports/UdpPort : space/kscience/controls/ports/PortFactory { + public static final field INSTANCE Lspace/kscience/controls/ports/UdpPort; + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/ChannelPort; + public fun getType ()Ljava/lang/String; + public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;)Lspace/kscience/controls/ports/ChannelPort; + public static synthetic fun open$default (Lspace/kscience/controls/ports/UdpPort;Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lspace/kscience/controls/ports/ChannelPort; +} + +public abstract interface class space/kscience/controls/spec/DeviceActionSpec { + public abstract fun execute (Lspace/kscience/controls/api/Device;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getDescriptor ()Lspace/kscience/controls/api/ActionDescriptor; + public abstract fun getInputConverter ()Lspace/kscience/dataforge/meta/transformations/MetaConverter; + public abstract fun getOutputConverter ()Lspace/kscience/dataforge/meta/transformations/MetaConverter; +} + +public abstract class space/kscience/controls/spec/DeviceBase : space/kscience/controls/api/Device { + public fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)V + public synthetic fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public fun execute (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getActionDescriptors ()Ljava/util/Collection; + public abstract fun getActions ()Ljava/util/Map; + public final fun getContext ()Lspace/kscience/dataforge/context/Context; + public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; + public fun getLifecycleState ()Lspace/kscience/controls/api/DeviceLifecycleState; + public synthetic fun getMessageFlow ()Lkotlinx/coroutines/flow/Flow; + public fun getMessageFlow ()Lkotlinx/coroutines/flow/SharedFlow; + public fun getMeta ()Lspace/kscience/dataforge/meta/Meta; + public abstract fun getProperties ()Ljava/util/Map; + public fun getProperty (Ljava/lang/String;)Lspace/kscience/dataforge/meta/Meta; + public fun getPropertyDescriptors ()Ljava/util/Collection; + public fun invalidate (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun open (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun readProperty (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun readPropertyOrNull (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected fun setLifecycleState (Lspace/kscience/controls/api/DeviceLifecycleState;)V + public abstract fun toString ()Ljava/lang/String; + protected final fun updateLogical (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateLogical (Lspace/kscience/controls/spec/DevicePropertySpec;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun writeProperty (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class space/kscience/controls/spec/DeviceBySpec : space/kscience/controls/spec/DeviceBase { + public fun (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)V + public synthetic fun (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public fun getActions ()Ljava/util/Map; + public fun getProperties ()Ljava/util/Map; + public final fun getSpec ()Lspace/kscience/controls/spec/DeviceSpec; + public fun open (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/controls/spec/DeviceExtensionsKt { + public static final fun doRecurring-8Mi8wO0 (Lspace/kscience/controls/api/Device;JLkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; + public static final fun readRecurring-8Mi8wO0 (Lspace/kscience/controls/api/Device;JLkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; +} + +public abstract interface class space/kscience/controls/spec/DevicePropertySpec { + public abstract fun getConverter ()Lspace/kscience/dataforge/meta/transformations/MetaConverter; + public abstract fun getDescriptor ()Lspace/kscience/controls/api/PropertyDescriptor; + public abstract fun read (Lspace/kscience/controls/api/Device;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/spec/DevicePropertySpecKt { + public static final fun execute (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/DeviceActionSpec;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun execute (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/DeviceActionSpec;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun get (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/DevicePropertySpec;)Ljava/lang/Object; + public static final fun getName (Lspace/kscience/controls/spec/DeviceActionSpec;)Ljava/lang/String; + public static final fun getName (Lspace/kscience/controls/spec/DevicePropertySpec;)Ljava/lang/String; + public static final fun invalidate (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/DevicePropertySpec;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun onPropertyChange (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/DevicePropertySpec;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/Job; + public static final fun propertyFlow (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/DevicePropertySpec;)Lkotlinx/coroutines/flow/Flow; + public static final fun read (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/DevicePropertySpec;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun readOrNull (Lspace/kscience/controls/spec/DeviceBase;Lspace/kscience/controls/spec/DevicePropertySpec;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun set (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/WritableDevicePropertySpec;Ljava/lang/Object;)Lkotlinx/coroutines/Job; + public static final fun useProperty (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/DevicePropertySpec;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; + public static final fun write (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/WritableDevicePropertySpec;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract class space/kscience/controls/spec/DeviceSpec { + public fun ()V + public final fun action (Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun action$default (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public final fun getActions ()Ljava/util/Map; + public final fun getProperties ()Ljava/util/Map; + public final fun metaAction (Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun metaAction$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public final fun mutableProperty (Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Lkotlin/properties/PropertyDelegateProvider; + public final fun mutableProperty (Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/reflect/KMutableProperty1;Lkotlin/jvm/functions/Function1;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun mutableProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun mutableProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/reflect/KMutableProperty1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public fun onClose (Lspace/kscience/controls/api/Device;)V + public fun onOpen (Lspace/kscience/controls/api/Device;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun property (Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lkotlin/properties/PropertyDelegateProvider; + public final fun property (Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/reflect/KProperty1;Lkotlin/jvm/functions/Function1;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun property$default (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun property$default (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/reflect/KProperty1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public final fun registerAction (Lspace/kscience/controls/spec/DeviceActionSpec;)Lspace/kscience/controls/spec/DeviceActionSpec; + public final fun registerProperty (Lspace/kscience/controls/spec/DevicePropertySpec;)Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun unitAction (Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun unitAction$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; +} + +public final class space/kscience/controls/spec/DeviceSpecKt { + public static final fun getUnit (Lspace/kscience/dataforge/meta/transformations/MetaConverter$Companion;)Lspace/kscience/dataforge/meta/transformations/MetaConverter; + public static final fun logicalProperty (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/jvm/functions/Function1;Ljava/lang/String;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun logicalProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/dataforge/meta/transformations/MetaConverter;Lkotlin/jvm/functions/Function1;Ljava/lang/String;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; +} + +public final class space/kscience/controls/spec/DurationConverter : space/kscience/dataforge/meta/transformations/MetaConverter { + public static final field INSTANCE Lspace/kscience/controls/spec/DurationConverter; + public synthetic fun metaToObject (Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun metaToObject-5sfh64U (Lspace/kscience/dataforge/meta/Meta;)J + public synthetic fun objectToMeta (Ljava/lang/Object;)Lspace/kscience/dataforge/meta/Meta; + public fun objectToMeta-LRDsOJo (J)Lspace/kscience/dataforge/meta/Meta; +} + +public abstract interface annotation class space/kscience/controls/spec/InternalDeviceAPI : java/lang/annotation/Annotation { +} + +public final class space/kscience/controls/spec/MiscKt { + public static final fun asMeta (D)Lspace/kscience/dataforge/meta/Meta; + public static final fun getDuration (Lspace/kscience/dataforge/meta/transformations/MetaConverter$Companion;)Lspace/kscience/dataforge/meta/transformations/MetaConverter; +} + +public final class space/kscience/controls/spec/PropertySpecDelegatesKt { + public static final fun booleanProperty (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lkotlin/properties/PropertyDelegateProvider; + public static final fun booleanProperty (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun booleanProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun booleanProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static final fun doubleProperty (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lkotlin/properties/PropertyDelegateProvider; + public static final fun doubleProperty (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun doubleProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun doubleProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static final fun metaProperty (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lkotlin/properties/PropertyDelegateProvider; + public static final fun metaProperty (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun metaProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun metaProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static final fun numberProperty (Lspace/kscience/controls/spec/DeviceSpec;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lkotlin/properties/PropertyDelegateProvider; + public static final fun numberProperty (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun numberProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun numberProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static final fun stringProperty (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lkotlin/properties/PropertyDelegateProvider; + public static final fun stringProperty (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun stringProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; + public static synthetic fun stringProperty$default (Lspace/kscience/controls/spec/DeviceSpec;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider; +} + +public final class space/kscience/controls/spec/UnitMetaConverter : space/kscience/dataforge/meta/transformations/MetaConverter { + public static final field INSTANCE Lspace/kscience/controls/spec/UnitMetaConverter; + public synthetic fun metaToObject (Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun metaToObject (Lspace/kscience/dataforge/meta/Meta;)V + public synthetic fun objectToMeta (Ljava/lang/Object;)Lspace/kscience/dataforge/meta/Meta; + public fun objectToMeta (Lkotlin/Unit;)Lspace/kscience/dataforge/meta/Meta; +} + +public abstract interface class space/kscience/controls/spec/WritableDevicePropertySpec : space/kscience/controls/spec/DevicePropertySpec { + public abstract fun write (Lspace/kscience/controls/api/Device;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + diff --git a/controls-core/build.gradle.kts b/controls-core/build.gradle.kts index 41acc60..bbe32eb 100644 --- a/controls-core/build.gradle.kts +++ b/controls-core/build.gradle.kts @@ -1,8 +1,14 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.mpp") `maven-publish` } +description = """ + Core interfaces for building a device server +""".trimIndent() + val dataforgeVersion: String by rootProject.extra kscience { @@ -22,25 +28,41 @@ kscience { readme{ + maturity = Maturity.EXPERIMENTAL + feature("device", ref = "src/commonMain/kotlin/space/kscience/controls/api/Device.kt"){ """ Device API with subscription (asynchronous and pseudo-synchronous properties) """.trimIndent() } -} -readme{ feature("deviceMessage", ref = "src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt"){ """ Specification for messages used to communicate between Controls-kt devices. """.trimIndent() } -} -readme{ feature("deviceHub", ref = "src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt"){ """ Grouping of devices into local tree-like hubs. """.trimIndent() } + + feature("deviceSpec", ref = "src/commonMain/kotlin/space/kscience/controls/spec"){ + """ + Mechanics and type-safe builders for devices. Including separation of device specification and device state. + """.trimIndent() + } + + feature("deviceManager", ref = "src/commonMain/kotlin/space/kscience/controls/manager"){ + """ + DataForge DI integration for devices. Includes device builders. + """.trimIndent() + } + + feature("ports", ref = "src/commonMain/kotlin/space/kscience/controls/ports"){ + """ + Working with asynchronous data sending and receiving raw byte arrays + """.trimIndent() + } } \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt index 5056123..4fc6365 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/Device.kt @@ -9,10 +9,21 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import space.kscience.controls.api.Device.Companion.DEVICE_TARGET import space.kscience.dataforge.context.ContextAware +import space.kscience.dataforge.context.info +import space.kscience.dataforge.context.logger import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.Type import space.kscience.dataforge.names.Name +/** + * A lifecycle state of a device + */ +public enum class DeviceLifecycleState{ + INIT, + OPEN, + CLOSED +} /** * General interface describing a managed Device. @@ -79,12 +90,16 @@ public interface Device : AutoCloseable, ContextAware, CoroutineScope { public suspend fun open(): Unit = Unit /** - * Close and terminate the device. This function does not wait for device to be closed. + * Close and terminate the device. This function does not wait for the device to be closed. */ override fun close() { + logger.info { "Device $this is closed" } cancel("The device is closed") } + @DFExperimental + public val lifecycleState: DeviceLifecycleState + public companion object { public const val DEVICE_TARGET: String = "device" } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt index 9565950..8bd7452 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceHub.kt @@ -14,26 +14,24 @@ public interface DeviceHub : Provider { override val defaultChainTarget: String get() = Device.DEVICE_TARGET - override fun content(target: String): Map { - if (target == Device.DEVICE_TARGET) { - return buildMap { - fun putAll(prefix: Name, hub: DeviceHub) { - hub.devices.forEach { - put(prefix + it.key, it.value) - } - } - - devices.forEach { - val name = it.key.asName() - put(name, it.value) - (it.value as? DeviceHub)?.let { hub -> - putAll(name, hub) - } + override fun content(target: String): Map = if (target == Device.DEVICE_TARGET) { + buildMap { + fun putAll(prefix: Name, hub: DeviceHub) { + hub.devices.forEach { + put(prefix + it.key, it.value) + } + } + + devices.forEach { + val name = it.key.asName() + put(name, it.value) + (it.value as? DeviceHub)?.let { hub -> + putAll(name, hub) } } - } else { - throw IllegalArgumentException("Target $target is not supported for $this") } + } else { + emptyMap() } public companion object diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt index 0b4131e..45d7f0b 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/api/DeviceMessage.kt @@ -1,7 +1,11 @@ +@file:OptIn(ExperimentalSerializationApi::class) + package space.kscience.controls.api import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -55,9 +59,9 @@ public data class PropertyChangedMessage( override val sourceDevice: Name = Name.EMPTY, override val targetDevice: Name? = null, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice)) } /** @@ -71,9 +75,9 @@ public data class PropertySetMessage( override val sourceDevice: Name? = null, override val targetDevice: Name, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) } /** @@ -87,9 +91,9 @@ public data class PropertyGetMessage( override val sourceDevice: Name? = null, override val targetDevice: Name, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) } /** @@ -101,9 +105,9 @@ public data class GetDescriptionMessage( override val sourceDevice: Name? = null, override val targetDevice: Name, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) } /** @@ -118,9 +122,9 @@ public data class DescriptionMessage( override val sourceDevice: Name, override val targetDevice: Name? = null, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice)) } /** @@ -137,9 +141,9 @@ public data class ActionExecuteMessage( override val sourceDevice: Name? = null, override val targetDevice: Name, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) } /** @@ -156,9 +160,9 @@ public data class ActionResultMessage( override val sourceDevice: Name, override val targetDevice: Name? = null, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice)) } /** @@ -171,9 +175,9 @@ public data class BinaryNotificationMessage( override val sourceDevice: Name, override val targetDevice: Name? = null, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice)) } /** @@ -186,9 +190,9 @@ public data class EmptyDeviceMessage( override val sourceDevice: Name? = null, override val targetDevice: Name? = null, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) } /** @@ -202,9 +206,9 @@ public data class DeviceLogMessage( override val sourceDevice: Name? = null, override val targetDevice: Name? = null, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = sourceDevice?.let(block)) } /** @@ -219,9 +223,9 @@ public data class DeviceErrorMessage( override val sourceDevice: Name, override val targetDevice: Name? = null, override val comment: String? = null, - override val time: Instant? = Clock.System.now() -) : DeviceMessage(){ - override fun changeSource(block: (Name) -> Name):DeviceMessage = copy(sourceDevice = block(sourceDevice)) + @EncodeDefault override val time: Instant? = Clock.System.now(), +) : DeviceMessage() { + override fun changeSource(block: (Name) -> Name): DeviceMessage = copy(sourceDevice = block(sourceDevice)) } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt index 871f236..cc043c7 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/manager/DeviceManager.kt @@ -37,12 +37,7 @@ public class DeviceManager : AbstractPlugin(), DeviceHub { } } - -/** - * Register and start a device built by [factory] with current [Context] and [meta]. - */ -public fun DeviceManager.install(name: String, factory: Factory, meta: Meta = Meta.EMPTY): D { - val device = factory(meta, context) +public fun DeviceManager.install(name: String, device: D): D { registerDevice(NameToken(name), device) device.launch { device.open() @@ -50,6 +45,13 @@ public fun DeviceManager.install(name: String, factory: Factory, return device } + +/** + * Register and start a device built by [factory] with current [Context] and [meta]. + */ +public fun DeviceManager.install(name: String, factory: Factory, meta: Meta = Meta.EMPTY): D = + install(name, factory(meta, context)) + /** * A delegate that initializes device on the first use */ diff --git a/controls-core/src/jvmMain/kotlin/space/kscience/controls/misc/javaTimeMeta.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/misc/timeMeta.kt similarity index 69% rename from controls-core/src/jvmMain/kotlin/space/kscience/controls/misc/javaTimeMeta.kt rename to controls-core/src/commonMain/kotlin/space/kscience/controls/misc/timeMeta.kt index 891c30b..11683d9 100644 --- a/controls-core/src/jvmMain/kotlin/space/kscience/controls/misc/javaTimeMeta.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/misc/timeMeta.kt @@ -1,18 +1,18 @@ package space.kscience.controls.misc +import kotlinx.datetime.Instant import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.long -import java.time.Instant // TODO move to core public fun Instant.toMeta(): Meta = Meta { - "seconds" put epochSecond - "nanos" put nano + "seconds" put epochSeconds + "nanos" put nanosecondsOfSecond } -public fun Meta.instant(): Instant = value?.long?.let { Instant.ofEpochMilli(it) } ?: Instant.ofEpochSecond( +public fun Meta.instant(): Instant = value?.long?.let { Instant.fromEpochMilliseconds(it) } ?: Instant.fromEpochSeconds( get("seconds")?.long ?: 0L, get("nanos")?.long ?: 0L, ) \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Port.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Port.kt index 4fad1db..1f07251 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Port.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/Port.kt @@ -18,10 +18,10 @@ public interface Port : ContextAware, Socket * A specialized factory for [Port] */ @Type(PortFactory.TYPE) -public interface PortFactory: Factory{ +public interface PortFactory : Factory { public val type: String - public companion object{ + public companion object { public const val TYPE: String = "controls.port" } } @@ -53,11 +53,9 @@ public abstract class AbstractPort( /** * Internal method to receive data synchronously */ - protected fun receive(data: ByteArray) { - scope.launch { - logger.debug { "${this@AbstractPort} RECEIVED: ${data.decodeToString()}" } - incoming.send(data) - } + protected suspend fun receive(data: ByteArray) { + logger.debug { "${this@AbstractPort} RECEIVED: ${data.decodeToString()}" } + incoming.send(data) } private val sendJob = scope.launch { @@ -82,7 +80,7 @@ public abstract class AbstractPort( /** * Raw flow of incoming data chunks. The chunks are not guaranteed to be complete phrases. * In order to form phrases, some condition should be used on top of it. - * For example [delimitedIncoming] generates phrases with fixed delimiter. + * For example [stringsDelimitedIncoming] generates phrases with fixed delimiter. */ override fun receiving(): Flow = incoming.receiveAsFlow() diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/phrases.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/phrases.kt index 21afa8d..1214d01 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/phrases.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/ports/phrases.kt @@ -48,3 +48,8 @@ public fun Flow.withStringDelimiter(delimiter: String): Flow * A flow of delimited phrases */ public fun Port.delimitedIncoming(delimiter: ByteArray): Flow = receiving().withDelimiter(delimiter) + +/** + * A flow of delimited phrases with string content + */ +public fun Port.stringsDelimitedIncoming(delimiter: String): Flow = receiving().withStringDelimiter(delimiter) diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt index 0f5436b..56f5aa9 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBase.kt @@ -1,15 +1,16 @@ package space.kscience.controls.spec -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import space.kscience.controls.api.* import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.Global +import space.kscience.dataforge.context.error +import space.kscience.dataforge.context.logger import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.misc.DFExperimental import kotlin.coroutines.CoroutineContext @@ -25,9 +26,9 @@ private suspend fun DevicePropertySpec.readMeta(device: D) private suspend fun DeviceActionSpec.executeWithMeta( device: D, - item: Meta?, + item: Meta, ): Meta? { - val arg = item?.let { inputConverter.metaToObject(item) } + val arg: I = inputConverter.metaToObject(item) ?: error("Failed to convert $item with $inputConverter") val res = execute(device, arg) return res?.let { outputConverter.objectToMeta(res) } } @@ -37,7 +38,7 @@ private suspend fun DeviceActionSpec.executeWithMeta * A base abstractions for [Device], introducing specifications for properties */ public abstract class DeviceBase( - override val context: Context = Global, + final override val context: Context, override val meta: Meta = Meta.EMPTY, ) : Device { @@ -58,9 +59,16 @@ public abstract class DeviceBase( get() = actions.values.map { it.descriptor } override val coroutineContext: CoroutineContext by lazy { - context.coroutineContext + SupervisorJob(context.coroutineContext[Job]) + context.newCoroutineContext( + SupervisorJob(context.coroutineContext[Job]) + + CoroutineName("Device $this") + + CoroutineExceptionHandler { _, throwable -> + logger.error(throwable) { "Exception in device $this job" } + } + ) } + /** * Logical state store */ @@ -146,8 +154,26 @@ public abstract class DeviceBase( override suspend fun execute(actionName: String, argument: Meta?): Meta? { val spec = actions[actionName] ?: error("Action with name $actionName not found") - return spec.executeWithMeta(self, argument) + return spec.executeWithMeta(self, argument ?: Meta.EMPTY) } + @DFExperimental + override var lifecycleState: DeviceLifecycleState = DeviceLifecycleState.INIT + protected set + + @OptIn(DFExperimental::class) + override suspend fun open() { + super.open() + lifecycleState = DeviceLifecycleState.OPEN + } + + @OptIn(DFExperimental::class) + override fun close() { + lifecycleState = DeviceLifecycleState.CLOSED + super.close() + } + + abstract override fun toString(): String + } diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBySpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBySpec.kt index e8a071c..9309224 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBySpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceBySpec.kt @@ -2,7 +2,6 @@ package space.kscience.controls.spec import space.kscience.controls.api.Device import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.Global import space.kscience.dataforge.meta.Meta /** @@ -11,7 +10,7 @@ import space.kscience.dataforge.meta.Meta */ public open class DeviceBySpec( public val spec: DeviceSpec, - context: Context = Global, + context: Context, meta: Meta = Meta.EMPTY, ) : DeviceBase(context, meta) { override val properties: Map> get() = spec.properties @@ -26,4 +25,6 @@ public open class DeviceBySpec( self.onClose() super.close() } + + override fun toString(): String = "Device(spec=$spec)" } \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt index 078a43d..cf8c741 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DevicePropertySpec.kt @@ -2,10 +2,7 @@ package space.kscience.controls.spec import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import space.kscience.controls.api.ActionDescriptor import space.kscience.controls.api.Device @@ -69,7 +66,7 @@ public interface DeviceActionSpec { /** * Execute action on a device */ - public suspend fun execute(device: D, input: I?): O? + public suspend fun execute(device: D, input: I): O } /** @@ -105,19 +102,50 @@ public operator fun D.set(propertySpec: WritableDevicePropertySp write(propertySpec, value) } +/** + * A type safe flow of property changes for given property + */ +public fun D.propertyFlow(spec: DevicePropertySpec): Flow = messageFlow + .filterIsInstance() + .filter { it.property == spec.name } + .mapNotNull { spec.converter.metaToObject(it.value) } + /** * A type safe property change listener. Uses the device [CoroutineScope]. */ -public fun Device.onPropertyChange( +public fun D.onPropertyChange( spec: DevicePropertySpec, - callback: suspend PropertyChangedMessage.(T?) -> Unit, + callback: suspend PropertyChangedMessage.(T) -> Unit, ): Job = messageFlow .filterIsInstance() .filter { it.property == spec.name } .onEach { change -> - change.callback(spec.converter.metaToObject(change.value)) + val newValue = spec.converter.metaToObject(change.value) + if (newValue != null) { + change.callback(newValue) + } }.launchIn(this) +/** + * Call [callback] on initial property value and each value change + */ +public fun D.useProperty( + spec: DevicePropertySpec, + callback: suspend (T) -> Unit, +): Job = launch { + callback(read(spec)) + messageFlow + .filterIsInstance() + .filter { it.property == spec.name } + .collect { change -> + val newValue = spec.converter.metaToObject(change.value) + if (newValue != null) { + callback(newValue) + } + } +} + + /** * Reset the logical state of a property */ @@ -128,5 +156,8 @@ public suspend fun D.invalidate(propertySpec: DevicePropertySpec D.execute(actionSpec: DeviceActionSpec, input: I? = null): O? = - actionSpec.execute(this, input) \ No newline at end of file +public suspend fun D.execute(actionSpec: DeviceActionSpec, input: I): O = + actionSpec.execute(this, input) + +public suspend fun D.execute(actionSpec: DeviceActionSpec): O = + actionSpec.execute(this, Unit) \ No newline at end of file diff --git a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt index eb5c978..d05bf30 100644 --- a/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt +++ b/controls-core/src/commonMain/kotlin/space/kscience/controls/spec/DeviceSpec.kt @@ -12,6 +12,13 @@ import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 +public object UnitMetaConverter: MetaConverter{ + override fun metaToObject(meta: Meta): Unit = Unit + + override fun objectToMeta(obj: Unit): Meta = Meta.EMPTY +} + +public val MetaConverter.Companion.unit: MetaConverter get() = UnitMetaConverter @OptIn(InternalDeviceAPI::class) public abstract class DeviceSpec { @@ -37,25 +44,6 @@ public abstract class DeviceSpec { return deviceProperty } -// public fun registerProperty( -// converter: MetaConverter, -// readOnlyProperty: KProperty1, -// descriptorBuilder: PropertyDescriptor.() -> Unit = {}, -// ): DevicePropertySpec { -// val deviceProperty = object : DevicePropertySpec { -// -// override val descriptor: PropertyDescriptor = PropertyDescriptor(readOnlyProperty.name) -// .apply(descriptorBuilder) -// -// override val converter: MetaConverter = converter -// -// override suspend fun read(device: D): T = withContext(device.coroutineContext) { -// readOnlyProperty.get(device) -// } -// } -// return registerProperty(deviceProperty) -// } - public fun property( converter: MetaConverter, readOnlyProperty: KProperty1, @@ -89,7 +77,7 @@ public abstract class DeviceSpec { val deviceProperty = object : WritableDevicePropertySpec { override val descriptor: PropertyDescriptor = PropertyDescriptor(property.name).apply { - //TODO add type from converter + //TODO add the type from converter writable = true }.apply(descriptorBuilder) @@ -165,7 +153,7 @@ public abstract class DeviceSpec { outputConverter: MetaConverter, descriptorBuilder: ActionDescriptor.() -> Unit = {}, name: String? = null, - execute: suspend D.(I?) -> O?, + execute: suspend D.(I) -> O, ): PropertyDelegateProvider, ReadOnlyProperty, DeviceActionSpec>> = PropertyDelegateProvider { _: DeviceSpec, property -> val actionName = name ?: property.name @@ -175,7 +163,7 @@ public abstract class DeviceSpec { override val inputConverter: MetaConverter = inputConverter override val outputConverter: MetaConverter = outputConverter - override suspend fun execute(device: D, input: I?): O? = withContext(device.coroutineContext) { + override suspend fun execute(device: D, input: I): O = withContext(device.coroutineContext) { device.execute(input) } } @@ -191,7 +179,7 @@ public abstract class DeviceSpec { public fun metaAction( descriptorBuilder: ActionDescriptor.() -> Unit = {}, name: String? = null, - execute: suspend D.(Meta?) -> Meta?, + execute: suspend D.(Meta) -> Meta, ): PropertyDelegateProvider, ReadOnlyProperty, DeviceActionSpec>> = action( MetaConverter.Companion.meta, @@ -209,15 +197,14 @@ public abstract class DeviceSpec { descriptorBuilder: ActionDescriptor.() -> Unit = {}, name: String? = null, execute: suspend D.() -> Unit, - ): PropertyDelegateProvider, ReadOnlyProperty, DeviceActionSpec>> = + ): PropertyDelegateProvider, ReadOnlyProperty, DeviceActionSpec>> = action( - MetaConverter.Companion.meta, - MetaConverter.Companion.meta, + MetaConverter.Companion.unit, + MetaConverter.Companion.unit, descriptorBuilder, name ) { execute() - null } } diff --git a/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/ChannelPort.kt b/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/ChannelPort.kt new file mode 100644 index 0000000..d7983f2 --- /dev/null +++ b/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/ChannelPort.kt @@ -0,0 +1,133 @@ +package space.kscience.controls.ports + +import kotlinx.coroutines.* +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.error +import space.kscience.dataforge.context.info +import space.kscience.dataforge.context.logger +import space.kscience.dataforge.meta.* +import java.net.InetSocketAddress +import java.nio.ByteBuffer +import java.nio.channels.ByteChannel +import java.nio.channels.DatagramChannel +import java.nio.channels.SocketChannel +import kotlin.coroutines.CoroutineContext + +public fun ByteBuffer.toArray(limit: Int = limit()): ByteArray { + rewind() + val response = ByteArray(limit) + get(response) + rewind() + return response +} + +/** + * A port based on nio [ByteChannel] + */ +public class ChannelPort( + context: Context, + coroutineContext: CoroutineContext = context.coroutineContext, + channelBuilder: suspend () -> ByteChannel, +) : AbstractPort(context, coroutineContext), AutoCloseable { + + private val futureChannel: Deferred = this.scope.async(Dispatchers.IO) { + channelBuilder() + } + + /** + * A handler to await port connection + */ + public val startJob: Job get() = futureChannel + + private val listenerJob = this.scope.launch(Dispatchers.IO) { + val channel = futureChannel.await() + val buffer = ByteBuffer.allocate(1024) + while (isActive) { + try { + val num = channel.read(buffer) + if (num > 0) { + receive(buffer.toArray(num)) + } + if (num < 0) cancel("The input channel is exhausted") + } catch (ex: Exception) { + logger.error(ex) { "Channel read error" } + delay(1000) + } + } + } + + override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) { + futureChannel.await().write(ByteBuffer.wrap(data)) + } + + @OptIn(ExperimentalCoroutinesApi::class) + override fun close() { + listenerJob.cancel() + if (futureChannel.isCompleted) { + futureChannel.getCompleted().close() + } else { + futureChannel.cancel() + } + super.close() + } +} + +/** + * A [PortFactory] for TCP connections + */ +public object TcpPort : PortFactory { + + override val type: String = "tcp" + + public fun open( + context: Context, + host: String, + port: Int, + coroutineContext: CoroutineContext = context.coroutineContext, + ): ChannelPort = ChannelPort(context, coroutineContext) { + SocketChannel.open(InetSocketAddress(host, port)) + } + + override fun build(context: Context, meta: Meta): ChannelPort { + val host = meta["host"].string ?: "localhost" + val port = meta["port"].int ?: error("Port value for TCP port is not defined in $meta") + return open(context, host, port) + } +} + + +/** + * A [PortFactory] for UDP connections + */ +public object UdpPort : PortFactory { + + override val type: String = "udp" + + /** + * Connect a datagram channel to a remote host/port. If [localPort] is provided, it is used to bind local port for receiving messages. + */ + public fun open( + context: Context, + remoteHost: String, + remotePort: Int, + localPort: Int? = null, + localHost: String = "localhost", + coroutineContext: CoroutineContext = context.coroutineContext, + ): ChannelPort = ChannelPort(context, coroutineContext) { + DatagramChannel.open().apply { + //bind the channel to a local port to receive messages + localPort?.let { bind(InetSocketAddress(localHost, localPort)) } + //connect to remote port to send messages + connect(InetSocketAddress(remoteHost, remotePort)) + context.logger.info { "Connected to UDP $remotePort on $remoteHost" } + } + } + + override fun build(context: Context, meta: Meta): ChannelPort { + val remoteHost by meta.string { error("Remote host is not specified") } + val remotePort by meta.number { error("Remote port is not specified") } + val localHost: String? by meta.string() + val localPort: Int? by meta.int() + return open(context, remoteHost, remotePort.toInt(), localPort, localHost ?: "localhost") + } +} \ No newline at end of file diff --git a/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/TcpPortPlugin.kt b/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/JvmPortsPlugin.kt similarity index 51% rename from controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/TcpPortPlugin.kt rename to controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/JvmPortsPlugin.kt index 592d0c3..d9d87e2 100644 --- a/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/TcpPortPlugin.kt +++ b/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/JvmPortsPlugin.kt @@ -6,21 +6,29 @@ import space.kscience.dataforge.context.PluginFactory import space.kscience.dataforge.context.PluginTag import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.parseAsName -public class TcpPortPlugin : AbstractPlugin() { +/** + * A plugin for loading JVM nio-based ports + */ +public class JvmPortsPlugin : AbstractPlugin() { + public val ports: Ports by require(Ports) override val tag: PluginTag get() = Companion.tag override fun content(target: String): Map = when(target){ - PortFactory.TYPE -> mapOf(Name.EMPTY to TcpPort) + PortFactory.TYPE -> mapOf( + TcpPort.type.parseAsName() to TcpPort, + UdpPort.type.parseAsName() to UdpPort + ) else -> emptyMap() } - public companion object : PluginFactory { + public companion object : PluginFactory { - override val tag: PluginTag = PluginTag("controls.ports.tcp", group = PluginTag.DATAFORGE_GROUP) + override val tag: PluginTag = PluginTag("controls.ports.jvm", group = PluginTag.DATAFORGE_GROUP) - override fun build(context: Context, meta: Meta): TcpPortPlugin = TcpPortPlugin() + override fun build(context: Context, meta: Meta): JvmPortsPlugin = JvmPortsPlugin() } diff --git a/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/TcpPort.kt b/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/TcpPort.kt deleted file mode 100644 index 77fec44..0000000 --- a/controls-core/src/jvmMain/kotlin/space/kscience/controls/ports/TcpPort.kt +++ /dev/null @@ -1,95 +0,0 @@ -package space.kscience.controls.ports - -import kotlinx.coroutines.* -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.error -import space.kscience.dataforge.context.logger -import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.int -import space.kscience.dataforge.meta.string -import java.net.InetSocketAddress -import java.nio.ByteBuffer -import java.nio.channels.SocketChannel -import kotlin.coroutines.CoroutineContext - -internal fun ByteBuffer.readArray(limit: Int = limit()): ByteArray { - rewind() - val response = ByteArray(limit) - get(response) - rewind() - return response -} - -public class TcpPort private constructor( - context: Context, - public val host: String, - public val port: Int, - coroutineContext: CoroutineContext = context.coroutineContext, -) : AbstractPort(context, coroutineContext), AutoCloseable { - - override fun toString(): String = "port[tcp:$host:$port]" - - private val futureChannel: Deferred = this.scope.async(Dispatchers.IO) { - SocketChannel.open(InetSocketAddress(host, port)).apply { - configureBlocking(false) - } - } - - /** - * A handler to await port connection - */ - public val startJob: Job get() = futureChannel - - private val listenerJob = this.scope.launch(Dispatchers.IO) { - val channel = futureChannel.await() - val buffer = ByteBuffer.allocate(1024) - while (isActive) { - try { - val num = channel.read(buffer) - if (num > 0) { - receive(buffer.readArray(num)) - } - if (num < 0) cancel("The input channel is exhausted") - } catch (ex: Exception) { - logger.error(ex) { "Channel read error" } - delay(1000) - } - } - } - - override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO){ - futureChannel.await().write(ByteBuffer.wrap(data)) - } - - @OptIn(ExperimentalCoroutinesApi::class) - override fun close() { - listenerJob.cancel() - if (futureChannel.isCompleted) { - futureChannel.getCompleted().close() - } else { - futureChannel.cancel() - } - super.close() - } - - public companion object : PortFactory { - - override val type: String = "tcp" - - public fun open( - context: Context, - host: String, - port: Int, - coroutineContext: CoroutineContext = context.coroutineContext, - ): TcpPort { - return TcpPort(context, host, port, coroutineContext) - } - - override fun build(context: Context, meta: Meta): Port { - val host = meta["host"].string ?: "localhost" - val port = meta["port"].int ?: error("Port value for TCP port is not defined in $meta") - return open(context, host, port) - } - } -} \ No newline at end of file diff --git a/controls-ktor-tcp/README.md b/controls-ktor-tcp/README.md deleted file mode 100644 index 94deb3b..0000000 --- a/controls-ktor-tcp/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Module controls-ktor-tcp - - - diff --git a/controls-ktor-tcp/build.gradle.kts b/controls-ktor-tcp/build.gradle.kts deleted file mode 100644 index 2089b19..0000000 --- a/controls-ktor-tcp/build.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -plugins { - id("space.kscience.gradle.jvm") -} - -val ktorVersion: String by rootProject.extra - -dependencies { - api(projects.controlsCore) - api("io.ktor:ktor-network:$ktorVersion") -} diff --git a/controls-magix-client/README.md b/controls-magix-client/README.md deleted file mode 100644 index 9e3187b..0000000 --- a/controls-magix-client/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Module controls-magix-client - -Magix service for binding controls devices (both as RPC client and server - -## Usage - -## Artifact: - -The Maven coordinates of this project are `space.kscience:controls-magix-client:0.1.1-SNAPSHOT`. - -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:controls-magix-client:0.1.1-SNAPSHOT' -} -``` -**Gradle Kotlin DSL:** -```kotlin -repositories { - maven("https://repo.kotlin.link") - mavenCentral() -} - -dependencies { - implementation("space.kscience:controls-magix-client:0.1.1-SNAPSHOT") -} -``` diff --git a/controls-magix/README.md b/controls-magix/README.md new file mode 100644 index 0000000..5473f02 --- /dev/null +++ b/controls-magix/README.md @@ -0,0 +1,29 @@ +# Module controls-magix + +Magix service for binding controls devices (both as RPC client and server) + +## Features + + - [controlsMagix](src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt) : Connect a `DeviceManage` with one or many devices to the Magix endpoint + - [DeviceClient](src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt) : A remote connector to Controls-kt device via Magix + + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-magix:0.2.0`. + +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-magix:0.2.0") +} +``` diff --git a/controls-magix/api/controls-magix.api b/controls-magix/api/controls-magix.api new file mode 100644 index 0000000..b892b3f --- /dev/null +++ b/controls-magix/api/controls-magix.api @@ -0,0 +1,199 @@ +public final class space/kscience/controls/client/ControlsMagixKt { + public static final fun getMagixFormat (Lspace/kscience/controls/manager/DeviceManager$Companion;)Lspace/kscience/magix/api/MagixFormat; + public static final fun launchMagixService (Lspace/kscience/controls/manager/DeviceManager;Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;)Lkotlinx/coroutines/Job; + public static synthetic fun launchMagixService$default (Lspace/kscience/controls/manager/DeviceManager;Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/coroutines/Job; +} + +public final class space/kscience/controls/client/DeviceClient : space/kscience/controls/api/Device { + public fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/names/Name;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)V + public fun execute (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getActionDescriptors ()Ljava/util/Collection; + public fun getContext ()Lspace/kscience/dataforge/context/Context; + public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; + public fun getLifecycleState ()Lspace/kscience/controls/api/DeviceLifecycleState; + public fun getMessageFlow ()Lkotlinx/coroutines/flow/Flow; + public fun getProperty (Ljava/lang/String;)Lspace/kscience/dataforge/meta/Meta; + public fun getPropertyDescriptors ()Ljava/util/Collection; + public fun invalidate (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun readProperty (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun writeProperty (Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/client/DeviceClientKt { + public static final fun remoteDevice (Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/dataforge/context/Context;Ljava/lang/String;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/controls/client/DeviceClient; +} + +public final class space/kscience/controls/client/DoocsAction : java/lang/Enum { + public static final field Companion Lspace/kscience/controls/client/DoocsAction$Companion; + public static final field get Lspace/kscience/controls/client/DoocsAction; + public static final field names Lspace/kscience/controls/client/DoocsAction; + public static final field set Lspace/kscience/controls/client/DoocsAction; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lspace/kscience/controls/client/DoocsAction; + public static fun values ()[Lspace/kscience/controls/client/DoocsAction; +} + +public final class space/kscience/controls/client/DoocsAction$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/client/DoocsPayload { + public static final field Companion Lspace/kscience/controls/client/DoocsPayload$Companion; + public synthetic fun (ILspace/kscience/controls/client/DoocsAction;Ljava/lang/String;Lspace/kscience/controls/client/EqData;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Lspace/kscience/controls/client/DoocsAction;Ljava/lang/String;Lspace/kscience/controls/client/EqData;)V + public final fun component1 ()Lspace/kscience/controls/client/DoocsAction; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lspace/kscience/controls/client/EqData; + public final fun copy (Lspace/kscience/controls/client/DoocsAction;Ljava/lang/String;Lspace/kscience/controls/client/EqData;)Lspace/kscience/controls/client/DoocsPayload; + public static synthetic fun copy$default (Lspace/kscience/controls/client/DoocsPayload;Lspace/kscience/controls/client/DoocsAction;Ljava/lang/String;Lspace/kscience/controls/client/EqData;ILjava/lang/Object;)Lspace/kscience/controls/client/DoocsPayload; + public fun equals (Ljava/lang/Object;)Z + public final fun getAction ()Lspace/kscience/controls/client/DoocsAction; + public final fun getAddress ()Ljava/lang/String; + public final fun getData ()Lspace/kscience/controls/client/EqData; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/client/DoocsPayload;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/client/DoocsPayload$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/client/DoocsPayload$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/client/DoocsPayload; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/client/DoocsPayload;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/client/DoocsPayload$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/client/EqData { + public static final field Companion Lspace/kscience/controls/client/EqData$Companion; + public synthetic fun (IILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Long;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Long;Ljava/lang/String;)V + public synthetic fun (ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Long;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/Integer; + public final fun component6 ()Ljava/lang/Long; + public final fun component7 ()Ljava/lang/String; + public final fun copy (ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Long;Ljava/lang/String;)Lspace/kscience/controls/client/EqData; + public static synthetic fun copy$default (Lspace/kscience/controls/client/EqData;ILjava/lang/String;Lspace/kscience/dataforge/meta/Meta;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Long;Ljava/lang/String;ILjava/lang/Object;)Lspace/kscience/controls/client/EqData; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Ljava/lang/Integer; + public final fun getEventId ()Ljava/lang/Integer; + public final fun getMessage ()Ljava/lang/String; + public final fun getTime ()Ljava/lang/Long; + public final fun getType ()Ljava/lang/String; + public final fun getTypeId ()I + public final fun getValue ()Lspace/kscience/dataforge/meta/Meta; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/client/EqData;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/client/EqData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/client/EqData$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/client/EqData; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/client/EqData;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/client/EqData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/client/TangoAction : java/lang/Enum { + public static final field Companion Lspace/kscience/controls/client/TangoAction$Companion; + public static final field exec Lspace/kscience/controls/client/TangoAction; + public static final field pipe Lspace/kscience/controls/client/TangoAction; + public static final field read Lspace/kscience/controls/client/TangoAction; + public static final field write Lspace/kscience/controls/client/TangoAction; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lspace/kscience/controls/client/TangoAction; + public static fun values ()[Lspace/kscience/controls/client/TangoAction; +} + +public final class space/kscience/controls/client/TangoAction$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/client/TangoMagixKt { + public static final field TANGO_MAGIX_FORMAT Ljava/lang/String; + public static final fun launchTangoMagix (Lspace/kscience/controls/manager/DeviceManager;Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;)Lkotlinx/coroutines/Job; + public static synthetic fun launchTangoMagix$default (Lspace/kscience/controls/manager/DeviceManager;Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/coroutines/Job; +} + +public final class space/kscience/controls/client/TangoPayload { + public static final field Companion Lspace/kscience/controls/client/TangoPayload$Companion; + public synthetic fun (ILspace/kscience/controls/client/TangoAction;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/controls/client/TangoQuality;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Ljava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Lspace/kscience/controls/client/TangoAction;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/controls/client/TangoQuality;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Ljava/util/List;)V + public synthetic fun (Lspace/kscience/controls/client/TangoAction;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/controls/client/TangoQuality;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lspace/kscience/controls/client/TangoAction; + public final fun component10 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component11 ()Ljava/util/List; + public final fun component2 ()I + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component7 ()Lspace/kscience/controls/client/TangoQuality; + public final fun component8 ()Lspace/kscience/dataforge/meta/Meta; + public final fun component9 ()Lspace/kscience/dataforge/meta/Meta; + public final fun copy (Lspace/kscience/controls/client/TangoAction;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/controls/client/TangoQuality;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Ljava/util/List;)Lspace/kscience/controls/client/TangoPayload; + public static synthetic fun copy$default (Lspace/kscience/controls/client/TangoPayload;Lspace/kscience/controls/client/TangoAction;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/controls/client/TangoQuality;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/meta/Meta;Ljava/util/List;ILjava/lang/Object;)Lspace/kscience/controls/client/TangoPayload; + public fun equals (Ljava/lang/Object;)Z + public final fun getAction ()Lspace/kscience/controls/client/TangoAction; + public final fun getArgin ()Lspace/kscience/dataforge/meta/Meta; + public final fun getArgout ()Lspace/kscience/dataforge/meta/Meta; + public final fun getData ()Lspace/kscience/dataforge/meta/Meta; + public final fun getDevice ()Ljava/lang/String; + public final fun getErrors ()Ljava/util/List; + public final fun getHost ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getQuality ()Lspace/kscience/controls/client/TangoQuality; + public final fun getTimestamp ()I + public final fun getValue ()Lspace/kscience/dataforge/meta/Meta; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/controls/client/TangoPayload;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/controls/client/TangoPayload$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/controls/client/TangoPayload$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/controls/client/TangoPayload; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/controls/client/TangoPayload;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/client/TangoPayload$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/controls/client/TangoQuality : java/lang/Enum { + public static final field ALARM Lspace/kscience/controls/client/TangoQuality; + public static final field Companion Lspace/kscience/controls/client/TangoQuality$Companion; + public static final field VALID Lspace/kscience/controls/client/TangoQuality; + public static final field WARNING Lspace/kscience/controls/client/TangoQuality; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lspace/kscience/controls/client/TangoQuality; + public static fun values ()[Lspace/kscience/controls/client/TangoQuality; +} + +public final class space/kscience/controls/client/TangoQuality$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + diff --git a/controls-magix/build.gradle.kts b/controls-magix/build.gradle.kts new file mode 100644 index 0000000..0296b8c --- /dev/null +++ b/controls-magix/build.gradle.kts @@ -0,0 +1,39 @@ +import space.kscience.gradle.Maturity + +plugins { + id("space.kscience.gradle.mpp") + `maven-publish` +} + +description = """ + Magix service for binding controls devices (both as RPC client and server) +""".trimIndent() + +kscience { + jvm() + js() + useSerialization { + json() + } + dependencies { + api(projects.magix.magixApi) + api(projects.controlsCore) + api("com.benasher44:uuid:0.8.0") + } +} + +readme { + maturity = Maturity.EXPERIMENTAL + + feature("controlsMagix", ref = "src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt"){ + """ + Connect a `DeviceManage` with one or many devices to the Magix endpoint + """.trimIndent() + } + + feature("DeviceClient", ref = "src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt"){ + """ + A remote connector to Controls-kt device via Magix + """.trimIndent() + } +} \ No newline at end of file diff --git a/controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt b/controls-magix/src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt similarity index 79% rename from controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt rename to controls-magix/src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt index 53a7704..64e8a9e 100644 --- a/controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt +++ b/controls-magix/src/commonMain/kotlin/space/kscience/controls/client/DeviceClient.kt @@ -6,18 +6,20 @@ import kotlinx.coroutines.newCoroutineContext import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import space.kscience.controls.api.* +import space.kscience.controls.manager.DeviceManager import space.kscience.dataforge.context.Context import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name import space.kscience.magix.api.MagixEndpoint -import space.kscience.magix.api.broadcast +import space.kscience.magix.api.send import space.kscience.magix.api.subscribe import kotlin.coroutines.CoroutineContext private fun stringUID() = uuid4().leastSignificantBits.toString(16) /** - * An implementation of device via RPC + * A remote accessible device that relies on connection via Magix */ public class DeviceClient( override val context: Context, @@ -95,14 +97,21 @@ public class DeviceClient( it.action == actionName && it.requestId == id }.result } + + @DFExperimental + override val lifecycleState: DeviceLifecycleState = DeviceLifecycleState.OPEN } /** - * Connect to a remote device via this client. + * Connect to a remote device via this endpoint. + * + * @param context a [Context] to run device in + * @param endpointName the name of endpoint in Magix to connect to + * @param deviceName the name of device within endpoint */ -public fun MagixEndpoint.remoteDevice(context: Context, magixTarget: String, deviceName: Name): DeviceClient { - val subscription = subscribe(controlsMagixFormat, originFilter = listOf(magixTarget)).map { it.second } +public fun MagixEndpoint.remoteDevice(context: Context, endpointName: String, deviceName: Name): DeviceClient { + val subscription = subscribe(DeviceManager.magixFormat, originFilter = listOf(endpointName)).map { it.second } return DeviceClient(context, deviceName, subscription) { - broadcast(controlsMagixFormat, it, magixTarget, id = stringUID()) + send(DeviceManager.magixFormat, it, endpointName, id = stringUID()) } } \ No newline at end of file diff --git a/controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt b/controls-magix/src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt similarity index 76% rename from controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt rename to controls-magix/src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt index f64c8e0..ed69bff 100644 --- a/controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt +++ b/controls-magix/src/commonMain/kotlin/space/kscience/controls/client/controlsMagix.kt @@ -14,31 +14,37 @@ import space.kscience.dataforge.context.logger import space.kscience.magix.api.* -public val controlsMagixFormat: MagixFormat = MagixFormat( +internal val controlsMagixFormat: MagixFormat = MagixFormat( DeviceMessage.serializer(), - setOf("controls-kt", "dataforge") + setOf("controls-kt") ) +/** + * A magix message format to work with Controls-kt data + */ +public val DeviceManager.Companion.magixFormat: MagixFormat get() = controlsMagixFormat + internal fun generateId(request: MagixMessage): String = if (request.id != null) { "${request.id}.response" } else { - "df[${request.payload.hashCode()}" + "controls[${request.payload.hashCode().toString(16)}" } /** * Communicate with server in [Magix format](https://github.com/waltz-controls/rfc/tree/master/1) */ -public fun DeviceManager.connectToMagix( +public fun DeviceManager.launchMagixService( endpoint: MagixEndpoint, endpointID: String = controlsMagixFormat.defaultFormat, ): Job = context.launch { endpoint.subscribe(controlsMagixFormat, targetFilter = listOf(endpointID)).onEach { (request, payload) -> val responsePayload = respondHubMessage(payload) if (responsePayload != null) { - endpoint.broadcast( + endpoint.send( format = controlsMagixFormat, - origin = endpointID, payload = responsePayload, + source = endpointID, + target = request.sourceEndpoint, id = generateId(request), parentId = request.id ) @@ -48,10 +54,10 @@ public fun DeviceManager.connectToMagix( }.launchIn(this) hubMessageFlow(this).onEach { payload -> - endpoint.broadcast( + endpoint.send( format = controlsMagixFormat, - origin = endpointID, payload = payload, + source = endpointID, id = "df[${payload.hashCode()}]" ) }.catch { error -> diff --git a/controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/doocsMagix.kt b/controls-magix/src/commonMain/kotlin/space/kscience/controls/client/doocsMagix.kt similarity index 100% rename from controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/doocsMagix.kt rename to controls-magix/src/commonMain/kotlin/space/kscience/controls/client/doocsMagix.kt diff --git a/controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/tangoMagix.kt b/controls-magix/src/commonMain/kotlin/space/kscience/controls/client/tangoMagix.kt similarity index 94% rename from controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/tangoMagix.kt rename to controls-magix/src/commonMain/kotlin/space/kscience/controls/client/tangoMagix.kt index 48ae3c5..d0bdda4 100644 --- a/controls-magix-client/src/commonMain/kotlin/space/kscience/controls/client/tangoMagix.kt +++ b/controls-magix/src/commonMain/kotlin/space/kscience/controls/client/tangoMagix.kt @@ -75,12 +75,12 @@ public fun DeviceManager.launchTangoMagix( ): Job { suspend fun respond(request: MagixMessage, payload: TangoPayload, payloadBuilder: (TangoPayload) -> TangoPayload) { - endpoint.broadcast( + endpoint.send( tangoMagixFormat, + payload = payloadBuilder(payload), + source = endpointID, id = generateId(request), - parentId = request.id, - origin = endpointID, - payload = payloadBuilder(payload) + parentId = request.id ) } @@ -125,12 +125,12 @@ public fun DeviceManager.launchTangoMagix( } } catch (ex: Exception) { logger.error(ex) { "Error while responding to message" } - endpoint.broadcast( + endpoint.send( tangoMagixFormat, + payload = payload.copy(quality = TangoQuality.WARNING), + source = endpointID, id = generateId(request), - parentId = request.id, - origin = endpointID, - payload = payload.copy(quality = TangoQuality.WARNING) + parentId = request.id ) } }.launchIn(this) diff --git a/controls-modbus/README.md b/controls-modbus/README.md index f6d2335..78d515a 100644 --- a/controls-modbus/README.md +++ b/controls-modbus/README.md @@ -2,3 +2,30 @@ A plugin for Controls-kt device server on top of modbus-rtu/modbus-tcp protocols +## Features + + - [modbusRegistryMap](src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt) : Type-safe modbus registry map. Allows to define both single-register and multi-register entries (using DataForge IO). +Automatically checks consistency. + - [modbusProcessImage](src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt) : Binding of slave (server) modbus device to Controls-kt device + - [modbusDevice](src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt) : A device with additional methods to work with modbus registers. + + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-modbus:0.2.0`. + +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-modbus:0.2.0") +} +``` diff --git a/controls-modbus/api/controls-modbus.api b/controls-modbus/api/controls-modbus.api new file mode 100644 index 0000000..36ad071 --- /dev/null +++ b/controls-modbus/api/controls-modbus.api @@ -0,0 +1,166 @@ +public final class space/kscience/controls/modbus/DeviceProcessImageBuilder { + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Lkotlin/jvm/functions/Function2;)Lcom/ghgande/j2mod/modbus/procimg/ObservableDigitalOut; + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Lspace/kscience/controls/spec/WritableDevicePropertySpec;)Lcom/ghgande/j2mod/modbus/procimg/ObservableDigitalOut; + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput;Lkotlin/jvm/functions/Function2;)Lcom/ghgande/j2mod/modbus/procimg/DigitalIn; + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput;Lspace/kscience/controls/spec/DevicePropertySpec;)Lcom/ghgande/j2mod/modbus/procimg/DigitalIn; + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange;Lspace/kscience/controls/spec/WritableDevicePropertySpec;)V + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Lkotlin/jvm/functions/Function2;)Lcom/ghgande/j2mod/modbus/procimg/ObservableRegister; + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Lspace/kscience/controls/spec/WritableDevicePropertySpec;)Lcom/ghgande/j2mod/modbus/procimg/ObservableRegister; + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRange;Lspace/kscience/controls/spec/DevicePropertySpec;)V + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister;Lkotlin/jvm/functions/Function2;)Lcom/ghgande/j2mod/modbus/procimg/SimpleInputRegister; + public final fun bind (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister;Lspace/kscience/controls/spec/DevicePropertySpec;)Lcom/ghgande/j2mod/modbus/procimg/SimpleInputRegister; + public static synthetic fun bind$default (Lspace/kscience/controls/modbus/DeviceProcessImageBuilder;Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/ObservableDigitalOut; + public static synthetic fun bind$default (Lspace/kscience/controls/modbus/DeviceProcessImageBuilder;Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/DigitalIn; + public static synthetic fun bind$default (Lspace/kscience/controls/modbus/DeviceProcessImageBuilder;Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/ObservableRegister; + public static synthetic fun bind$default (Lspace/kscience/controls/modbus/DeviceProcessImageBuilder;Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/SimpleInputRegister; + public final fun bindAction (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Lkotlin/jvm/functions/Function3;)Lcom/ghgande/j2mod/modbus/procimg/ObservableDigitalOut; + public final fun bindAction (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange;Lkotlin/jvm/functions/Function3;)Ljava/util/List; + public final fun bindAction (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Lkotlin/jvm/functions/Function3;)Lcom/ghgande/j2mod/modbus/procimg/ObservableRegister; + public final fun getImage ()Lcom/ghgande/j2mod/modbus/procimg/ProcessImageImplementation; +} + +public final class space/kscience/controls/modbus/DeviceProcessImageKt { + public static final fun bindProcessImage (Lspace/kscience/controls/api/Device;ZLkotlin/jvm/functions/Function1;)Lcom/ghgande/j2mod/modbus/procimg/ProcessImage; + public static synthetic fun bindProcessImage$default (Lspace/kscience/controls/api/Device;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/ghgande/j2mod/modbus/procimg/ProcessImage; +} + +public abstract interface class space/kscience/controls/modbus/ModbusDevice : space/kscience/controls/api/Device { + public abstract fun getClientId ()I + public abstract fun getMaster ()Lcom/ghgande/j2mod/modbus/facade/AbstractModbusMaster; + public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Z + public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Z + public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; + public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Ljava/lang/Object;Lkotlin/reflect/KProperty;)S + public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRange;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; + public fun getValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister;Ljava/lang/Object;Lkotlin/reflect/KProperty;)S + public fun setValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Ljava/lang/Object;Lkotlin/reflect/KProperty;Z)V + public fun setValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange;Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V + public fun setValue (Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;Ljava/lang/Object;Lkotlin/reflect/KProperty;S)V +} + +public class space/kscience/controls/modbus/ModbusDeviceBySpec : space/kscience/controls/spec/DeviceBySpec, space/kscience/controls/modbus/ModbusDevice { + public fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/controls/spec/DeviceSpec;ILcom/ghgande/j2mod/modbus/facade/AbstractModbusMaster;ZLspace/kscience/dataforge/meta/Meta;)V + public synthetic fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/controls/spec/DeviceSpec;ILcom/ghgande/j2mod/modbus/facade/AbstractModbusMaster;ZLspace/kscience/dataforge/meta/Meta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public fun getClientId ()I + public fun getMaster ()Lcom/ghgande/j2mod/modbus/facade/AbstractModbusMaster; + public fun open (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/modbus/ModbusDeviceKt { + public static final fun modbusDoubleRegister (Lspace/kscience/controls/modbus/ModbusDevice;I)Lkotlin/properties/ReadWriteProperty; + public static final fun modbusRegister (Lspace/kscience/controls/modbus/ModbusDevice;I)Lkotlin/properties/ReadWriteProperty; + public static final fun readCoil (Lspace/kscience/controls/modbus/ModbusDevice;I)Z + public static final fun readCoils (Lspace/kscience/controls/modbus/ModbusDevice;II)Lcom/ghgande/j2mod/modbus/util/BitVector; + public static final fun readDoubleInput (Lspace/kscience/controls/modbus/ModbusDevice;I)D + public static final fun readDoubleRegister (Lspace/kscience/controls/modbus/ModbusDevice;I)D + public static final fun readHoldingRegister (Lspace/kscience/controls/modbus/ModbusDevice;I)S + public static final fun readHoldingRegisters (Lspace/kscience/controls/modbus/ModbusDevice;II)Ljava/util/List; + public static final fun readHoldingRegistersToBuffer (Lspace/kscience/controls/modbus/ModbusDevice;II)Ljava/nio/ByteBuffer; + public static final fun readHoldingRegistersToPacket (Lspace/kscience/controls/modbus/ModbusDevice;II)Lio/ktor/utils/io/core/ByteReadPacket; + public static final fun readInputDiscrete (Lspace/kscience/controls/modbus/ModbusDevice;I)Z + public static final fun readInputDiscretes (Lspace/kscience/controls/modbus/ModbusDevice;II)Lcom/ghgande/j2mod/modbus/util/BitVector; + public static final fun readInputRegister (Lspace/kscience/controls/modbus/ModbusDevice;I)S + public static final fun readInputRegisters (Lspace/kscience/controls/modbus/ModbusDevice;II)Ljava/util/List; + public static final fun readInputRegistersToBuffer (Lspace/kscience/controls/modbus/ModbusDevice;II)Ljava/nio/ByteBuffer; + public static final fun readInputRegistersToPacket (Lspace/kscience/controls/modbus/ModbusDevice;II)Lio/ktor/utils/io/core/ByteReadPacket; + public static final fun writeCoil (Lspace/kscience/controls/modbus/ModbusDevice;IZ)V + public static final fun writeCoil (Lspace/kscience/controls/modbus/ModbusDevice;Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;Z)V + public static final fun writeCoils (Lspace/kscience/controls/modbus/ModbusDevice;I[Z)V + public static final fun writeHoldingRegister (Lspace/kscience/controls/modbus/ModbusDevice;IS)I + public static final fun writeHoldingRegister (Lspace/kscience/controls/modbus/ModbusDevice;Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister;S)I + public static final fun writeHoldingRegisters (Lspace/kscience/controls/modbus/ModbusDevice;ILjava/nio/ByteBuffer;)I + public static final fun writeHoldingRegisters (Lspace/kscience/controls/modbus/ModbusDevice;I[S)I + public static final fun writeShortRegister (Lspace/kscience/controls/modbus/ModbusDevice;IS)V +} + +public final class space/kscience/controls/modbus/ModbusHub : java/lang/AutoCloseable, space/kscience/controls/api/DeviceHub { + public fun (Lspace/kscience/dataforge/context/Context;Lkotlin/jvm/functions/Function0;Ljava/util/Map;)V + public fun close ()V + public final fun getContext ()Lspace/kscience/dataforge/context/Context; + public fun getDevices ()Ljava/util/Map; + public final fun getMaster ()Lcom/ghgande/j2mod/modbus/facade/AbstractModbusMaster; + public final fun getMasterBuilder ()Lkotlin/jvm/functions/Function0; + public final fun getSpecs ()Ljava/util/Map; +} + +public abstract class space/kscience/controls/modbus/ModbusRegistryKey { + public abstract fun getAddress ()I + public fun getCount ()I +} + +public final class space/kscience/controls/modbus/ModbusRegistryKey$Coil : space/kscience/controls/modbus/ModbusRegistryKey { + public fun (I)V + public final fun component1 ()I + public final fun copy (I)Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil; + public static synthetic fun copy$default (Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil;IILjava/lang/Object;)Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil; + public fun equals (Ljava/lang/Object;)Z + public fun getAddress ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput : space/kscience/controls/modbus/ModbusRegistryKey { + public fun (I)V + public final fun component1 ()I + public final fun copy (I)Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput; + public static synthetic fun copy$default (Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput;IILjava/lang/Object;)Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput; + public fun equals (Ljava/lang/Object;)Z + public fun getAddress ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/controls/modbus/ModbusRegistryKey$HoldingRange : space/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister { + public fun (IILspace/kscience/dataforge/io/IOFormat;)V + public fun getCount ()I + public final fun getEndAddress ()I + public final fun getFormat ()Lspace/kscience/dataforge/io/IOFormat; + public fun toString ()Ljava/lang/String; +} + +public class space/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister : space/kscience/controls/modbus/ModbusRegistryKey { + public fun (I)V + public fun getAddress ()I + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/controls/modbus/ModbusRegistryKey$InputRange : space/kscience/controls/modbus/ModbusRegistryKey$InputRegister { + public fun (IILspace/kscience/dataforge/io/IOFormat;)V + public fun getCount ()I + public final fun getEndAddress ()I + public final fun getFormat ()Lspace/kscience/dataforge/io/IOFormat; + public fun toString ()Ljava/lang/String; +} + +public class space/kscience/controls/modbus/ModbusRegistryKey$InputRegister : space/kscience/controls/modbus/ModbusRegistryKey { + public fun (I)V + public fun getAddress ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class space/kscience/controls/modbus/ModbusRegistryMap { + public static final field Companion Lspace/kscience/controls/modbus/ModbusRegistryMap$Companion; + public fun ()V + protected final fun coil (ILjava/lang/String;)Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil; + public static synthetic fun coil$default (Lspace/kscience/controls/modbus/ModbusRegistryMap;ILjava/lang/String;ILjava/lang/Object;)Lspace/kscience/controls/modbus/ModbusRegistryKey$Coil; + protected final fun discrete (ILjava/lang/String;)Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput; + public static synthetic fun discrete$default (Lspace/kscience/controls/modbus/ModbusRegistryMap;ILjava/lang/String;ILjava/lang/Object;)Lspace/kscience/controls/modbus/ModbusRegistryKey$DiscreteInput; + public final fun getEntries ()Ljava/util/Map; + protected final fun input (IILspace/kscience/dataforge/io/IOFormat;Ljava/lang/String;)Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRange; + protected final fun input (ILjava/lang/String;)Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister; + public static synthetic fun input$default (Lspace/kscience/controls/modbus/ModbusRegistryMap;IILspace/kscience/dataforge/io/IOFormat;Ljava/lang/String;ILjava/lang/Object;)Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRange; + public static synthetic fun input$default (Lspace/kscience/controls/modbus/ModbusRegistryMap;ILjava/lang/String;ILjava/lang/Object;)Lspace/kscience/controls/modbus/ModbusRegistryKey$InputRegister; + protected final fun register (IILspace/kscience/dataforge/io/IOFormat;Ljava/lang/String;)Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange; + protected final fun register (ILjava/lang/String;)Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister; + protected final fun register (Lspace/kscience/controls/modbus/ModbusRegistryKey;Ljava/lang/String;)Lspace/kscience/controls/modbus/ModbusRegistryKey; + public static synthetic fun register$default (Lspace/kscience/controls/modbus/ModbusRegistryMap;IILspace/kscience/dataforge/io/IOFormat;Ljava/lang/String;ILjava/lang/Object;)Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRange; + public static synthetic fun register$default (Lspace/kscience/controls/modbus/ModbusRegistryMap;ILjava/lang/String;ILjava/lang/Object;)Lspace/kscience/controls/modbus/ModbusRegistryKey$HoldingRegister; +} + +public final class space/kscience/controls/modbus/ModbusRegistryMap$Companion { + public final fun print (Lspace/kscience/controls/modbus/ModbusRegistryMap;Ljava/lang/Appendable;)V + public static synthetic fun print$default (Lspace/kscience/controls/modbus/ModbusRegistryMap$Companion;Lspace/kscience/controls/modbus/ModbusRegistryMap;Ljava/lang/Appendable;ILjava/lang/Object;)V + public final fun validate (Lspace/kscience/controls/modbus/ModbusRegistryMap;)V +} + diff --git a/controls-modbus/build.gradle.kts b/controls-modbus/build.gradle.kts index 8734460..aee64d5 100644 --- a/controls-modbus/build.gradle.kts +++ b/controls-modbus/build.gradle.kts @@ -1,5 +1,8 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.jvm") + `maven-publish` } description = """ @@ -11,3 +14,26 @@ dependencies { api(projects.controlsCore) api("com.ghgande:j2mod:3.1.1") } + +readme{ + maturity = Maturity.EXPERIMENTAL + + feature("modbusRegistryMap", ref = "src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt"){ + """ + Type-safe modbus registry map. Allows to define both single-register and multi-register entries (using DataForge IO). + Automatically checks consistency. + """.trimIndent() + } + + feature("modbusProcessImage", ref = "src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt"){ + """ + Binding of slave (server) modbus device to Controls-kt device + """.trimIndent() + } + + feature("modbusDevice", ref = "src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt"){ + """ + A device with additional methods to work with modbus registers. + """.trimIndent() + } +} \ No newline at end of file diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt new file mode 100644 index 0000000..2f56a8a --- /dev/null +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/DeviceProcessImage.kt @@ -0,0 +1,219 @@ +package space.kscience.controls.modbus + +import com.ghgande.j2mod.modbus.procimg.* +import io.ktor.utils.io.core.buildPacket +import io.ktor.utils.io.core.readByteBuffer +import io.ktor.utils.io.core.writeShort +import kotlinx.coroutines.launch +import space.kscience.controls.api.Device +import space.kscience.controls.spec.DevicePropertySpec +import space.kscience.controls.spec.WritableDevicePropertySpec +import space.kscience.controls.spec.set +import space.kscience.controls.spec.useProperty + + +public class DeviceProcessImageBuilder 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: WritableDevicePropertySpec, + ): ObservableDigitalOut = bind(key) { coil -> + coil.addObserver { _, _ -> + device[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, + ): 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, + ): 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: WritableDevicePropertySpec, + ): ObservableRegister = bind(key) { register -> + register.addObserver { _, _ -> + device[propertySpec] = register.toShort() + } + device.useProperty(propertySpec) { value -> + register.setValue(value) + } + } + + public fun bind(key: ModbusRegistryKey.InputRange, propertySpec: DevicePropertySpec) { + val registers = List(key.count) { + SimpleInputRegister() + } + + registers.forEachIndexed { index, register -> + image.addInputRegister(key.address + index, register) + } + + device.useProperty(propertySpec) { value -> + val packet = buildPacket { + key.format.writeObject(this, value) + }.readByteBuffer() + registers.forEachIndexed { index, register -> + register.setValue(packet.getShort(index * 2)) + } + } + } + + public fun bind(key: ModbusRegistryKey.HoldingRange, propertySpec: WritableDevicePropertySpec) { + val registers = List(key.count) { + ObservableRegister() + } + 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) + } + + device.useProperty(propertySpec) { value -> + val packet = buildPacket { + key.format.writeObject(this, value) + }.readByteBuffer() + registers.forEachIndexed { index, observableRegister -> + observableRegister.setValue(packet.getShort(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 bindAction( + key: ModbusRegistryKey.HoldingRange, + action: suspend D.(T) -> Unit, + ): List { + val registers = List(key.count) { + ObservableRegister() + } + 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) + } + + return registers + } + +} + +/** + * Bind the device to Modbus slave (server) image. + */ +public fun D.bindProcessImage( + openOnBind: Boolean = true, + binding: DeviceProcessImageBuilder.() -> Unit, +): ProcessImage { + val image = SimpleProcessImage() + DeviceProcessImageBuilder(this, image).apply(binding) + if (openOnBind) { + launch { + open() + } + } + return image +} \ No newline at end of file diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt index 04bb6a0..ea4330c 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDevice.kt @@ -3,8 +3,12 @@ package space.kscience.controls.modbus import com.ghgande.j2mod.modbus.facade.AbstractModbusMaster import com.ghgande.j2mod.modbus.procimg.InputRegister import com.ghgande.j2mod.modbus.procimg.Register -import com.ghgande.j2mod.modbus.procimg.SimpleRegister +import com.ghgande.j2mod.modbus.procimg.SimpleInputRegister import com.ghgande.j2mod.modbus.util.BitVector +import io.ktor.utils.io.core.ByteReadPacket +import io.ktor.utils.io.core.buildPacket +import io.ktor.utils.io.core.readByteBuffer +import io.ktor.utils.io.core.writeShort import space.kscience.controls.api.Device import java.nio.ByteBuffer import kotlin.properties.ReadWriteProperty @@ -22,37 +26,91 @@ public interface ModbusDevice : Device { public val clientId: Int /** - * The OPC-UA client initialized on first use + * The modubus master connector */ public val master: AbstractModbusMaster + + public operator fun ModbusRegistryKey.Coil.getValue(thisRef: Any?, property: KProperty<*>): Boolean = + readCoil(address) + + public operator fun ModbusRegistryKey.Coil.setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { + writeCoil(address, value) + } + + public operator fun ModbusRegistryKey.DiscreteInput.getValue(thisRef: Any?, property: KProperty<*>): Boolean = + readInputDiscrete(address) + + public operator fun ModbusRegistryKey.InputRegister.getValue(thisRef: Any?, property: KProperty<*>): Short = + readInputRegister(address) + + public operator fun ModbusRegistryKey.InputRange.getValue(thisRef: Any?, property: KProperty<*>): T { + val packet = readInputRegistersToPacket(address, count) + return format.readObject(packet) + } + + + public operator fun ModbusRegistryKey.HoldingRegister.getValue(thisRef: Any?, property: KProperty<*>): Short = + readHoldingRegister(address) + + public operator fun ModbusRegistryKey.HoldingRegister.setValue( + thisRef: Any?, + property: KProperty<*>, + value: Short, + ) { + writeHoldingRegister(address, value) + } + + public operator fun ModbusRegistryKey.HoldingRange.getValue(thisRef: Any?, property: KProperty<*>): T { + val packet = readInputRegistersToPacket(address, count) + return format.readObject(packet) + } + + public operator fun ModbusRegistryKey.HoldingRange.setValue( + thisRef: Any?, + property: KProperty<*>, + value: T, + ) { + val buffer = buildPacket { + format.writeObject(this, value) + }.readByteBuffer() + writeHoldingRegisters(address, buffer) + } + } /** * Read multiple sequential modbus coils (bit-values) */ -public fun ModbusDevice.readCoils(ref: Int, count: Int): BitVector = - master.readCoils(clientId, ref, count) +public fun ModbusDevice.readCoils(address: Int, count: Int): BitVector = + master.readCoils(clientId, address, count) -public fun ModbusDevice.readCoil(ref: Int): Boolean = - master.readCoils(clientId, ref, 1).getBit(0) +public fun ModbusDevice.readCoil(address: Int): Boolean = + master.readCoils(clientId, address, 1).getBit(0) -public fun ModbusDevice.writeCoils(ref: Int, values: BooleanArray) { +public fun ModbusDevice.writeCoils(address: Int, values: BooleanArray) { val bitVector = BitVector(values.size) values.forEachIndexed { index, value -> bitVector.setBit(index, value) } - master.writeMultipleCoils(clientId, ref, bitVector) + master.writeMultipleCoils(clientId, address, bitVector) } -public fun ModbusDevice.writeCoil(ref: Int, value: Boolean) { - master.writeCoil(clientId, ref, value) +public fun ModbusDevice.writeCoil(address: Int, value: Boolean) { + master.writeCoil(clientId, address, value) } -public fun ModbusDevice.readInputDiscretes(ref: Int, count: Int): BitVector = - master.readInputDiscretes(clientId, ref, count) +public fun ModbusDevice.writeCoil(key: ModbusRegistryKey.Coil, value: Boolean) { + master.writeCoil(clientId, key.address, value) +} -public fun ModbusDevice.readInputRegisters(ref: Int, count: Int): List = - master.readInputRegisters(clientId, ref, count).toList() +public fun ModbusDevice.readInputDiscretes(address: Int, count: Int): BitVector = + master.readInputDiscretes(clientId, address, count) + +public fun ModbusDevice.readInputDiscrete(address: Int): Boolean = + master.readInputDiscretes(clientId, address, 1).getBit(0) + +public fun ModbusDevice.readInputRegisters(address: Int, count: Int): List = + master.readInputRegisters(clientId, address, count).toList() private fun Array.toBuffer(): ByteBuffer { val buffer: ByteBuffer = ByteBuffer.allocate(size * 2) @@ -64,83 +122,88 @@ private fun Array.toBuffer(): ByteBuffer { return buffer } -public fun ModbusDevice.readInputRegistersToBuffer(ref: Int, count: Int): ByteBuffer = - master.readInputRegisters(clientId, ref, count).toBuffer() - -public fun ModbusDevice.readDoubleInput(ref: Int): Double = - readInputRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble() - -public fun ModbusDevice.readShortInput(ref: Int): Short = - readInputRegisters(ref, 1).first().toShort() - -public fun ModbusDevice.readHoldingRegisters(ref: Int, count: Int): List = - master.readMultipleRegisters(clientId, ref, count).toList() - -public fun ModbusDevice.readHoldingRegistersToBuffer(ref: Int, count: Int): ByteBuffer = - master.readMultipleRegisters(clientId, ref, count).toBuffer() - -public fun ModbusDevice.readDoubleRegister(ref: Int): Double = - readHoldingRegistersToBuffer(ref, Double.SIZE_BYTES).getDouble() - -public fun ModbusDevice.readShortRegister(ref: Int): Short = - readHoldingRegisters(ref, 1).first().toShort() - -public fun ModbusDevice.writeHoldingRegisters(ref: Int, values: ShortArray): Int = - master.writeMultipleRegisters( - clientId, - ref, - Array(values.size) { SimpleRegister().apply { setValue(values[it]) } } - ) - -public fun ModbusDevice.writeHoldingRegister(ref: Int, value: Short): Int = - master.writeSingleRegister( - clientId, - ref, - SimpleRegister().apply { setValue(value) } - ) - -public fun ModbusDevice.writeHoldingRegisters(ref: Int, buffer: ByteBuffer): Int { - val array = ShortArray(buffer.limit().floorDiv(2)) { buffer.getShort(it * 2) } - - return writeHoldingRegisters(ref, array) -} - -public fun ModbusDevice.writeShortRegister(ref: Int, value: Short) { - master.writeSingleRegister(ref, SimpleRegister().apply { setValue(value) }) -} - -public fun ModbusDevice.modBusRegister( - ref: Int, -): ReadWriteProperty = object : ReadWriteProperty { - override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Short = readShortRegister(ref) - - override fun setValue(thisRef: ModbusDevice, property: KProperty<*>, value: Short) { - writeHoldingRegister(ref, value) +private fun Array.toPacket(): ByteReadPacket = buildPacket { + forEach { value -> + writeShort(value.toShort()) } } -public fun ModbusDevice.modBusDoubleRegister( - ref: Int, +public fun ModbusDevice.readInputRegistersToBuffer(address: Int, count: Int): ByteBuffer = + master.readInputRegisters(clientId, address, count).toBuffer() + +public fun ModbusDevice.readInputRegistersToPacket(address: Int, count: Int): ByteReadPacket = + master.readInputRegisters(clientId, address, count).toPacket() + +public fun ModbusDevice.readDoubleInput(address: Int): Double = + readInputRegistersToBuffer(address, Double.SIZE_BYTES).getDouble() + +public fun ModbusDevice.readInputRegister(address: Int): Short = + readInputRegisters(address, 1).first().toShort() + +public fun ModbusDevice.readHoldingRegisters(address: Int, count: Int): List = + master.readMultipleRegisters(clientId, address, count).toList() + +/** + * Read a number of registers to a [ByteBuffer] + * @param address of a register + * @param count number of 2-bytes registers to read. Buffer size is 2*[count] + */ +public fun ModbusDevice.readHoldingRegistersToBuffer(address: Int, count: Int): ByteBuffer = + master.readMultipleRegisters(clientId, address, count).toBuffer() + +public fun ModbusDevice.readHoldingRegistersToPacket(address: Int, count: Int): ByteReadPacket = + master.readMultipleRegisters(clientId, address, count).toPacket() + +public fun ModbusDevice.readDoubleRegister(address: Int): Double = + readHoldingRegistersToBuffer(address, Double.SIZE_BYTES).getDouble() + +public fun ModbusDevice.readHoldingRegister(address: Int): Short = + readHoldingRegisters(address, 1).first().toShort() + +public fun ModbusDevice.writeHoldingRegisters(address: Int, values: ShortArray): Int = + master.writeMultipleRegisters( + clientId, + address, + Array(values.size) { SimpleInputRegister(values[it].toInt()) } + ) + +public fun ModbusDevice.writeHoldingRegister(address: Int, value: Short): Int = + master.writeSingleRegister( + clientId, + address, + SimpleInputRegister(value.toInt()) + ) + +public fun ModbusDevice.writeHoldingRegister(key: ModbusRegistryKey.HoldingRegister, value: Short): Int = + writeHoldingRegister(key.address, value) + +public fun ModbusDevice.writeHoldingRegisters(address: Int, buffer: ByteBuffer): Int { + val array: ShortArray = ShortArray(buffer.limit().floorDiv(2)) { buffer.getShort(it * 2) } + + return writeHoldingRegisters(address, array) +} + +public fun ModbusDevice.writeShortRegister(address: Int, value: Short) { + master.writeSingleRegister(address, SimpleInputRegister(value.toInt())) +} + +public fun ModbusDevice.modbusRegister( + address: Int, +): ReadWriteProperty = object : ReadWriteProperty { + override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Short = readHoldingRegister(address) + + override fun setValue(thisRef: ModbusDevice, property: KProperty<*>, value: Short) { + writeHoldingRegister(address, value) + } +} + +public fun ModbusDevice.modbusDoubleRegister( + address: Int, ): ReadWriteProperty = object : ReadWriteProperty { - override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Double = readDoubleRegister(ref) + override fun getValue(thisRef: ModbusDevice, property: KProperty<*>): Double = readDoubleRegister(address) override fun setValue(thisRef: ModbusDevice, property: KProperty<*>, value: Double) { val buffer = ByteBuffer.allocate(Double.SIZE_BYTES).apply { putDouble(value) } - writeHoldingRegisters(ref, buffer) + writeHoldingRegisters(address, buffer) } } - - -// -//public inline fun ModbusDevice.opcDouble( -//): ReadWriteProperty = ma -// -//public inline fun ModbusDeviceBySpec<*>.opcInt( -// nodeId: NodeId, -// magAge: Double = 1.0, -//): ReadWriteProperty = opc(nodeId, MetaConverter.int, magAge) -// -//public inline fun ModbusDeviceBySpec<*>.opcString( -// nodeId: NodeId, -// magAge: Double = 1.0, -//): ReadWriteProperty = opc(nodeId, MetaConverter.string, magAge) diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt index e1acd4b..916187f 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusDeviceBySpec.kt @@ -17,8 +17,21 @@ public open class ModbusDeviceBySpec( spec: DeviceSpec, override val clientId: Int, override val master: AbstractModbusMaster, + private val disposeMasterOnClose: Boolean = true, meta: Meta = Meta.EMPTY, -) : ModbusDevice, DeviceBySpec(spec, context, meta) +) : ModbusDevice, DeviceBySpec(spec, context, meta){ + override suspend fun open() { + master.connect() + super.open() + } + + override fun close() { + if(disposeMasterOnClose){ + master.disconnect() + } + super.close() + } +} public class ModbusHub( diff --git a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt index e365a88..a6fc894 100644 --- a/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt +++ b/controls-modbus/src/main/kotlin/space/kscience/controls/modbus/ModbusRegistryMap.kt @@ -1,44 +1,163 @@ package space.kscience.controls.modbus +import space.kscience.dataforge.io.IOFormat + public sealed class ModbusRegistryKey { + public abstract val address: Int + public open val count: Int = 1 + + /** * Read-only boolean value */ - public class Coil(public val address: Int) : ModbusRegistryKey() { - init { - require(address in 1..9999) { "Coil address must be in 1..9999 range" } - } - } + public data class Coil(override val address: Int) : ModbusRegistryKey() /** * Read-write boolean value */ - public class DiscreteInput(public val address: Int) : ModbusRegistryKey() { - init { - require(address in 10001..19999) { "DiscreteInput address must be in 10001..19999 range" } - } + public data class DiscreteInput(override val address: Int) : ModbusRegistryKey() + + /** + * Read-only binary value + */ + public open class InputRegister(override val address: Int) : ModbusRegistryKey() { + override fun toString(): String = "InputRegister(address=$address)" } - public class InputRegister(public val address: Int) : ModbusRegistryKey() { - init { - require(address in 20001..29999) { "InputRegister address must be in 20001..29999 range" } - } + public class InputRange( + address: Int, + override val count: Int, + public val format: IOFormat, + ) : InputRegister(address) { + public val endAddress: Int get() = address + count + override fun toString(): String = "InputRange(count=$count, format=$format)" + + } - public class HoldingRegister(public val address: Int) : ModbusRegistryKey() { - init { - require(address in 30001..39999) { "HoldingRegister address must be in 30001..39999 range" } - } + public open class HoldingRegister(override val address: Int) : ModbusRegistryKey() { + override fun toString(): String = "HoldingRegister(address=$address)" + } + + public class HoldingRange( + address: Int, + override val count: Int, + public val format: IOFormat, + ) : HoldingRegister(address) { + public val endAddress: Int get() = address + count + override fun toString(): String = "HoldingRange(count=$count, format=$format)" + + } } public abstract class ModbusRegistryMap { - protected fun coil(address: Int): ModbusRegistryKey.Coil = ModbusRegistryKey.Coil(address) - protected fun discrete(address: Int): ModbusRegistryKey.DiscreteInput = ModbusRegistryKey.DiscreteInput(address) + private val _entries: MutableMap = mutableMapOf() - protected fun input(address: Int): ModbusRegistryKey.InputRegister = ModbusRegistryKey.InputRegister(address) + public val entries: Map get() = _entries - protected fun register(address: Int): ModbusRegistryKey.HoldingRegister = ModbusRegistryKey.HoldingRegister(address) + protected fun register(key: T, description: String): T { + _entries[key] = description + return key + } + + protected fun coil(address: Int, description: String = ""): ModbusRegistryKey.Coil = + register(ModbusRegistryKey.Coil(address), description) + + + protected fun discrete(address: Int, description: String = ""): ModbusRegistryKey.DiscreteInput = + register(ModbusRegistryKey.DiscreteInput(address), description) + + protected fun input(address: Int, description: String = ""): ModbusRegistryKey.InputRegister = + register(ModbusRegistryKey.InputRegister(address), description) + + protected fun input( + address: Int, + count: Int, + reader: IOFormat, + description: String = "", + ): ModbusRegistryKey.InputRange = + register(ModbusRegistryKey.InputRange(address, count, reader), description) + + protected fun register(address: Int, description: String = ""): ModbusRegistryKey.HoldingRegister = + register(ModbusRegistryKey.HoldingRegister(address), description) + + protected fun register( + address: Int, + count: Int, + format: IOFormat, + description: String = "", + ): ModbusRegistryKey.HoldingRange = + register(ModbusRegistryKey.HoldingRange(address, count, format), description) + + public companion object { + public fun validate(map: ModbusRegistryMap) { + var lastCoil: ModbusRegistryKey.Coil? = null + var lastDiscreteInput: ModbusRegistryKey.DiscreteInput? = null + var lastInput: ModbusRegistryKey.InputRegister? = null + var lastRegister: ModbusRegistryKey.HoldingRegister? = null + map.entries.keys.sortedBy { it.address }.forEach { key -> + when (key) { + is ModbusRegistryKey.Coil -> if (lastCoil?.let { key.address >= it.address + it.count } != false) { + lastCoil = key + } else { + error("Key $lastCoil overlaps with key $key") + } + + is ModbusRegistryKey.DiscreteInput -> if (lastDiscreteInput?.let { key.address >= it.address + it.count } != false) { + lastDiscreteInput = key + } else { + error("Key $lastDiscreteInput overlaps with key $key") + } + + is ModbusRegistryKey.InputRegister -> if (lastInput?.let { key.address >= it.address + it.count } != false) { + lastInput = key + } else { + error("Key $lastInput overlaps with key $key") + } + + is ModbusRegistryKey.HoldingRegister -> if (lastRegister?.let { key.address >= it.address + it.count } != false) { + lastRegister = key + } else { + error("Key $lastRegister overlaps with key $key") + } + } + } + } + + private val ModbusRegistryKey.sectionNumber + get() = when (this) { + is ModbusRegistryKey.Coil -> 1 + is ModbusRegistryKey.DiscreteInput -> 2 + is ModbusRegistryKey.HoldingRegister -> 4 + is ModbusRegistryKey.InputRegister -> 3 + } + + public fun print(map: ModbusRegistryMap, to: Appendable = System.out) { + validate(map) + map.entries.entries + .sortedWith( + Comparator.comparingInt?> { it.key.sectionNumber } + .thenComparingInt { it.key.address } + ) + .forEach { (key, description) -> + val typeString = when (key) { + is ModbusRegistryKey.Coil -> "Coil" + is ModbusRegistryKey.DiscreteInput -> "Discrete" + is ModbusRegistryKey.HoldingRegister -> "Register" + is ModbusRegistryKey.InputRegister -> "Input" + } + val rangeString = if (key.count == 1) { + key.address.toString() + } else { + "${key.address} - ${key.address + key.count}" + } + to.appendLine("${typeString}\t$rangeString\t$description") + } + } + } } + + diff --git a/controls-opcua/README.md b/controls-opcua/README.md index 3accf5b..8cdd373 100644 --- a/controls-opcua/README.md +++ b/controls-opcua/README.md @@ -1,4 +1,29 @@ # Module controls-opcua +A client and server connectors for OPC-UA via Eclipse Milo + +## Features + + - [opcuaClient](src/main/kotlin/space/kscience/controls/opcua/client) : Connect a Controls-kt as a client to OPC UA server + - [opcuaServer](src/main/kotlin/space/kscience/controls/opcua/server) : Create an OPC UA server on top of Controls-kt device (or device hub) +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-opcua:0.2.0`. + +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-opcua:0.2.0") +} +``` diff --git a/controls-opcua/api/controls-opcua.api b/controls-opcua/api/controls-opcua.api new file mode 100644 index 0000000..6ec2ef5 --- /dev/null +++ b/controls-opcua/api/controls-opcua.api @@ -0,0 +1,82 @@ +public final class space/kscience/controls/opcua/client/MetaBsdParser : org/eclipse/milo/opcua/binaryschema/parser/BsdParser { + public fun ()V +} + +public final class space/kscience/controls/opcua/client/MetaBsdParserKt { + public static final fun toMeta (Lorg/eclipse/milo/opcua/stack/core/types/builtin/Variant;Lorg/eclipse/milo/opcua/stack/core/serialization/SerializationContext;)Lspace/kscience/dataforge/meta/Meta; +} + +public final class space/kscience/controls/opcua/client/MiloConfiguration : space/kscience/dataforge/meta/Scheme { + public static final field Companion Lspace/kscience/controls/opcua/client/MiloConfiguration$Companion; + public fun ()V + public final fun getEndpointUrl ()Ljava/lang/String; + public final fun getSecurityPolicy ()Lorg/eclipse/milo/opcua/stack/core/security/SecurityPolicy; + public final fun getUsername ()Lspace/kscience/controls/opcua/client/MiloUsername; + public final fun setEndpointUrl (Ljava/lang/String;)V + public final fun setSecurityPolicy (Lorg/eclipse/milo/opcua/stack/core/security/SecurityPolicy;)V + public final fun setUsername (Lspace/kscience/controls/opcua/client/MiloUsername;)V +} + +public final class space/kscience/controls/opcua/client/MiloConfiguration$Companion : space/kscience/dataforge/meta/SchemeSpec { +} + +public abstract class space/kscience/controls/opcua/client/MiloIdentity : space/kscience/dataforge/meta/Scheme { +} + +public final class space/kscience/controls/opcua/client/MiloUsername : space/kscience/controls/opcua/client/MiloIdentity { + public static final field Companion Lspace/kscience/controls/opcua/client/MiloUsername$Companion; + public fun ()V + public final fun getPassword ()Ljava/lang/String; + public final fun getUsername ()Ljava/lang/String; + public final fun setPassword (Ljava/lang/String;)V + public final fun setUsername (Ljava/lang/String;)V +} + +public final class space/kscience/controls/opcua/client/MiloUsername$Companion : space/kscience/dataforge/meta/SchemeSpec { +} + +public abstract interface class space/kscience/controls/opcua/client/OpcUaDevice : space/kscience/controls/api/Device { + public abstract fun getClient ()Lorg/eclipse/milo/opcua/sdk/client/OpcUaClient; +} + +public class space/kscience/controls/opcua/client/OpcUaDeviceBySpec : space/kscience/controls/spec/DeviceBySpec, space/kscience/controls/opcua/client/OpcUaDevice { + public fun (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/controls/opcua/client/MiloConfiguration;Lspace/kscience/dataforge/context/Context;)V + public synthetic fun (Lspace/kscience/controls/spec/DeviceSpec;Lspace/kscience/controls/opcua/client/MiloConfiguration;Lspace/kscience/dataforge/context/Context;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public fun getClient ()Lorg/eclipse/milo/opcua/sdk/client/OpcUaClient; +} + +public final class space/kscience/controls/opcua/client/OpcUaDeviceKt { + public static final fun opcDouble (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;D)Lkotlin/properties/ReadWriteProperty; + public static synthetic fun opcDouble$default (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;DILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty; + public static final fun opcInt (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;D)Lkotlin/properties/ReadWriteProperty; + public static synthetic fun opcInt$default (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;DILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty; + public static final fun opcString (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;D)Lkotlin/properties/ReadWriteProperty; + public static synthetic fun opcString$default (Lspace/kscience/controls/opcua/client/OpcUaDevice;Lorg/eclipse/milo/opcua/stack/core/types/builtin/NodeId;DILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty; +} + +public final class space/kscience/controls/opcua/server/DeviceNameSpace : org/eclipse/milo/opcua/sdk/server/api/ManagedNamespaceWithLifecycle { + public static final field Companion Lspace/kscience/controls/opcua/server/DeviceNameSpace$Companion; + public static final field NAMESPACE_URI Ljava/lang/String; + public fun (Lorg/eclipse/milo/opcua/sdk/server/OpcUaServer;Lspace/kscience/controls/manager/DeviceManager;)V + public final fun getDeviceManager ()Lspace/kscience/controls/manager/DeviceManager; + public fun onDataItemsCreated (Ljava/util/List;)V + public fun onDataItemsDeleted (Ljava/util/List;)V + public fun onDataItemsModified (Ljava/util/List;)V + public fun onMonitoringModeChanged (Ljava/util/List;)V +} + +public final class space/kscience/controls/opcua/server/DeviceNameSpace$Companion { +} + +public final class space/kscience/controls/opcua/server/DeviceNameSpaceKt { + public static final fun get (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/api/PropertyDescriptor;)Lspace/kscience/dataforge/meta/Meta; + public static final fun read (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/api/PropertyDescriptor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun serveDevices (Lorg/eclipse/milo/opcua/sdk/server/OpcUaServer;Lspace/kscience/controls/manager/DeviceManager;)Lspace/kscience/controls/opcua/server/DeviceNameSpace; +} + +public final class space/kscience/controls/opcua/server/ServerUtilsKt { + public static final fun OpcUaServer (Lkotlin/jvm/functions/Function1;)Lorg/eclipse/milo/opcua/sdk/server/OpcUaServer; + public static final fun endpoint (Lorg/eclipse/milo/opcua/sdk/server/api/config/OpcUaServerConfigBuilder;Lkotlin/jvm/functions/Function1;)V +} + diff --git a/controls-opcua/build.gradle.kts b/controls-opcua/build.gradle.kts index 02510e2..e7e1da8 100644 --- a/controls-opcua/build.gradle.kts +++ b/controls-opcua/build.gradle.kts @@ -1,10 +1,17 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.jvm") + `maven-publish` } +description = """ + A client and server connectors for OPC-UA via Eclipse Milo +""".trimIndent() + val ktorVersion: String by rootProject.extra -val miloVersion: String = "0.6.9" +val miloVersion: String = "0.6.10" dependencies { api(projects.controlsCore) @@ -16,3 +23,19 @@ dependencies { testImplementation(spclibs.kotlinx.coroutines.test) } + +readme{ + maturity = Maturity.EXPERIMENTAL + + feature("opcuaClient", ref = "src/main/kotlin/space/kscience/controls/opcua/client"){ + """ + Connect a Controls-kt as a client to OPC UA server + """.trimIndent() + } + + feature("opcuaServer", ref = "src/main/kotlin/space/kscience/controls/opcua/server"){ + """ + Create an OPC UA server on top of Controls-kt device (or device hub) + """.trimIndent() + } +} diff --git a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MetaBsdParser.kt b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MetaBsdParser.kt index 74257af..574343e 100644 --- a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MetaBsdParser.kt +++ b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MetaBsdParser.kt @@ -1,5 +1,7 @@ package space.kscience.controls.opcua.client +import kotlinx.datetime.toJavaInstant +import kotlinx.datetime.toKotlinInstant import org.eclipse.milo.opcua.binaryschema.AbstractCodec import org.eclipse.milo.opcua.binaryschema.parser.BsdParser import org.eclipse.milo.opcua.stack.core.UaSerializationException @@ -66,7 +68,7 @@ internal fun opcToMeta(value: Any?): Meta = when (value) { is Boolean -> Meta(value.asValue()) is String -> Meta(value.asValue()) is Char -> Meta(value.toString().asValue()) - is DateTime -> value.javaInstant.toMeta() + is DateTime -> value.javaInstant.toKotlinInstant().toMeta() is UUID -> Meta(value.toString().asValue()) is QualifiedName -> Meta { "namespaceIndex" put value.namespaceIndex @@ -79,9 +81,9 @@ internal fun opcToMeta(value: Any?): Meta = when (value) { 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.sourceTime?.javaInstant?.let { "sourceTime" put it.toKotlinInstant().toMeta() } value.sourcePicoseconds?.let { "sourcePicoseconds" put Meta(it.asValue()) } - value.serverTime?.javaInstant?.let { "serverTime" put it.toMeta() } + value.serverTime?.javaInstant?.let { "serverTime" put it.toKotlinInstant().toMeta() } value.serverPicoseconds?.let { "serverPicoseconds" put Meta(it.asValue()) } } is ByteString -> Meta(value.bytesOrEmpty().asValue()) @@ -145,7 +147,7 @@ internal class MetaStructureCodec( "Float" -> member.value?.numberOrNull?.toFloat() "Double" -> member.value?.numberOrNull?.toDouble() "String" -> member.string - "DateTime" -> DateTime(member.instant()) + "DateTime" -> DateTime(member.instant().toJavaInstant()) "Guid" -> member.string?.let { UUID.fromString(it) } "ByteString" -> member.value?.list?.let { list -> ByteString(list.map { it.number.toByte() }.toByteArray()) diff --git a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/OpcUaDeviceBySpec.kt b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/OpcUaDeviceBySpec.kt index 821b693..eb9b688 100644 --- a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/OpcUaDeviceBySpec.kt +++ b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/OpcUaDeviceBySpec.kt @@ -1,7 +1,7 @@ package space.kscience.controls.opcua.client import org.eclipse.milo.opcua.sdk.client.OpcUaClient -import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider +import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy import space.kscience.controls.api.Device @@ -12,12 +12,12 @@ import space.kscience.dataforge.context.Global import space.kscience.dataforge.meta.* -public sealed class MiloIdentity: Scheme() +public sealed class MiloIdentity : Scheme() public class MiloUsername : MiloIdentity() { - public var username: String by string{ error("Username not defined") } - public var password: String by string{ error("Password not defined") } + public var username: String by string { error("Username not defined") } + public var password: String by string { error("Password not defined") } public companion object : SchemeSpec(::MiloUsername) } @@ -35,6 +35,12 @@ public class MiloConfiguration : Scheme() { public var securityPolicy: SecurityPolicy by enum(SecurityPolicy.None) + internal fun configureClient(builder: OpcUaClientConfigBuilder) { + username?.let { + builder.setIdentityProvider(UsernameProvider(it.username, it.password)) + } + } + public companion object : SchemeSpec(::MiloConfiguration) } @@ -51,9 +57,7 @@ public open class OpcUaDeviceBySpec( context.createOpcUaClient( config.endpointUrl, securityPolicy = config.securityPolicy, - identityProvider = config.username?.let { - UsernameProvider(it.username,it.password) - } ?: AnonymousProvider() + opcClientConfig = { config.configureClient(this) } ).apply { connect().get() } diff --git a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/miloClient.kt b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/miloClient.kt index face6cc..8149bd9 100644 --- a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/miloClient.kt +++ b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/miloClient.kt @@ -3,7 +3,6 @@ package space.kscience.controls.opcua.client 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.stack.client.security.DefaultClientCertificateValidator import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy @@ -18,14 +17,14 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.* -internal fun T?.toOptional(): Optional = if(this == null) Optional.empty() else Optional.of(this) +internal fun T?.toOptional(): Optional = Optional.ofNullable(this) internal fun Context.createOpcUaClient( endpointUrl: String, //"opc.tcp://localhost:12686/milo" securityPolicy: SecurityPolicy = SecurityPolicy.Basic256Sha256, - identityProvider: IdentityProvider = AnonymousProvider(), - endpointFilter: (EndpointDescription?) -> Boolean = { securityPolicy.uri == it?.securityPolicyUri } + endpointFilter: (EndpointDescription?) -> Boolean = { securityPolicy.uri == it?.securityPolicyUri }, + opcClientConfig: OpcUaClientConfigBuilder.() -> Unit, ): OpcUaClient { val securityTempDir: Path = Paths.get(System.getProperty("java.io.tmpdir"), "client", "security") @@ -47,14 +46,15 @@ internal fun Context.createOpcUaClient( } ) { configBuilder: OpcUaClientConfigBuilder -> configBuilder - .setApplicationName(LocalizedText.english("Controls.kt")) - .setApplicationUri("urn:ru.mipt:npm:controls:opcua") + .setApplicationName(LocalizedText.english("Controls-kt")) + .setApplicationUri("urn:space.kscience:controls:opcua") // .setKeyPair(loader.getClientKeyPair()) // .setCertificate(loader.getClientCertificate()) // .setCertificateChain(loader.getClientCertificateChain()) .setCertificateValidator(certificateValidator) - .setIdentityProvider(identityProvider) + .setIdentityProvider(AnonymousProvider()) .setRequestTimeout(uint(5000)) + .apply(opcClientConfig) .build() } // .apply { diff --git a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt index 6b8e44c..010c2c0 100644 --- a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt +++ b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt @@ -12,6 +12,7 @@ import org.eclipse.milo.opcua.sdk.server.api.ManagedNamespaceWithLifecycle import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode import org.eclipse.milo.opcua.sdk.server.nodes.UaNode +import org.eclipse.milo.opcua.sdk.server.nodes.UaNodeContext import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel import org.eclipse.milo.opcua.stack.core.AttributeId @@ -27,7 +28,6 @@ import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.MetaSerializer import space.kscience.dataforge.meta.ValueType import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.plus @@ -50,25 +50,7 @@ public class DeviceNameSpace( lifecycleManager.addLifecycle(subscription) lifecycleManager.addStartupTask { - deviceManager.devices.forEach { (deviceName, device) -> - val tokenAsString = deviceName.toString() - val deviceFolder = UaFolderNode( - this.nodeContext, - newNodeId(tokenAsString), - newQualifiedName(tokenAsString), - LocalizedText.english(tokenAsString) - ) - deviceFolder.addReference( - Reference( - deviceFolder.nodeId, - Identifiers.Organizes, - Identifiers.ObjectsFolder.expanded(), - false - ) - ) - deviceFolder.registerDeviceNodes(deviceName.asName(), device) - this.nodeManager.addNode(deviceFolder) - } + nodeContext.registerHub(deviceManager, Name.EMPTY) } lifecycleManager.addLifecycle(object : Lifecycle { @@ -88,7 +70,7 @@ public class DeviceNameSpace( val node: UaVariableNode = UaVariableNode.UaVariableNodeBuilder(nodeContext).apply { - //for now use DF path as id + //for now, use DF paths as ids nodeId = newNodeId("${deviceName.tokens.joinToString("/")}/$propertyName") when { descriptor.readable && descriptor.writable -> { @@ -161,15 +143,15 @@ public class DeviceNameSpace( } //recursively add sub-devices if (device is DeviceHub) { - registerHub(device, deviceName) + nodeContext.registerHub(device, deviceName) } } - private fun UaNode.registerHub(hub: DeviceHub, namePrefix: Name) { + private fun UaNodeContext.registerHub(hub: DeviceHub, namePrefix: Name) { hub.devices.forEach { (deviceName, device) -> val tokenAsString = deviceName.toString() val deviceFolder = UaFolderNode( - this.nodeContext, + this, newNodeId(tokenAsString), newQualifiedName(tokenAsString), LocalizedText.english(tokenAsString) diff --git a/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt b/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt index e9b4c69..eedafe7 100644 --- a/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt +++ b/controls-opcua/src/test/kotlin/space/kscience/controls/opcua/client/OpcUaClientTest.kt @@ -8,6 +8,7 @@ import space.kscience.controls.spec.DeviceSpec import space.kscience.controls.spec.doubleProperty import space.kscience.controls.spec.read import space.kscience.dataforge.meta.transformations.MetaConverter +import kotlin.test.Ignore class OpcUaClientTest { class DemoOpcUaDevice(config: MiloConfiguration) : OpcUaDeviceBySpec(DemoOpcUaDevice, config) { @@ -37,6 +38,7 @@ class OpcUaClientTest { @OptIn(ExperimentalCoroutinesApi::class) @Test + @Ignore fun testReadDouble() = runTest { DemoOpcUaDevice.build().use{ println(it.read(DemoOpcUaDevice.randomDouble)) diff --git a/controls-pi/README.md b/controls-pi/README.md new file mode 100644 index 0000000..cd9ee0a --- /dev/null +++ b/controls-pi/README.md @@ -0,0 +1,23 @@ +# Module controls-pi + +Utils to work with controls-kt on Raspberry pi + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-pi:0.2.0`. + +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-pi:0.2.0") +} +``` diff --git a/controls-pi/api/controls-pi.api b/controls-pi/api/controls-pi.api new file mode 100644 index 0000000..2fdaf2d --- /dev/null +++ b/controls-pi/api/controls-pi.api @@ -0,0 +1,28 @@ +public final class space/kscience/controls/pi/PiPlugin : space/kscience/dataforge/context/AbstractPlugin { + public static final field Companion Lspace/kscience/controls/pi/PiPlugin$Companion; + public fun ()V + public final fun getPorts ()Lspace/kscience/controls/ports/Ports; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/pi/PiPlugin$Companion : space/kscience/dataforge/context/PluginFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/pi/PiPlugin; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/pi/PiSerialPort : space/kscience/controls/ports/AbstractPort { + public static final field Companion Lspace/kscience/controls/pi/PiSerialPort$Companion; + public fun (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function0;)V + public synthetic fun (Lspace/kscience/dataforge/context/Context;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public final fun getSerialBuilder ()Lkotlin/jvm/functions/Function0; +} + +public final class space/kscience/controls/pi/PiSerialPort$Companion : space/kscience/controls/ports/PortFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Port; + public fun getType ()Ljava/lang/String; + public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lspace/kscience/controls/pi/PiSerialPort; +} + diff --git a/controls-pi/build.gradle.kts b/controls-pi/build.gradle.kts new file mode 100644 index 0000000..a763396 --- /dev/null +++ b/controls-pi/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("space.kscience.gradle.jvm") + `maven-publish` +} + +description = """ + Utils to work with controls-kt on Raspberry pi +""".trimIndent() + +dependencies{ + api(project(":controls-core")) + api("com.pi4j:pi4j-ktx:2.4.0") // Kotlin DSL + api("com.pi4j:pi4j-core:2.3.0") + api("com.pi4j:pi4j-plugin-raspberrypi:2.3.0") + api("com.pi4j:pi4j-plugin-pigpio:2.3.0") +} \ No newline at end of file diff --git a/controls-pi/src/main/kotlin/space/kscience/controls/pi/PiPlugin.kt b/controls-pi/src/main/kotlin/space/kscience/controls/pi/PiPlugin.kt new file mode 100644 index 0000000..547a142 --- /dev/null +++ b/controls-pi/src/main/kotlin/space/kscience/controls/pi/PiPlugin.kt @@ -0,0 +1,22 @@ +package space.kscience.controls.pi + +import space.kscience.controls.ports.Ports +import space.kscience.dataforge.context.AbstractPlugin +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.PluginFactory +import space.kscience.dataforge.context.PluginTag +import space.kscience.dataforge.meta.Meta + +public class PiPlugin : AbstractPlugin() { + public val ports: Ports by require(Ports) + + override val tag: PluginTag get() = Companion.tag + + public companion object : PluginFactory { + + override val tag: PluginTag = PluginTag("controls.ports.pi", group = PluginTag.DATAFORGE_GROUP) + + override fun build(context: Context, meta: Meta): PiPlugin = PiPlugin() + + } +} \ No newline at end of file diff --git a/controls-pi/src/main/kotlin/space/kscience/controls/pi/PiSerialPort.kt b/controls-pi/src/main/kotlin/space/kscience/controls/pi/PiSerialPort.kt new file mode 100644 index 0000000..4924b8d --- /dev/null +++ b/controls-pi/src/main/kotlin/space/kscience/controls/pi/PiSerialPort.kt @@ -0,0 +1,75 @@ +package space.kscience.controls.pi + +import com.pi4j.Pi4J +import com.pi4j.io.serial.Baud +import com.pi4j.io.serial.Serial +import com.pi4j.io.serial.SerialConfigBuilder +import com.pi4j.ktx.io.serial +import kotlinx.coroutines.* +import space.kscience.controls.ports.AbstractPort +import space.kscience.controls.ports.Port +import space.kscience.controls.ports.PortFactory +import space.kscience.controls.ports.toArray +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.error +import space.kscience.dataforge.context.logger +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.enum +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.string +import java.nio.ByteBuffer +import kotlin.coroutines.CoroutineContext + +public class PiSerialPort( + context: Context, + coroutineContext: CoroutineContext = context.coroutineContext, + public val serialBuilder: () -> Serial, +) : AbstractPort(context, coroutineContext) { + + private val serial: Serial by lazy { serialBuilder() } + + + private val listenerJob = this.scope.launch(Dispatchers.IO) { + val buffer = ByteBuffer.allocate(1024) + while (isActive) { + try { + val num = serial.read(buffer) + if (num > 0) { + receive(buffer.toArray(num)) + } + if (num < 0) cancel("The input channel is exhausted") + } catch (ex: Exception) { + logger.error(ex) { "Channel read error" } + delay(1000) + } + } + } + + override suspend fun write(data: ByteArray): Unit = withContext(Dispatchers.IO) { + serial.write(data) + } + + override fun close() { + listenerJob.cancel() + serial.close() + } + + public companion object : PortFactory { + override val type: String get() = "pi" + + public fun open(context: Context, device: String, block: SerialConfigBuilder.() -> Unit): PiSerialPort = + PiSerialPort(context) { + Pi4J.newAutoContext().serial(device, block) + } + + override fun build(context: Context, meta: Meta): Port = PiSerialPort(context) { + val device: String = meta["device"].string ?: error("Device name not defined") + val baudRate: Baud = meta["baudRate"].enum() ?: Baud._9600 + Pi4J.newAutoContext().serial(device) { + baud8N1(baudRate) + } + } + + } +} + diff --git a/controls-ports-ktor/README.md b/controls-ports-ktor/README.md new file mode 100644 index 0000000..7f935f9 --- /dev/null +++ b/controls-ports-ktor/README.md @@ -0,0 +1,23 @@ +# Module controls-ports-ktor + +Implementation of byte ports on top os ktor-io asynchronous API + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:controls-ports-ktor:0.2.0`. + +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") + mavenCentral() +} + +dependencies { + implementation("space.kscience:controls-ports-ktor:0.2.0") +} +``` diff --git a/controls-ports-ktor/api/controls-ports-ktor.api b/controls-ports-ktor/api/controls-ports-ktor.api new file mode 100644 index 0000000..ecce329 --- /dev/null +++ b/controls-ports-ktor/api/controls-ports-ktor.api @@ -0,0 +1,47 @@ +public final class space/kscience/controls/ports/KtorPortsPlugin : space/kscience/dataforge/context/AbstractPlugin { + public static final field Companion Lspace/kscience/controls/ports/KtorPortsPlugin$Companion; + public fun ()V + public fun content (Ljava/lang/String;)Ljava/util/Map; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/ports/KtorPortsPlugin$Companion : space/kscience/dataforge/context/PluginFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/KtorPortsPlugin; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/ports/KtorTcpPort : space/kscience/controls/ports/AbstractPort, java/io/Closeable { + public static final field Companion Lspace/kscience/controls/ports/KtorTcpPort$Companion; + public fun close ()V + public final fun getHost ()Ljava/lang/String; + public final fun getPort ()I + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/controls/ports/KtorTcpPort$Companion : space/kscience/controls/ports/PortFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Port; + public fun getType ()Ljava/lang/String; + public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILkotlin/coroutines/CoroutineContext;)Lspace/kscience/controls/ports/KtorTcpPort; + public static synthetic fun open$default (Lspace/kscience/controls/ports/KtorTcpPort$Companion;Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lspace/kscience/controls/ports/KtorTcpPort; +} + +public final class space/kscience/controls/ports/KtorUdpPort : space/kscience/controls/ports/AbstractPort, java/io/Closeable { + public static final field Companion Lspace/kscience/controls/ports/KtorUdpPort$Companion; + public fun close ()V + public final fun getLocalHost ()Ljava/lang/String; + public final fun getLocalPort ()Ljava/lang/Integer; + public final fun getRemoteHost ()Ljava/lang/String; + public final fun getRemotePort ()I + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/controls/ports/KtorUdpPort$Companion : space/kscience/controls/ports/PortFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Port; + public fun getType ()Ljava/lang/String; + public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;)Lspace/kscience/controls/ports/KtorUdpPort; + public static synthetic fun open$default (Lspace/kscience/controls/ports/KtorUdpPort$Companion;Lspace/kscience/dataforge/context/Context;Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lspace/kscience/controls/ports/KtorUdpPort; +} + diff --git a/controls-ports-ktor/build.gradle.kts b/controls-ports-ktor/build.gradle.kts new file mode 100644 index 0000000..4c8ad9a --- /dev/null +++ b/controls-ports-ktor/build.gradle.kts @@ -0,0 +1,21 @@ +import space.kscience.gradle.Maturity + +plugins { + id("space.kscience.gradle.jvm") + `maven-publish` +} + +description = """ + Implementation of byte ports on top os ktor-io asynchronous API +""".trimIndent() + +val ktorVersion: String by rootProject.extra + +dependencies { + api(projects.controlsCore) + api("io.ktor:ktor-network:$ktorVersion") +} + +readme{ + maturity = Maturity.PROTOTYPE +} diff --git a/controls-ktor-tcp/src/main/kotlin/space/kscience/controls/ports/KtorTcpPortPlugin.kt b/controls-ports-ktor/src/main/kotlin/space/kscience/controls/ports/KtorPortsPlugin.kt similarity index 51% rename from controls-ktor-tcp/src/main/kotlin/space/kscience/controls/ports/KtorTcpPortPlugin.kt rename to controls-ports-ktor/src/main/kotlin/space/kscience/controls/ports/KtorPortsPlugin.kt index 079f457..9c6dbba 100644 --- a/controls-ktor-tcp/src/main/kotlin/space/kscience/controls/ports/KtorTcpPortPlugin.kt +++ b/controls-ports-ktor/src/main/kotlin/space/kscience/controls/ports/KtorPortsPlugin.kt @@ -6,21 +6,22 @@ import space.kscience.dataforge.context.PluginFactory import space.kscience.dataforge.context.PluginTag import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.asName -public class KtorTcpPortPlugin : AbstractPlugin() { +public class KtorPortsPlugin : AbstractPlugin() { override val tag: PluginTag get() = Companion.tag - override fun content(target: String): Map = when(target){ - PortFactory.TYPE -> mapOf(Name.EMPTY to KtorTcpPort) + override fun content(target: String): Map = when (target) { + PortFactory.TYPE -> mapOf("tcp".asName() to KtorTcpPort, "udp".asName() to KtorUdpPort) else -> emptyMap() } - public companion object : PluginFactory { + public companion object : PluginFactory { - override val tag: PluginTag = PluginTag("controls.ports.serial", group = PluginTag.DATAFORGE_GROUP) + override val tag: PluginTag = PluginTag("controls.ports.ktor", group = PluginTag.DATAFORGE_GROUP) - override fun build(context: Context, meta: Meta): KtorTcpPortPlugin = KtorTcpPortPlugin() + override fun build(context: Context, meta: Meta): KtorPortsPlugin = KtorPortsPlugin() } diff --git a/controls-ktor-tcp/src/main/kotlin/space/kscience/controls/ports/KtorTcpPort.kt b/controls-ports-ktor/src/main/kotlin/space/kscience/controls/ports/KtorTcpPort.kt similarity index 100% rename from controls-ktor-tcp/src/main/kotlin/space/kscience/controls/ports/KtorTcpPort.kt rename to controls-ports-ktor/src/main/kotlin/space/kscience/controls/ports/KtorTcpPort.kt diff --git a/controls-ports-ktor/src/main/kotlin/space/kscience/controls/ports/KtorUdpPort.kt b/controls-ports-ktor/src/main/kotlin/space/kscience/controls/ports/KtorUdpPort.kt new file mode 100644 index 0000000..8b8446c --- /dev/null +++ b/controls-ports-ktor/src/main/kotlin/space/kscience/controls/ports/KtorUdpPort.kt @@ -0,0 +1,87 @@ +package space.kscience.controls.ports + +import io.ktor.network.selector.ActorSelectorManager +import io.ktor.network.sockets.InetSocketAddress +import io.ktor.network.sockets.aSocket +import io.ktor.network.sockets.openReadChannel +import io.ktor.network.sockets.openWriteChannel +import io.ktor.utils.io.consumeEachBufferRange +import io.ktor.utils.io.core.Closeable +import io.ktor.utils.io.writeAvailable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.int +import space.kscience.dataforge.meta.number +import space.kscience.dataforge.meta.string +import kotlin.coroutines.CoroutineContext + +public class KtorUdpPort internal constructor( + context: Context, + public val remoteHost: String, + public val remotePort: Int, + public val localPort: Int? = null, + public val localHost: String = "localhost", + coroutineContext: CoroutineContext = context.coroutineContext, +) : AbstractPort(context, coroutineContext), Closeable { + + override fun toString(): String = "port[udp:$remoteHost:$remotePort]" + + private val futureSocket = scope.async { + aSocket(ActorSelectorManager(Dispatchers.IO)).udp().connect( + remoteAddress = InetSocketAddress(remoteHost, remotePort), + localAddress = localPort?.let { InetSocketAddress(localHost, localPort) } + ) + } + + private val writeChannel = scope.async { + futureSocket.await().openWriteChannel(true) + } + + private val listenerJob = scope.launch { + val input = futureSocket.await().openReadChannel() + input.consumeEachBufferRange { buffer, _ -> + val array = ByteArray(buffer.remaining()) + buffer.get(array) + receive(array) + isActive + } + } + + override suspend fun write(data: ByteArray) { + writeChannel.await().writeAvailable(data) + } + + override fun close() { + listenerJob.cancel() + futureSocket.cancel() + super.close() + } + + public companion object : PortFactory { + + override val type: String = "udp" + + public fun open( + context: Context, + remoteHost: String, + remotePort: Int, + localPort: Int? = null, + localHost: String = "localhost", + coroutineContext: CoroutineContext = context.coroutineContext, + ): KtorUdpPort { + return KtorUdpPort(context, remoteHost, remotePort, localPort, localHost, coroutineContext) + } + + override fun build(context: Context, meta: Meta): Port { + val remoteHost by meta.string { error("Remote host is not specified") } + val remotePort by meta.number { error("Remote port is not specified") } + val localHost: String? by meta.string() + val localPort: Int? by meta.int() + return open(context, remoteHost, remotePort.toInt(), localPort, localHost ?: "localhost") + } + } +} \ No newline at end of file diff --git a/controls-serial/README.md b/controls-serial/README.md index eb214ba..209055d 100644 --- a/controls-serial/README.md +++ b/controls-serial/README.md @@ -1,32 +1,23 @@ # Module controls-serial - +Implementation of direct serial port communication with JSerialComm ## Usage ## Artifact: -The Maven coordinates of this project are `space.kscience:controls-serial:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:controls-serial:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:controls-serial:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:controls-serial:0.1.1-SNAPSHOT") + implementation("space.kscience:controls-serial:0.2.0") } ``` diff --git a/controls-serial/api/controls-serial.api b/controls-serial/api/controls-serial.api new file mode 100644 index 0000000..636b825 --- /dev/null +++ b/controls-serial/api/controls-serial.api @@ -0,0 +1,29 @@ +public final class space/kscience/controls/serial/JSerialCommPort : space/kscience/controls/ports/AbstractPort { + public static final field Companion Lspace/kscience/controls/serial/JSerialCommPort$Companion; + public fun (Lspace/kscience/dataforge/context/Context;Lcom/fazecast/jSerialComm/SerialPort;Lkotlin/coroutines/CoroutineContext;)V + public synthetic fun (Lspace/kscience/dataforge/context/Context;Lcom/fazecast/jSerialComm/SerialPort;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/controls/serial/JSerialCommPort$Companion : space/kscience/controls/ports/PortFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/ports/Port; + public fun getType ()Ljava/lang/String; + public final fun open (Lspace/kscience/dataforge/context/Context;Ljava/lang/String;IIIILkotlin/coroutines/CoroutineContext;)Lspace/kscience/controls/serial/JSerialCommPort; + public static synthetic fun open$default (Lspace/kscience/controls/serial/JSerialCommPort$Companion;Lspace/kscience/dataforge/context/Context;Ljava/lang/String;IIIILkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lspace/kscience/controls/serial/JSerialCommPort; +} + +public final class space/kscience/controls/serial/SerialPortPlugin : space/kscience/dataforge/context/AbstractPlugin { + public static final field Companion Lspace/kscience/controls/serial/SerialPortPlugin$Companion; + public fun ()V + public fun content (Ljava/lang/String;)Ljava/util/Map; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + +public final class space/kscience/controls/serial/SerialPortPlugin$Companion : space/kscience/dataforge/context/PluginFactory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/serial/SerialPortPlugin; + public fun getTag ()Lspace/kscience/dataforge/context/PluginTag; +} + diff --git a/controls-serial/build.gradle.kts b/controls-serial/build.gradle.kts index b28a210..a9afc41 100644 --- a/controls-serial/build.gradle.kts +++ b/controls-serial/build.gradle.kts @@ -1,9 +1,17 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.jvm") `maven-publish` } +description = "Implementation of direct serial port communication with JSerialComm" + dependencies{ api(project(":controls-core")) - implementation("org.scream3r:jssc:2.8.0") + implementation("com.fazecast:jSerialComm:2.10.3") +} + +readme{ + maturity = Maturity.EXPERIMENTAL } \ No newline at end of file diff --git a/controls-serial/src/main/kotlin/space/kscience/controls/serial/JSerialCommPort.kt b/controls-serial/src/main/kotlin/space/kscience/controls/serial/JSerialCommPort.kt new file mode 100644 index 0000000..3e0601c --- /dev/null +++ b/controls-serial/src/main/kotlin/space/kscience/controls/serial/JSerialCommPort.kt @@ -0,0 +1,87 @@ +package space.kscience.controls.serial + +import com.fazecast.jSerialComm.SerialPort +import com.fazecast.jSerialComm.SerialPortDataListener +import com.fazecast.jSerialComm.SerialPortEvent +import kotlinx.coroutines.launch +import space.kscience.controls.ports.AbstractPort +import space.kscience.controls.ports.Port +import space.kscience.controls.ports.PortFactory +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.int +import space.kscience.dataforge.meta.string +import kotlin.coroutines.CoroutineContext + +/** + * A port based on JSerialComm + */ +public class JSerialCommPort( + context: Context, + private val comPort: SerialPort, + coroutineContext: CoroutineContext = context.coroutineContext, +) : AbstractPort(context, coroutineContext) { + + override fun toString(): String = "port[${comPort.descriptivePortName}]" + + private val serialPortListener = object : SerialPortDataListener { + override fun getListeningEvents(): Int = SerialPort.LISTENING_EVENT_DATA_AVAILABLE + + override fun serialEvent(event: SerialPortEvent) { + if (event.eventType == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) { + scope.launch { receive(event.receivedData) } + } + } + } + + init { + comPort.addDataListener(serialPortListener) + } + + override suspend fun write(data: ByteArray) { + comPort.writeBytes(data, data.size) + } + + override fun close() { + comPort.removeDataListener() + if (comPort.isOpen) { + comPort.closePort() + } + super.close() + } + + public companion object : PortFactory { + + override val type: String = "com" + + + /** + * Construct ComPort with given parameters + */ + public fun open( + context: Context, + portName: String, + baudRate: Int = 9600, + dataBits: Int = 8, + stopBits: Int = SerialPort.ONE_STOP_BIT, + parity: Int = SerialPort.NO_PARITY, + coroutineContext: CoroutineContext = context.coroutineContext, + ): JSerialCommPort { + val serialPort = SerialPort.getCommPort(portName).apply { + setComPortParameters(baudRate, dataBits, stopBits, parity) + openPort() + } + return JSerialCommPort(context, serialPort, coroutineContext) + } + + override fun build(context: Context, meta: Meta): Port { + val name by meta.string { error("Serial port name not defined") } + val baudRate by meta.int(9600) + val dataBits by meta.int(8) + val stopBits by meta.int(SerialPort.ONE_STOP_BIT) + val parity by meta.int(SerialPort.NO_PARITY) + return open(context, name, baudRate, dataBits, stopBits, parity) + } + } + +} \ No newline at end of file diff --git a/controls-serial/src/main/kotlin/space/kscience/controls/serial/SerialPort.kt b/controls-serial/src/main/kotlin/space/kscience/controls/serial/SerialPort.kt deleted file mode 100644 index 05d4b64..0000000 --- a/controls-serial/src/main/kotlin/space/kscience/controls/serial/SerialPort.kt +++ /dev/null @@ -1,92 +0,0 @@ -package space.kscience.controls.serial - -import jssc.SerialPort.* -import jssc.SerialPortEventListener -import space.kscience.controls.ports.AbstractPort -import space.kscience.controls.ports.Port -import space.kscience.controls.ports.PortFactory -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.int -import space.kscience.dataforge.meta.string -import kotlin.coroutines.CoroutineContext -import jssc.SerialPort as JSSCPort - -/** - * COM/USB port - */ -public class SerialPort private constructor( - context: Context, - private val jssc: JSSCPort, - coroutineContext: CoroutineContext = context.coroutineContext, -) : AbstractPort(context, coroutineContext) { - - override fun toString(): String = "port[${jssc.portName}]" - - private val serialPortListener = SerialPortEventListener { event -> - if (event.isRXCHAR) { - val chars = event.eventValue - val bytes = jssc.readBytes(chars) - receive(bytes) - } - } - - init { - jssc.addEventListener(serialPortListener) - } - - /** - * Clear current input and output buffers - */ - internal fun clearPort() { - jssc.purgePort(PURGE_RXCLEAR or PURGE_TXCLEAR) - } - - override suspend fun write(data: ByteArray) { - jssc.writeBytes(data) - } - - @Throws(Exception::class) - override fun close() { - jssc.removeEventListener() - clearPort() - if (jssc.isOpened) { - jssc.closePort() - } - super.close() - } - - public companion object : PortFactory { - - override val type: String = "com" - - - /** - * Construct ComPort with given parameters - */ - public fun open( - context: Context, - portName: String, - baudRate: Int = BAUDRATE_9600, - dataBits: Int = DATABITS_8, - stopBits: Int = STOPBITS_1, - parity: Int = PARITY_NONE, - coroutineContext: CoroutineContext = context.coroutineContext, - ): SerialPort { - val jssc = JSSCPort(portName).apply { - openPort() - setParams(baudRate, dataBits, stopBits, parity) - } - return SerialPort(context, jssc, coroutineContext) - } - - override fun build(context: Context, meta: Meta): Port { - val name by meta.string { error("Serial port name not defined") } - val baudRate by meta.int(BAUDRATE_9600) - val dataBits by meta.int(DATABITS_8) - val stopBits by meta.int(STOPBITS_1) - val parity by meta.int(PARITY_NONE) - return open(context, name, baudRate, dataBits, stopBits, parity) - } - } -} \ No newline at end of file diff --git a/controls-serial/src/main/kotlin/space/kscience/controls/serial/SerialPortPlugin.kt b/controls-serial/src/main/kotlin/space/kscience/controls/serial/SerialPortPlugin.kt index 6b573ae..ae9d4fc 100644 --- a/controls-serial/src/main/kotlin/space/kscience/controls/serial/SerialPortPlugin.kt +++ b/controls-serial/src/main/kotlin/space/kscience/controls/serial/SerialPortPlugin.kt @@ -13,7 +13,7 @@ public class SerialPortPlugin : AbstractPlugin() { override val tag: PluginTag get() = Companion.tag override fun content(target: String): Map = when(target){ - PortFactory.TYPE -> mapOf(Name.EMPTY to SerialPort) + PortFactory.TYPE -> mapOf(Name.EMPTY to JSerialCommPort) else -> emptyMap() } diff --git a/controls-server/README.md b/controls-server/README.md index 561a815..83408e8 100644 --- a/controls-server/README.md +++ b/controls-server/README.md @@ -1,32 +1,23 @@ # Module controls-server -A magix event loop server with web server for visualization. +A combined Magix event loop server with web server for visualization. ## Usage ## Artifact: -The Maven coordinates of this project are `space.kscience:controls-server:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:controls-server:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:controls-server:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:controls-server:0.1.1-SNAPSHOT") + implementation("space.kscience:controls-server:0.2.0") } ``` diff --git a/controls-server/api/controls-server.api b/controls-server/api/controls-server.api new file mode 100644 index 0000000..f94aeba --- /dev/null +++ b/controls-server/api/controls-server.api @@ -0,0 +1,9 @@ +public final class space/kscience/controls/server/DeviceWebServerKt { + public static final fun deviceManagerModule (Lio/ktor/server/application/Application;Lspace/kscience/controls/manager/DeviceManager;[Lspace/kscience/magix/api/MagixFlowPlugin;Ljava/util/Collection;Ljava/lang/String;I)V + public static synthetic fun deviceManagerModule$default (Lio/ktor/server/application/Application;Lspace/kscience/controls/manager/DeviceManager;[Lspace/kscience/magix/api/MagixFlowPlugin;Ljava/util/Collection;Ljava/lang/String;IILjava/lang/Object;)V + public static final fun getWEB_SERVER_TARGET ()Lspace/kscience/dataforge/names/Name; + public static final fun startDeviceServer (Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/controls/manager/DeviceManager;ILjava/lang/String;)Lio/ktor/server/engine/ApplicationEngine; + public static synthetic fun startDeviceServer$default (Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/controls/manager/DeviceManager;ILjava/lang/String;ILjava/lang/Object;)Lio/ktor/server/engine/ApplicationEngine; + public static final fun whenStarted (Lio/ktor/server/engine/ApplicationEngine;Lkotlin/jvm/functions/Function1;)V +} + diff --git a/controls-server/build.gradle.kts b/controls-server/build.gradle.kts index d661459..43a9d61 100644 --- a/controls-server/build.gradle.kts +++ b/controls-server/build.gradle.kts @@ -1,10 +1,12 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.jvm") `maven-publish` } description = """ - A magix event loop server with web server for visualization. + A combined Magix event loop server with web server for visualization. """.trimIndent() val dataforgeVersion: String by rootProject.extra @@ -12,7 +14,7 @@ val ktorVersion: String by rootProject.extra dependencies { implementation(projects.controlsCore) - implementation(projects.controlsKtorTcp) + implementation(projects.controlsPortsKtor) implementation(projects.magix.magixServer) implementation("io.ktor:ktor-server-cio:$ktorVersion") implementation("io.ktor:ktor-server-websockets:$ktorVersion") @@ -20,4 +22,8 @@ dependencies { implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation("io.ktor:ktor-server-html-builder:$ktorVersion") implementation("io.ktor:ktor-server-status-pages:$ktorVersion") +} + +readme{ + maturity = Maturity.PROTOTYPE } \ No newline at end of file diff --git a/controls-storage/README.md b/controls-storage/README.md index 650dd53..51cdbcb 100644 --- a/controls-storage/README.md +++ b/controls-storage/README.md @@ -1,32 +1,23 @@ # Module controls-storage - +An API for stand-alone Controls-kt device or a hub. ## Usage ## Artifact: -The Maven coordinates of this project are `space.kscience:controls-storage:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:controls-storage:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:controls-storage:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:controls-storage:0.1.1-SNAPSHOT") + implementation("space.kscience:controls-storage:0.2.0") } ``` diff --git a/controls-storage/build.gradle.kts b/controls-storage/build.gradle.kts index 6bf1aa2..b3d8c15 100644 --- a/controls-storage/build.gradle.kts +++ b/controls-storage/build.gradle.kts @@ -5,6 +5,10 @@ plugins { val dataforgeVersion: String by rootProject.extra +description = """ + An API for stand-alone Controls-kt device or a hub. +""".trimIndent() + kscience{ jvm() js() @@ -13,7 +17,7 @@ kscience{ } dependencies(jvmMain){ api(projects.magix.magixApi) - api(projects.controlsMagixClient) + api(projects.controlsMagix) api(projects.magix.magixServer) } } diff --git a/controls-storage/controls-xodus/README.md b/controls-storage/controls-xodus/README.md index a25650e..790b356 100644 --- a/controls-storage/controls-xodus/README.md +++ b/controls-storage/controls-xodus/README.md @@ -1,32 +1,23 @@ # Module controls-xodus - +An implementation of controls-storage on top of JetBrains Xodus. ## Usage ## Artifact: -The Maven coordinates of this project are `space.kscience:controls-xodus:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:controls-xodus:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:controls-xodus:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:controls-xodus:0.1.1-SNAPSHOT") + implementation("space.kscience:controls-xodus:0.2.0") } ``` diff --git a/controls-storage/controls-xodus/build.gradle.kts b/controls-storage/controls-xodus/build.gradle.kts index 635f8e3..329f3ce 100644 --- a/controls-storage/controls-xodus/build.gradle.kts +++ b/controls-storage/controls-xodus/build.gradle.kts @@ -5,6 +5,10 @@ plugins { val xodusVersion: String by rootProject.extra +description = """ + An implementation of controls-storage on top of JetBrains Xodus. +""".trimIndent() + dependencies { api(projects.controlsStorage) implementation("org.jetbrains.xodus:xodus-entity-store:$xodusVersion") diff --git a/demo/all-things/api/all-things.api b/demo/all-things/api/all-things.api new file mode 100644 index 0000000..4d283e5 --- /dev/null +++ b/demo/all-things/api/all-things.api @@ -0,0 +1,77 @@ +public final class space/kscience/controls/demo/DemoController : tornadofx/Controller, space/kscience/dataforge/context/ContextAware { + public fun ()V + public fun getContext ()Lspace/kscience/dataforge/context/Context; + public final fun getDevice ()Lspace/kscience/controls/demo/DemoDevice; + public final fun getMagixServer ()Lio/ktor/server/engine/ApplicationEngine; + public final fun getOpcUaServer ()Lorg/eclipse/milo/opcua/sdk/server/OpcUaServer; + public final fun getVisualizer ()Lio/ktor/server/engine/ApplicationEngine; + public final fun init ()V + public final fun setDevice (Lspace/kscience/controls/demo/DemoDevice;)V + public final fun setMagixServer (Lio/ktor/server/engine/ApplicationEngine;)V + public final fun setOpcUaServer (Lorg/eclipse/milo/opcua/sdk/server/OpcUaServer;)V + public final fun setVisualizer (Lio/ktor/server/engine/ApplicationEngine;)V + public final fun shutdown ()V +} + +public final class space/kscience/controls/demo/DemoControllerApp : tornadofx/App { + public fun ()V + public fun start (Ljavafx/stage/Stage;)V + public fun stop ()V +} + +public final class space/kscience/controls/demo/DemoControllerView : tornadofx/View { + public fun ()V + public fun getRoot ()Ljavafx/scene/Parent; +} + +public final class space/kscience/controls/demo/DemoControllerViewKt { + public static final fun main ()V + public static synthetic fun main ([Ljava/lang/String;)V +} + +public final class space/kscience/controls/demo/DemoDevice : space/kscience/controls/spec/DeviceBySpec, space/kscience/controls/demo/IDemoDevice { + public static final field Companion Lspace/kscience/controls/demo/DemoDevice$Companion; + public fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)V + public fun cosValue ()D + public fun getCosScaleState ()D + public fun getSinScaleState ()D + public fun getTimeScaleState ()D + public fun setCosScaleState (D)V + public fun setSinScaleState (D)V + public fun setTimeScaleState (D)V + public fun sinValue ()D +} + +public final class space/kscience/controls/demo/DemoDevice$Companion : space/kscience/controls/spec/DeviceSpec, space/kscience/dataforge/context/Factory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/demo/DemoDevice; + public final fun getCoordinates ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getCos ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getCosScale ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getResetScale ()Lspace/kscience/controls/spec/DeviceActionSpec; + public final fun getSin ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getSinScale ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getTimeScale ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public synthetic fun onOpen (Lspace/kscience/controls/api/Device;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun onOpen (Lspace/kscience/controls/demo/IDemoDevice;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/demo/DemoDeviceServerKt { + public static final fun startDemoDeviceServer (Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/magix/api/MagixEndpoint;)Lio/ktor/server/engine/ApplicationEngine; + public static final fun updateFrom (Lspace/kscience/plotly/models/Trace;Ljava/lang/String;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun updateXYFrom (Lspace/kscience/plotly/models/Trace;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun windowed (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow; +} + +public abstract interface class space/kscience/controls/demo/IDemoDevice : space/kscience/controls/api/Device { + public abstract fun cosValue ()D + public abstract fun getCosScaleState ()D + public abstract fun getSinScaleState ()D + public abstract fun getTimeScaleState ()D + public abstract fun setCosScaleState (D)V + public abstract fun setSinScaleState (D)V + public abstract fun setTimeScaleState (D)V + public abstract fun sinValue ()D + public fun time ()Ljava/time/Instant; +} + diff --git a/demo/all-things/build.gradle.kts b/demo/all-things/build.gradle.kts index 1ea3c05..05d7395 100644 --- a/demo/all-things/build.gradle.kts +++ b/demo/all-things/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { implementation(projects.controlsCore) //implementation(projects.controlsServer) implementation(projects.magix.magixServer) - implementation(projects.controlsMagixClient) + implementation(projects.controlsMagix) implementation(projects.magix.magixRsocket) implementation(projects.magix.magixZmq) implementation(projects.controlsOpcua) diff --git a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt index 9eb8244..94815fa 100644 --- a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt +++ b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt @@ -8,7 +8,7 @@ import javafx.stage.Stage import kotlinx.coroutines.launch import org.eclipse.milo.opcua.sdk.server.OpcUaServer import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText -import space.kscience.controls.client.connectToMagix +import space.kscience.controls.client.launchMagixService import space.kscience.controls.demo.DemoDevice.Companion.cosScale import space.kscience.controls.demo.DemoDevice.Companion.sinScale import space.kscience.controls.demo.DemoDevice.Companion.timeScale @@ -36,8 +36,9 @@ class DemoController : Controller(), ContextAware { var visualizer: ApplicationEngine? = null var opcUaServer: OpcUaServer = OpcUaServer { setApplicationName(LocalizedText.english("space.kscience.controls.opcua")) + endpoint { - setBindPort(9999) + setBindPort(4840) //use default endpoint } } @@ -58,7 +59,7 @@ class DemoController : Controller(), ContextAware { ) //Launch a device client and connect it to the server val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost") - deviceManager.connectToMagix(deviceEndpoint) + deviceManager.launchMagixService(deviceEndpoint) //connect visualization to a magix endpoint val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost") visualizer = startDemoDeviceServer(visualEndpoint) diff --git a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt index 37b7b4b..dd65b78 100644 --- a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt +++ b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt @@ -68,11 +68,10 @@ class DemoDevice(context: Context, meta: Meta) : DeviceBySpec(Compa } - val resetScale by action(MetaConverter.meta, MetaConverter.meta) { + val resetScale by unitAction { write(timeScale, 5000.0) write(sinScale, 1.0) write(cosScale, 1.0) - null } override suspend fun IDemoDevice.onOpen() { diff --git a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/demoDeviceServer.kt b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/demoDeviceServer.kt index 9ef69cf..fda8ead 100644 --- a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/demoDeviceServer.kt +++ b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/demoDeviceServer.kt @@ -7,7 +7,8 @@ import kotlinx.coroutines.launch import kotlinx.html.div import kotlinx.html.link import space.kscience.controls.api.PropertyChangedMessage -import space.kscience.controls.client.controlsMagixFormat +import space.kscience.controls.client.magixFormat +import space.kscience.controls.manager.DeviceManager import space.kscience.controls.spec.name import space.kscience.dataforge.meta.double import space.kscience.dataforge.meta.get @@ -54,7 +55,7 @@ suspend fun Trace.updateXYFrom(flow: Flow>>) { fun CoroutineScope.startDemoDeviceServer(magixEndpoint: MagixEndpoint): ApplicationEngine { //share subscription to a parse message only once - val subscription = magixEndpoint.subscribe(controlsMagixFormat).shareIn(this, SharingStarted.Lazily) + val subscription = magixEndpoint.subscribe(DeviceManager.magixFormat).shareIn(this, SharingStarted.Lazily) val sinFlow = subscription.mapNotNull { (_, payload) -> (payload as? PropertyChangedMessage)?.takeIf { it.property == DemoDevice.sin.name } diff --git a/demo/car/api/car.api b/demo/car/api/car.api new file mode 100644 index 0000000..5b1940f --- /dev/null +++ b/demo/car/api/car.api @@ -0,0 +1,111 @@ +public abstract interface class space/kscience/controls/demo/car/IVirtualCar : space/kscience/controls/api/Device { + public static final field Companion Lspace/kscience/controls/demo/car/IVirtualCar$Companion; + public abstract fun getAccelerationState ()Lspace/kscience/controls/demo/car/Vector2D; + public abstract fun getLocationState ()Lspace/kscience/controls/demo/car/Vector2D; + public abstract fun getSpeedState ()Lspace/kscience/controls/demo/car/Vector2D; + public abstract fun setAccelerationState (Lspace/kscience/controls/demo/car/Vector2D;)V + public abstract fun setLocationState (Lspace/kscience/controls/demo/car/Vector2D;)V + public abstract fun setSpeedState (Lspace/kscience/controls/demo/car/Vector2D;)V +} + +public final class space/kscience/controls/demo/car/IVirtualCar$Companion : space/kscience/controls/spec/DeviceSpec { + public final fun getAcceleration ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getLocation ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getSpeed ()Lspace/kscience/controls/spec/DevicePropertySpec; +} + +public final class space/kscience/controls/demo/car/MagixVirtualCar : space/kscience/controls/demo/car/VirtualCar { + public static final field Companion Lspace/kscience/controls/demo/car/MagixVirtualCar$Companion; + public fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)V + public fun open (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/demo/car/MagixVirtualCar$Companion : space/kscience/dataforge/context/Factory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/demo/car/MagixVirtualCar; +} + +public final class space/kscience/controls/demo/car/Vector2D : space/kscience/dataforge/meta/MetaRepr { + public static final field CoordinatesMetaConverter Lspace/kscience/controls/demo/car/Vector2D$CoordinatesMetaConverter; + public fun ()V + public fun (DD)V + public synthetic fun (DDILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()D + public final fun component2 ()D + public final fun copy (DD)Lspace/kscience/controls/demo/car/Vector2D; + public static synthetic fun copy$default (Lspace/kscience/controls/demo/car/Vector2D;DDILjava/lang/Object;)Lspace/kscience/controls/demo/car/Vector2D; + public final fun div (D)Lspace/kscience/controls/demo/car/Vector2D; + public fun equals (Ljava/lang/Object;)Z + public final fun getX ()D + public final fun getY ()D + public fun hashCode ()I + public final fun setX (D)V + public final fun setY (D)V + public fun toMeta ()Lspace/kscience/dataforge/meta/Meta; + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/controls/demo/car/Vector2D$CoordinatesMetaConverter : space/kscience/dataforge/meta/transformations/MetaConverter { + public synthetic fun metaToObject (Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun metaToObject (Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/demo/car/Vector2D; + public synthetic fun objectToMeta (Ljava/lang/Object;)Lspace/kscience/dataforge/meta/Meta; + public fun objectToMeta (Lspace/kscience/controls/demo/car/Vector2D;)Lspace/kscience/dataforge/meta/Meta; +} + +public class space/kscience/controls/demo/car/VirtualCar : space/kscience/controls/spec/DeviceBySpec, space/kscience/controls/demo/car/IVirtualCar { + public static final field Companion Lspace/kscience/controls/demo/car/VirtualCar$Companion; + public fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)V + public final fun applyForce-HG0u8IE (Lspace/kscience/controls/demo/car/Vector2D;J)V + public fun getAccelerationState ()Lspace/kscience/controls/demo/car/Vector2D; + public fun getLocationState ()Lspace/kscience/controls/demo/car/Vector2D; + public fun getSpeedState ()Lspace/kscience/controls/demo/car/Vector2D; + public fun open (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setAccelerationState (Lspace/kscience/controls/demo/car/Vector2D;)V + public fun setLocationState (Lspace/kscience/controls/demo/car/Vector2D;)V + public fun setSpeedState (Lspace/kscience/controls/demo/car/Vector2D;)V +} + +public final class space/kscience/controls/demo/car/VirtualCar$Companion : space/kscience/dataforge/context/Factory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/demo/car/VirtualCar; +} + +public final class space/kscience/controls/demo/car/VirtualCarController : tornadofx/Controller, space/kscience/dataforge/context/ContextAware { + public static final field Companion Lspace/kscience/controls/demo/car/VirtualCarController$Companion; + public fun ()V + public fun getContext ()Lspace/kscience/dataforge/context/Context; + public final fun getMagixServer ()Lio/ktor/server/engine/ApplicationEngine; + public final fun getMagixVirtualCar ()Lspace/kscience/controls/demo/car/MagixVirtualCar; + public final fun getStorageEndpoint ()Lspace/kscience/magix/api/MagixEndpoint; + public final fun getVirtualCar ()Lspace/kscience/controls/demo/car/VirtualCar; + public final fun getXodusStorageJob ()Lkotlinx/coroutines/Job; + public final fun init ()V + public final fun setMagixServer (Lio/ktor/server/engine/ApplicationEngine;)V + public final fun setMagixVirtualCar (Lspace/kscience/controls/demo/car/MagixVirtualCar;)V + public final fun setStorageEndpoint (Lspace/kscience/magix/api/MagixEndpoint;)V + public final fun setVirtualCar (Lspace/kscience/controls/demo/car/VirtualCar;)V + public final fun setXodusStorageJob (Lkotlinx/coroutines/Job;)V + public final fun shutdown ()V +} + +public final class space/kscience/controls/demo/car/VirtualCarController$Companion { + public final fun getDeviceEntityStorePath ()Ljava/nio/file/Path; + public final fun getMagixEntityStorePath ()Ljava/nio/file/Path; +} + +public final class space/kscience/controls/demo/car/VirtualCarControllerApp : tornadofx/App { + public fun ()V + public fun start (Ljavafx/stage/Stage;)V + public fun stop ()V +} + +public final class space/kscience/controls/demo/car/VirtualCarControllerKt { + public static final fun main ()V + public static synthetic fun main ([Ljava/lang/String;)V +} + +public final class space/kscience/controls/demo/car/VirtualCarControllerView : tornadofx/View { + public fun ()V + public fun getRoot ()Ljavafx/scene/Parent; +} + diff --git a/demo/car/build.gradle.kts b/demo/car/build.gradle.kts index 3c943dd..5d53f11 100644 --- a/demo/car/build.gradle.kts +++ b/demo/car/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { implementation(projects.magix.magixServer) implementation(projects.magix.magixRsocket) implementation(projects.magix.magixZmq) - implementation(projects.controlsMagixClient) + implementation(projects.controlsMagix) implementation(projects.controlsStorage.controlsXodus) implementation(projects.magix.magixStorage.magixStorageXodus) // implementation(projects.controlsMongo) diff --git a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/MagixVirtualCar.kt b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/MagixVirtualCar.kt index addc3a9..03c781b 100644 --- a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/MagixVirtualCar.kt +++ b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/MagixVirtualCar.kt @@ -2,7 +2,8 @@ package space.kscience.controls.demo.car import kotlinx.coroutines.launch import space.kscience.controls.api.PropertyChangedMessage -import space.kscience.controls.client.controlsMagixFormat +import space.kscience.controls.client.magixFormat +import space.kscience.controls.manager.DeviceManager import space.kscience.controls.spec.write import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Factory @@ -18,7 +19,7 @@ import kotlin.time.ExperimentalTime class MagixVirtualCar(context: Context, meta: Meta) : VirtualCar(context, meta) { private fun MagixEndpoint.launchMagixVirtualCarUpdate() = launch { - subscribe(controlsMagixFormat).collect { (_, payload) -> + subscribe(DeviceManager.magixFormat).collect { (_, payload) -> (payload as? PropertyChangedMessage)?.let { message -> if (message.sourceDevice == Name.parse("virtual-car")) { when (message.property) { diff --git a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCarController.kt b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCarController.kt index f68c585..7a170ac 100644 --- a/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCarController.kt +++ b/demo/car/src/main/kotlin/space/kscience/controls/demo/car/VirtualCarController.kt @@ -8,7 +8,7 @@ import javafx.scene.layout.Priority import javafx.stage.Stage import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import space.kscience.controls.client.connectToMagix +import space.kscience.controls.client.launchMagixService import space.kscience.controls.demo.car.IVirtualCar.Companion.acceleration import space.kscience.controls.manager.DeviceManager import space.kscience.controls.manager.install @@ -63,7 +63,7 @@ class VirtualCarController : Controller(), ContextAware { //mongoStorageJob = deviceManager.storeMessages(DefaultAsynchronousMongoClientFactory) //Launch device client and connect it to the server val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost") - deviceManager.connectToMagix(deviceEndpoint) + deviceManager.launchMagixService(deviceEndpoint) } } diff --git a/demo/echo/api/echo.api b/demo/echo/api/echo.api new file mode 100644 index 0000000..bc0fe05 --- /dev/null +++ b/demo/echo/api/echo.api @@ -0,0 +1,5 @@ +public final class space/kscience/controls/demo/echo/MainKt { + public static final fun main (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun main ([Ljava/lang/String;)V +} + diff --git a/demo/echo/src/main/kotlin/space/kscience/controls/demo/echo/main.kt b/demo/echo/src/main/kotlin/space/kscience/controls/demo/echo/main.kt index 3dbb43d..c0ad995 100644 --- a/demo/echo/src/main/kotlin/space/kscience/controls/demo/echo/main.kt +++ b/demo/echo/src/main/kotlin/space/kscience/controls/demo/echo/main.kt @@ -22,7 +22,7 @@ private suspend fun MagixEndpoint.collectEcho(scope: CoroutineScope, n: Int) { scope.launch { subscribe( MagixMessageFilter( - origin = listOf("loop") + source = listOf("loop") ) ).collect { message -> if (message.id?.endsWith(".response") == true) { @@ -44,8 +44,8 @@ private suspend fun MagixEndpoint.collectEcho(scope: CoroutineScope, n: Int) { MagixMessage( format = "test", payload = JsonObject(emptyMap()), - origin = "test", - target = "loop", + sourceEndpoint = "test", + targetEndpoint = "loop", id = it.toString() ) ) @@ -60,14 +60,14 @@ private suspend fun MagixEndpoint.collectEcho(scope: CoroutineScope, n: Int) { @OptIn(ExperimentalTime::class) suspend fun main(): Unit = coroutineScope { launch(Dispatchers.Default) { - val server = startMagixServer(MagixFlowPlugin { _, flow -> + val server = startMagixServer(MagixFlowPlugin { _, flow, send -> val logger = LoggerFactory.getLogger("echo") //echo each message flow.onEach { message -> if (message.parentId == null) { - val m = message.copy(origin = "loop", parentId = message.id, id = message.id + ".response") + val m = message.copy(sourceEndpoint = "loop", parentId = message.id, id = message.id + ".response") logger.info(m.toString()) - flow.emit(m) + send(m) } }.launchIn(this) }) diff --git a/demo/magix-demo/api/magix-demo.api b/demo/magix-demo/api/magix-demo.api new file mode 100644 index 0000000..7a1c19e --- /dev/null +++ b/demo/magix-demo/api/magix-demo.api @@ -0,0 +1,7 @@ +public final class ZmqKt { + public static final fun main (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun main ([Ljava/lang/String;)V + public static final fun sendJson (Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun sendJson$default (Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + diff --git a/demo/many-devices/api/many-devices.api b/demo/many-devices/api/many-devices.api new file mode 100644 index 0000000..1b398a2 --- /dev/null +++ b/demo/many-devices/api/many-devices.api @@ -0,0 +1,18 @@ +public final class space/kscience/controls/demo/MassDevice : space/kscience/controls/spec/DeviceBySpec { + public static final field Companion Lspace/kscience/controls/demo/MassDevice$Companion; + public fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)V +} + +public final class space/kscience/controls/demo/MassDevice$Companion : space/kscience/controls/spec/DeviceSpec, space/kscience/dataforge/context/Factory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lspace/kscience/controls/demo/MassDevice; + public final fun getValue ()Lspace/kscience/controls/spec/DevicePropertySpec; + public synthetic fun onOpen (Lspace/kscience/controls/api/Device;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun onOpen (Lspace/kscience/controls/demo/MassDevice;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/controls/demo/MassDeviceKt { + public static final fun main (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun main ([Ljava/lang/String;)V +} + diff --git a/demo/many-devices/build.gradle.kts b/demo/many-devices/build.gradle.kts index 8c765d6..7248e42 100644 --- a/demo/many-devices/build.gradle.kts +++ b/demo/many-devices/build.gradle.kts @@ -14,12 +14,12 @@ val rsocketVersion: String by rootProject.extra dependencies { implementation(projects.magix.magixServer) - implementation(projects.controlsMagixClient) + implementation(projects.controlsMagix) implementation(projects.magix.magixRsocket) implementation(projects.magix.magixZmq) implementation("io.ktor:ktor-client-cio:$ktorVersion") - implementation("space.kscience:plotlykt-server:0.5.3") + implementation("space.kscience:plotlykt-server:0.6.0") implementation(spclibs.logback.classic) } diff --git a/demo/many-devices/src/main/kotlin/space/kscience/controls/demo/MassDevice.kt b/demo/many-devices/src/main/kotlin/space/kscience/controls/demo/MassDevice.kt index 6370d8d..5c89c26 100644 --- a/demo/many-devices/src/main/kotlin/space/kscience/controls/demo/MassDevice.kt +++ b/demo/many-devices/src/main/kotlin/space/kscience/controls/demo/MassDevice.kt @@ -1,15 +1,13 @@ package space.kscience.controls.demo -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import space.kscience.controls.client.connectToMagix -import space.kscience.controls.client.controlsMagixFormat +import space.kscience.controls.client.launchMagixService +import space.kscience.controls.client.magixFormat import space.kscience.controls.manager.DeviceManager import space.kscience.controls.manager.install import space.kscience.controls.spec.* @@ -21,7 +19,8 @@ import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.int import space.kscience.magix.api.MagixEndpoint import space.kscience.magix.api.subscribe -import space.kscience.magix.rsocket.rSocketStreamWithTcp +import space.kscience.magix.rsocket.rSocketWithTcp +import space.kscience.magix.rsocket.rSocketWithWebSockets import space.kscience.magix.server.RSocketMagixFlowPlugin import space.kscience.magix.server.startMagixServer import space.kscience.plotly.Plotly @@ -31,8 +30,10 @@ import space.kscience.plotly.plot import space.kscience.plotly.server.PlotlyUpdateMode import space.kscience.plotly.server.serve import space.kscience.plotly.server.show -import java.util.concurrent.ConcurrentHashMap +import space.kscince.magix.zmq.ZmqMagixFlowPlugin import kotlin.random.Random +import kotlin.time.Duration +import kotlin.time.Duration.Companion.ZERO import kotlin.time.Duration.Companion.milliseconds @@ -48,25 +49,27 @@ class MassDevice(context: Context, meta: Meta) : DeviceBySpec(MassDe val value by doubleProperty { randomValue } override suspend fun MassDevice.onOpen() { - doRecurring(100.milliseconds) { + doRecurring((meta["delay"].int ?: 10).milliseconds) { read(value) } } } } -fun main() { +@OptIn(DelicateCoroutinesApi::class) +suspend fun main() { val context = Context("Mass") context.startMagixServer( RSocketMagixFlowPlugin(), -// ZmqMagixFlowPlugin() + ZmqMagixFlowPlugin() ) val numDevices = 100 - context.launch(Dispatchers.IO) { - repeat(numDevices) { + repeat(numDevices) { + context.launch(newFixedThreadPoolContext(2, "Device${it}")) { + delay(1) val deviceContext = Context("Device${it}") { plugin(DeviceManager) } @@ -76,8 +79,8 @@ fun main() { deviceManager.install("device$it", MassDevice) val endpointId = "device$it" - val deviceEndpoint = MagixEndpoint.rSocketStreamWithTcp("localhost") - deviceManager.connectToMagix(deviceEndpoint, endpointId) + val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost") + deviceManager.launchMagixService(deviceEndpoint, endpointId) } } @@ -88,23 +91,36 @@ fun main() { plot(renderer = container) { layout { title = "Latest event" + xaxis.title = "Device number" + yaxis.title = "Maximum latency in ms" } bar { - launch(Dispatchers.Default){ - val monitorEndpoint = MagixEndpoint.rSocketStreamWithTcp("localhost") + launch(Dispatchers.IO) { + val monitorEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost") - val latest = ConcurrentHashMap() + val mutex = Mutex() - monitorEndpoint.subscribe(controlsMagixFormat).onEach { (magixMessage, payload) -> - latest[magixMessage.origin] = payload.time ?: Clock.System.now() + val latest = HashMap() + val max = HashMap() + + monitorEndpoint.subscribe(DeviceManager.magixFormat).onEach { (magixMessage, payload) -> + mutex.withLock { + val delay = Clock.System.now() - payload.time!! + latest[magixMessage.sourceEndpoint] = Clock.System.now() - payload.time!! + max[magixMessage.sourceEndpoint] = + maxOf(delay, max[magixMessage.sourceEndpoint] ?: ZERO) + } }.launchIn(this) while (isActive) { delay(200) - val now = Clock.System.now() - val sorted = latest.mapKeys { it.key.substring(6).toInt() }.toSortedMap() - x.numbers = sorted.keys - y.numbers = sorted.values.map { now.minus(it).inWholeMilliseconds / 1000.0 } + mutex.withLock { + val sorted = max.mapKeys { it.key.substring(6).toInt() }.toSortedMap() + latest.clear() + max.clear() + x.numbers = sorted.keys + y.numbers = sorted.values.map { it.inWholeMilliseconds / 1000.0 + 0.0001 } + } } } } diff --git a/demo/mks-pdr900/api/mks-pdr900.api b/demo/mks-pdr900/api/mks-pdr900.api new file mode 100644 index 0000000..a9c9ecb --- /dev/null +++ b/demo/mks-pdr900/api/mks-pdr900.api @@ -0,0 +1,28 @@ +public final class center/sciprog/devices/mks/MksPdr900Device : space/kscience/controls/spec/DeviceBySpec { + public static final field Companion Lcenter/sciprog/devices/mks/MksPdr900Device$Companion; + public static final field DEFAULT_CHANNEL I + public fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)V + public final fun readChannelData (ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun readPowerOn (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun writePowerOn (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class center/sciprog/devices/mks/MksPdr900Device$Companion : space/kscience/controls/spec/DeviceSpec, space/kscience/dataforge/context/Factory { + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lcenter/sciprog/devices/mks/MksPdr900Device; + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public final fun getChannel ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getError ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getPowerOn ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getValue ()Lspace/kscience/controls/spec/DevicePropertySpec; + public fun onClose (Lcenter/sciprog/devices/mks/MksPdr900Device;)V + public synthetic fun onClose (Lspace/kscience/controls/api/Device;)V +} + +public final class center/sciprog/devices/mks/NullableStringMetaConverter : space/kscience/dataforge/meta/transformations/MetaConverter { + public static final field INSTANCE Lcenter/sciprog/devices/mks/NullableStringMetaConverter; + public synthetic fun metaToObject (Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun metaToObject (Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/String; + public synthetic fun objectToMeta (Ljava/lang/Object;)Lspace/kscience/dataforge/meta/Meta; + public fun objectToMeta (Ljava/lang/String;)Lspace/kscience/dataforge/meta/Meta; +} + diff --git a/demo/mks-pdr900/build.gradle.kts b/demo/mks-pdr900/build.gradle.kts index bae6263..dfa7543 100644 --- a/demo/mks-pdr900/build.gradle.kts +++ b/demo/mks-pdr900/build.gradle.kts @@ -17,5 +17,5 @@ val ktorVersion: String by rootProject.extra val dataforgeVersion: String by extra dependencies { - implementation(projects.controlsKtorTcp) + implementation(projects.controlsPortsKtor) } diff --git a/demo/motors/api/motors.api b/demo/motors/api/motors.api new file mode 100644 index 0000000..506d7ee --- /dev/null +++ b/demo/motors/api/motors.api @@ -0,0 +1,115 @@ +public final class ru/mipt/npm/devices/pimotionmaster/FxDevicePropertiesKt { + public static final fun fxProperty (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/DevicePropertySpec;)Ljavafx/beans/property/ReadOnlyProperty; + public static final fun fxProperty (Lspace/kscience/controls/api/Device;Lspace/kscience/controls/spec/WritableDevicePropertySpec;)Ljavafx/beans/property/Property; +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiDebugServerKt { + public static final fun getExceptionHandler ()Lkotlinx/coroutines/CoroutineExceptionHandler; + public static final fun launchPiDebugServer (Lspace/kscience/dataforge/context/Context;ILjava/util/List;)Lkotlinx/coroutines/Job; + public static final fun main ()V + public static synthetic fun main ([Ljava/lang/String;)V +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterApp : tornadofx/App { + public fun ()V +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterAppKt { + public static final fun axisPane (Ljavafx/scene/Parent;Ljava/util/Map;Lkotlinx/coroutines/CoroutineScope;)V + public static final fun main ()V + public static synthetic fun main ([Ljava/lang/String;)V + public static final fun piMotionMasterAxis (Ljavafx/scene/layout/VBox;Ljava/lang/String;Lru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice$Axis;Lkotlinx/coroutines/CoroutineScope;)Ljavafx/scene/layout/HBox; +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterController : tornadofx/Controller { + public fun ()V + public final fun getContext ()Lspace/kscience/dataforge/context/Context; + public final fun getDeviceManager ()Lspace/kscience/controls/manager/DeviceManager; + public final fun getMotionMaster ()Lru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice; +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice : space/kscience/controls/spec/DeviceBySpec, space/kscience/controls/api/DeviceHub { + public static final field Companion Lru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice$Companion; + public fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/controls/ports/PortFactory;)V + public synthetic fun (Lspace/kscience/dataforge/context/Context;Lspace/kscience/controls/ports/PortFactory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun connect (Ljava/lang/String;I)V + public final fun disconnect ()V + public final fun getAxes ()Ljava/util/Map; + public fun getDevices ()Ljava/util/Map; + public final fun getErrorCode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getTimeoutValue-UwyO8pc ()J + public final fun setTimeoutValue-LRDsOJo (J)V +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice$Axis : space/kscience/controls/spec/DeviceBySpec { + public static final field Companion Lru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice$Axis$Companion; + public fun (Lru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice;Ljava/lang/String;)V + public final fun getAxisId ()Ljava/lang/String; + public final fun getMm ()Lru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice; + public final fun move (DLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice$Axis$Companion : space/kscience/controls/spec/DeviceSpec { + public final fun getClosedLoop ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getEnabled ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getHalt ()Lspace/kscience/controls/spec/DeviceActionSpec; + public final fun getMaxPosition ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getMinPosition ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getMove ()Lspace/kscience/controls/spec/DeviceActionSpec; + public final fun getMoveToReference ()Lspace/kscience/controls/spec/DeviceActionSpec; + public final fun getOnTarget ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getOpenLoopTarget ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getPosition ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getReference ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getTargetPosition ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; + public final fun getVelocity ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice$Companion : space/kscience/controls/spec/DeviceSpec, space/kscience/dataforge/context/Factory { + public synthetic fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Ljava/lang/Object; + public fun build (Lspace/kscience/dataforge/context/Context;Lspace/kscience/dataforge/meta/Meta;)Lru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice; + public final fun getConnect ()Lspace/kscience/controls/spec/DeviceActionSpec; + public final fun getConnected ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getDisconnect ()Lspace/kscience/controls/spec/DeviceActionSpec; + public final fun getFirmwareVersion ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getIdentity ()Lspace/kscience/controls/spec/DevicePropertySpec; + public final fun getInitialize ()Lspace/kscience/controls/spec/DeviceActionSpec; + public final fun getStop ()Lspace/kscience/controls/spec/DeviceActionSpec; + public final fun getTimeout ()Lspace/kscience/controls/spec/WritableDevicePropertySpec; +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterView : tornadofx/View { + public fun ()V + public final fun getDevice ()Lru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice; + public fun getRoot ()Ljavafx/scene/Parent; +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterVirtualDevice : ru/mipt/npm/devices/pimotionmaster/VirtualDevice, space/kscience/dataforge/context/ContextAware { + public static final field Companion Lru/mipt/npm/devices/pimotionmaster/PiMotionMasterVirtualDevice$Companion; + public fun (Lspace/kscience/dataforge/context/Context;Ljava/util/List;Lkotlinx/coroutines/CoroutineScope;)V + public synthetic fun (Lspace/kscience/dataforge/context/Context;Ljava/util/List;Lkotlinx/coroutines/CoroutineScope;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun getContext ()Lspace/kscience/dataforge/context/Context; +} + +public final class ru/mipt/npm/devices/pimotionmaster/PiMotionMasterVirtualDevice$Companion { +} + +public abstract class ru/mipt/npm/devices/pimotionmaster/VirtualDevice : space/kscience/controls/api/Socket { + public fun (Lkotlinx/coroutines/CoroutineScope;)V + public fun close ()V + protected abstract fun evaluateRequest ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getScope ()Lkotlinx/coroutines/CoroutineScope; + public fun isOpen ()Z + public fun receiving ()Lkotlinx/coroutines/flow/Flow; + protected final fun respond ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun respondInFuture-VtjQ1oo (JLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/Job; + public synthetic fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun send ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected fun transformRequests (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow; +} + +public final class ru/mipt/npm/devices/pimotionmaster/VirtualPort : space/kscience/controls/ports/AbstractPort { + public fun (Lru/mipt/npm/devices/pimotionmaster/VirtualDevice;Lspace/kscience/dataforge/context/Context;)V + public fun close ()V +} + diff --git a/demo/motors/build.gradle.kts b/demo/motors/build.gradle.kts index b60ebb9..8626c72 100644 --- a/demo/motors/build.gradle.kts +++ b/demo/motors/build.gradle.kts @@ -23,7 +23,7 @@ val ktorVersion: String by rootProject.extra val dataforgeVersion: String by extra dependencies { - implementation(project(":controls-ktor-tcp")) - implementation(project(":controls-magix-client")) + implementation(project(":controls-ports-ktor")) + implementation(projects.controlsMagix) implementation("no.tornado:tornadofx:1.7.20") } diff --git a/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt b/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt index 3a36131..4eff6c4 100644 --- a/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt +++ b/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/PiMotionMasterDevice.kt @@ -162,7 +162,7 @@ class PiMotionMasterDevice( send("STP") } - val connect by metaAction(descriptorBuilder = { + val connect by action(MetaConverter.meta, MetaConverter.unit, descriptorBuilder = { info = "Connect to specific port and initialize axis" }) { portSpec -> //Clear current actions if present @@ -171,36 +171,32 @@ class PiMotionMasterDevice( } //Update port //address = portSpec.node - port = portFactory(portSpec ?: Meta.EMPTY, context) + port = portFactory(portSpec, context) updateLogical(connected, true) // connector.open() //Initialize axes - if (portSpec != null) { - val idn = read(identity) - failIfError { "Can't connect to $portSpec. Error code: $it" } - logger.info { "Connected to $idn on $portSpec" } - val ids = request("SAI?").map { it.trim() } - if (ids != axes.keys.toList()) { - //re-define axes if needed - axes = ids.associateWith { Axis(this, it) } - } - Meta(ids.map { it.asValue() }.asValue()) - execute(initialize) - failIfError() + val idn = read(identity) + failIfError { "Can't connect to $portSpec. Error code: $it" } + logger.info { "Connected to $idn on $portSpec" } + val ids = request("SAI?").map { it.trim() } + if (ids != axes.keys.toList()) { + //re-define axes if needed + axes = ids.associateWith { Axis(this, it) } } - null + Meta(ids.map { it.asValue() }.asValue()) + execute(initialize) + failIfError() } - val disconnect by metaAction({ + val disconnect by unitAction({ info = "Disconnect the program from the device if it is connected" }) { - port?.let{ + port?.let { execute(stop) it.close() } port = null updateLogical(connected, false) - null } @@ -212,7 +208,7 @@ class PiMotionMasterDevice( class Axis( val mm: PiMotionMasterDevice, - val axisId: String + val axisId: String, ) : DeviceBySpec(Axis, mm.context) { /** @@ -244,7 +240,7 @@ class PiMotionMasterDevice( private fun axisBooleanProperty( command: String, - descriptorBuilder: PropertyDescriptor.() -> Unit = {} + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, ) = booleanProperty( read = { readAxisBoolean("$command?") @@ -257,7 +253,7 @@ class PiMotionMasterDevice( private fun axisNumberProperty( command: String, - descriptorBuilder: PropertyDescriptor.() -> Unit = {} + descriptorBuilder: PropertyDescriptor.() -> Unit = {}, ) = doubleProperty( read = { mm.requestAndParse("$command?", axisId)[axisId]?.toDoubleOrNull() @@ -334,11 +330,11 @@ class PiMotionMasterDevice( info = "Velocity value for closed-loop operation" } - val move by metaAction { - val target = it.double ?: it?.get("target").double ?: error("Unacceptable target value $it") + val move by action(MetaConverter.meta, MetaConverter.unit) { + val target = it.double ?: it["target"].double ?: error("Unacceptable target value $it") write(closedLoop, true) //optionally set velocity - it?.get("velocity").double?.let { v -> + it["velocity"].double?.let { v -> write(velocity, v) } write(targetPosition, target) @@ -347,7 +343,6 @@ class PiMotionMasterDevice( read(position) delay(200) } - null } } diff --git a/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/fxDeviceProperties.kt b/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/fxDeviceProperties.kt index e8b4e68..0328631 100644 --- a/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/fxDeviceProperties.kt +++ b/demo/motors/src/main/kotlin/ru/mipt/npm/devices/pimotionmaster/fxDeviceProperties.kt @@ -12,8 +12,8 @@ import tornadofx.* /** * Bind a FX property to a device property with a given [spec] */ -fun Device.fxProperty( - spec: DevicePropertySpec +fun D.fxProperty( + spec: DevicePropertySpec, ): ReadOnlyProperty = object : ObjectPropertyBase() { override fun getBean(): Any = this override fun getName(): String = spec.name @@ -21,16 +21,12 @@ fun Device.fxProperty( init { //Read incoming changes onPropertyChange(spec) { - if (it != null) { - runLater { - try { - set(it) - } catch (ex: Throwable) { - logger.info { "Failed to set property $name to $it" } - } + runLater { + try { + set(it) + } catch (ex: Throwable) { + logger.info { "Failed to set property $name to $it" } } - } else { - invalidated() } } } @@ -44,16 +40,12 @@ fun D.fxProperty(spec: WritableDevicePropertySpec): init { //Read incoming changes onPropertyChange(spec) { - if (it != null) { - runLater { - try { - set(it) - } catch (ex: Throwable) { - logger.info { "Failed to set property $name to $it" } - } + runLater { + try { + set(it) + } catch (ex: Throwable) { + logger.info { "Failed to set property $name to $it" } } - } else { - invalidated() } } diff --git a/gradle.properties b/gradle.properties index 3ba1c64..5b956f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,4 +7,7 @@ org.gradle.parallel=true publishing.github=false publishing.sonatype=false -toolsVersion=0.14.6-kotlin-1.8.20 \ No newline at end of file +org.gradle.configureondemand=true +org.gradle.jvmargs=-Xmx4096m + +toolsVersion=0.14.10-kotlin-1.9.0 \ No newline at end of file diff --git a/magix/README.md b/magix/README.md deleted file mode 100644 index de1d7a3..0000000 --- a/magix/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Module magix - - - diff --git a/magix/build.gradle.kts b/magix/build.gradle.kts deleted file mode 100644 index ae31ca2..0000000 --- a/magix/build.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -subprojects{ - -} \ No newline at end of file diff --git a/magix/magix-api/README.md b/magix/magix-api/README.md index 0226fc6..ee00c53 100644 --- a/magix/magix-api/README.md +++ b/magix/magix-api/README.md @@ -1,32 +1,23 @@ # Module magix-api - +A kotlin API for magix standard and some zero-dependency magix services ## Usage ## Artifact: -The Maven coordinates of this project are `space.kscience:magix-api:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:magix-api:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:magix-api:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:magix-api:0.1.1-SNAPSHOT") + implementation("space.kscience:magix-api:0.2.0") } ``` diff --git a/magix/magix-api/api/magix-api.api b/magix/magix-api/api/magix-api.api new file mode 100644 index 0000000..dabc4cc --- /dev/null +++ b/magix/magix-api/api/magix-api.api @@ -0,0 +1,271 @@ +public abstract interface class space/kscience/magix/api/MagixEndpoint { + public static final field Companion Lspace/kscience/magix/api/MagixEndpoint$Companion; + public static final field DEFAULT_MAGIX_HTTP_PORT I + public static final field DEFAULT_MAGIX_RAW_PORT I + public static final field DEFAULT_MAGIX_ZMQ_PUB_PORT I + public static final field DEFAULT_MAGIX_ZMQ_PULL_PORT I + public abstract fun broadcast (Lspace/kscience/magix/api/MagixMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun close ()V + public abstract fun subscribe (Lspace/kscience/magix/api/MagixMessageFilter;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun subscribe$default (Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; +} + +public final class space/kscience/magix/api/MagixEndpoint$Companion { + public static final field DEFAULT_MAGIX_HTTP_PORT I + public static final field DEFAULT_MAGIX_RAW_PORT I + public static final field DEFAULT_MAGIX_ZMQ_PUB_PORT I + public static final field DEFAULT_MAGIX_ZMQ_PULL_PORT I + public final fun getMagixJson ()Lkotlinx/serialization/json/Json; +} + +public final class space/kscience/magix/api/MagixEndpointKt { + public static final fun send (Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class space/kscience/magix/api/MagixFlowPlugin { + public abstract fun start (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; + public fun start (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/MutableSharedFlow;)Lkotlinx/coroutines/Job; +} + +public final class space/kscience/magix/api/MagixFormat { + public fun (Lkotlinx/serialization/KSerializer;Ljava/util/Set;)V + public final fun component1 ()Lkotlinx/serialization/KSerializer; + public final fun component2 ()Ljava/util/Set; + public final fun copy (Lkotlinx/serialization/KSerializer;Ljava/util/Set;)Lspace/kscience/magix/api/MagixFormat; + public static synthetic fun copy$default (Lspace/kscience/magix/api/MagixFormat;Lkotlinx/serialization/KSerializer;Ljava/util/Set;ILjava/lang/Object;)Lspace/kscience/magix/api/MagixFormat; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefaultFormat ()Ljava/lang/String; + public final fun getFormats ()Ljava/util/Set; + public final fun getSerializer ()Lkotlinx/serialization/KSerializer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/magix/api/MagixFormatKt { + public static final fun send (Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixFormat;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun send$default (Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixFormat;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun subscribe (Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixFormat;Ljava/util/Collection;Ljava/util/Collection;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun subscribe$default (Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixFormat;Ljava/util/Collection;Ljava/util/Collection;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; +} + +public final class space/kscience/magix/api/MagixMessage { + public static final field Companion Lspace/kscience/magix/api/MagixMessage$Companion; + public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lkotlinx/serialization/json/JsonElement; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Lkotlinx/serialization/json/JsonElement; + public final fun copy (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)Lspace/kscience/magix/api/MagixMessage; + public static synthetic fun copy$default (Lspace/kscience/magix/api/MagixMessage;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILjava/lang/Object;)Lspace/kscience/magix/api/MagixMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getFormat ()Ljava/lang/String; + public final fun getId ()Ljava/lang/String; + public final fun getParentId ()Ljava/lang/String; + public final fun getPayload ()Lkotlinx/serialization/json/JsonElement; + public final fun getSourceEndpoint ()Ljava/lang/String; + public final fun getTargetEndpoint ()Ljava/lang/String; + public final fun getUser ()Lkotlinx/serialization/json/JsonElement; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/magix/api/MagixMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/api/MagixMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/magix/api/MagixMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/magix/api/MagixMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/magix/api/MagixMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/api/MagixMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/api/MagixMessageFilter { + public static final field Companion Lspace/kscience/magix/api/MagixMessageFilter$Companion; + public fun ()V + public synthetic fun (ILjava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;)V + public synthetic fun (Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun accepts (Lspace/kscience/magix/api/MagixMessage;)Z + public final fun component1 ()Ljava/util/Collection; + public final fun component2 ()Ljava/util/Collection; + public final fun component3 ()Ljava/util/Collection; + public final fun copy (Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;)Lspace/kscience/magix/api/MagixMessageFilter; + public static synthetic fun copy$default (Lspace/kscience/magix/api/MagixMessageFilter;Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;ILjava/lang/Object;)Lspace/kscience/magix/api/MagixMessageFilter; + public fun equals (Ljava/lang/Object;)Z + public final fun getFormat ()Ljava/util/Collection; + public final fun getSource ()Ljava/util/Collection; + public final fun getTarget ()Ljava/util/Collection; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/magix/api/MagixMessageFilter;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/api/MagixMessageFilter$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/magix/api/MagixMessageFilter$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/magix/api/MagixMessageFilter; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/magix/api/MagixMessageFilter;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/api/MagixMessageFilter$Companion { + public final fun getALL ()Lspace/kscience/magix/api/MagixMessageFilter; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/api/MagixMessageFilterKt { + public static final fun filter (Lkotlinx/coroutines/flow/Flow;Lspace/kscience/magix/api/MagixMessageFilter;)Lkotlinx/coroutines/flow/Flow; +} + +public final class space/kscience/magix/api/MagixMessageKt { + public static final fun getUserName (Lspace/kscience/magix/api/MagixMessage;)Ljava/lang/String; +} + +public final class space/kscience/magix/services/ConvertersKt { + public static final fun launchMagixConverter (Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixMessageFilter;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; + public static synthetic fun launchMagixConverter$default (Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixMessageFilter;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; +} + +public final class space/kscience/magix/services/MagixPortalKt { + public static final fun launchMagixPortal (Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixMessageFilter;Lspace/kscience/magix/api/MagixMessageFilter;)Lkotlinx/coroutines/Job; + public static synthetic fun launchMagixPortal$default (Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/api/MagixMessageFilter;Lspace/kscience/magix/api/MagixMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/Job; +} + +public abstract interface class space/kscience/magix/services/MagixRegistry { + public abstract fun get (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class space/kscience/magix/services/MagixRegistryErrorMessage : space/kscience/magix/services/MagixRegistryMessage { + public static final field Companion Lspace/kscience/magix/services/MagixRegistryErrorMessage$Companion; + public synthetic fun (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getErrorMessage ()Ljava/lang/String; + public final fun getErrorType ()Ljava/lang/String; + public fun getPropertyName ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/magix/services/MagixRegistryErrorMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/services/MagixRegistryErrorMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/magix/services/MagixRegistryErrorMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/magix/services/MagixRegistryErrorMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/magix/services/MagixRegistryErrorMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/services/MagixRegistryErrorMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/services/MagixRegistryKt { + public static final fun getProperty (Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getProperty$default (Lspace/kscience/magix/api/MagixEndpoint;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun launchMagixRegistry (Lkotlinx/coroutines/CoroutineScope;Ljava/lang/String;Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/services/MagixRegistry;Ljava/util/Collection;Ljava/util/Collection;)Lkotlinx/coroutines/Job; + public static synthetic fun launchMagixRegistry$default (Lkotlinx/coroutines/CoroutineScope;Ljava/lang/String;Lspace/kscience/magix/api/MagixEndpoint;Lspace/kscience/magix/services/MagixRegistry;Ljava/util/Collection;Ljava/util/Collection;ILjava/lang/Object;)Lkotlinx/coroutines/Job; +} + +public abstract class space/kscience/magix/services/MagixRegistryMessage { + public static final field Companion Lspace/kscience/magix/services/MagixRegistryMessage$Companion; + public synthetic fun (ILkotlinx/serialization/internal/SerializationConstructorMarker;)V + public abstract fun getPropertyName ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/magix/services/MagixRegistryMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/services/MagixRegistryMessage$Companion { + public final fun getFormat ()Lspace/kscience/magix/api/MagixFormat; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/services/MagixRegistryModifyMessage : space/kscience/magix/services/MagixRegistryMessage { + public static final field Companion Lspace/kscience/magix/services/MagixRegistryModifyMessage$Companion; + public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public fun getPropertyName ()Ljava/lang/String; + public final fun getValue ()Lkotlinx/serialization/json/JsonElement; + public static final synthetic fun write$Self (Lspace/kscience/magix/services/MagixRegistryModifyMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/services/MagixRegistryModifyMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/magix/services/MagixRegistryModifyMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/magix/services/MagixRegistryModifyMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/magix/services/MagixRegistryModifyMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/services/MagixRegistryModifyMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/services/MagixRegistryRequestMessage : space/kscience/magix/services/MagixRegistryMessage { + public static final field Companion Lspace/kscience/magix/services/MagixRegistryRequestMessage$Companion; + public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;)V + public fun getPropertyName ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/magix/services/MagixRegistryRequestMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/services/MagixRegistryRequestMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/magix/services/MagixRegistryRequestMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/magix/services/MagixRegistryRequestMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/magix/services/MagixRegistryRequestMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/services/MagixRegistryRequestMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/services/MagixRegistryValueMessage : space/kscience/magix/services/MagixRegistryMessage { + public static final field Companion Lspace/kscience/magix/services/MagixRegistryValueMessage$Companion; + public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public fun getPropertyName ()Ljava/lang/String; + public final fun getValue ()Lkotlinx/serialization/json/JsonElement; + public static final synthetic fun write$Self (Lspace/kscience/magix/services/MagixRegistryValueMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/services/MagixRegistryValueMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/magix/services/MagixRegistryValueMessage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/magix/services/MagixRegistryValueMessage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/magix/services/MagixRegistryValueMessage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/services/MagixRegistryValueMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class space/kscience/magix/services/MutableMagixRegistry { + public abstract fun set (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + diff --git a/magix/magix-api/build.gradle.kts b/magix/magix-api/build.gradle.kts index 5f8bdf3..159989d 100644 --- a/magix/magix-api/build.gradle.kts +++ b/magix/magix-api/build.gradle.kts @@ -1,8 +1,14 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.mpp") `maven-publish` } +description = """ + A kotlin API for magix standard and some zero-dependency magix services +""".trimIndent() + kscience { jvm() js() @@ -13,3 +19,6 @@ kscience { } } +readme{ + maturity = Maturity.EXPERIMENTAL +} diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixEndpoint.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixEndpoint.kt index 35cb8e5..19cf2f5 100644 --- a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixEndpoint.kt +++ b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixEndpoint.kt @@ -53,4 +53,9 @@ public interface MagixEndpoint { encodeDefaults = false } } -} \ No newline at end of file +} + +/** + * An alias for [MagixEndpoint.send] + */ +public suspend fun MagixEndpoint.send(message: MagixMessage): Unit = broadcast(message) \ No newline at end of file diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixFlowPlugin.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixFlowPlugin.kt index 8cf9ccd..83c95cc 100644 --- a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixFlowPlugin.kt +++ b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixFlowPlugin.kt @@ -2,8 +2,28 @@ package space.kscience.magix.api import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +/** + * A plugin that could be inserted into basic loop implementation. + */ public fun interface MagixFlowPlugin { - public fun start(scope: CoroutineScope, magixFlow: MutableSharedFlow): Job + + /** + * Attach a [Job] to magix loop. + * Receive messages from [receive]. + * Send messages via [sendMessage] + */ + public fun start( + scope: CoroutineScope, + receive: Flow, + sendMessage: suspend (MagixMessage) -> Unit, + ): Job + + /** + * Use the same [MutableSharedFlow] to send and receive messages. Could be a bottleneck in case of many plugins. + */ + public fun start(scope: CoroutineScope, magixFlow: MutableSharedFlow): Job = + start(scope, magixFlow) { magixFlow.emit(it) } } \ No newline at end of file diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixFormat.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixFormat.kt index 4921129..115fcff 100644 --- a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixFormat.kt +++ b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixFormat.kt @@ -6,6 +6,11 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.json.JsonElement import space.kscience.magix.api.MagixEndpoint.Companion.magixJson +/** + * A format for [MagixMessage] that allows to decode typed payload + * + * @param formats allowed values of the format field that are processed. The first value is the primary format for sending. + */ public data class MagixFormat( val serializer: KSerializer, val formats: Set, @@ -13,31 +18,40 @@ public data class MagixFormat( val defaultFormat: String get() = formats.firstOrNull() ?: "magix" } +/** + * Subscribe for messages in given endpoint using [format] to declare format filter as well as automatic decoding. + * + * @return a flow of pairs (raw message, decoded message). Raw messages are to be used to extract headers. + */ public fun MagixEndpoint.subscribe( format: MagixFormat, - originFilter: Collection? = null, - targetFilter: Collection? = null, + originFilter: Collection? = null, + targetFilter: Collection? = null, ): Flow> = subscribe( - MagixMessageFilter(format = format.formats, origin = originFilter, target = targetFilter) + MagixMessageFilter(format = format.formats, source = originFilter, target = targetFilter) ).map { val value: T = magixJson.decodeFromJsonElement(format.serializer, it.payload) it to value } -public suspend fun MagixEndpoint.broadcast( +/** + * Send a message using given [format] to encode the message payload. The format field is also taken from [format]. + * + */ +public suspend fun MagixEndpoint.send( format: MagixFormat, payload: T, + source: String, target: String? = null, id: String? = null, parentId: String? = null, user: JsonElement? = null, - origin: String = format.defaultFormat, ) { val message = MagixMessage( format = format.defaultFormat, payload = magixJson.encodeToJsonElement(format.serializer, payload), - origin = origin, - target = target, + sourceEndpoint = source, + targetEndpoint = target, id = id, parentId = parentId, user = user diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixMessage.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixMessage.kt index c25c08a..c1557e0 100644 --- a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixMessage.kt +++ b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixMessage.kt @@ -1,7 +1,7 @@ package space.kscience.magix.api import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.* /* @@ -27,9 +27,20 @@ import kotlinx.serialization.json.JsonElement public data class MagixMessage( val format: String, val payload: JsonElement, - val origin: String, - val target: String? = null, + val sourceEndpoint: String, + val targetEndpoint: String? = null, val id: String? = null, val parentId: String? = null, val user: JsonElement? = null, -) \ No newline at end of file +) + +/** + * The default accessor for username. If `user` is an object, take it's "name" field. + * If it is primitive, take its content. Return "@error" if it is an array. + */ +public val MagixMessage.userName: String? get() = when(user){ + null, JsonNull -> null + is JsonObject -> user.jsonObject["name"]?.jsonPrimitive?.content + is JsonPrimitive -> user.content + else -> "@error" +} \ No newline at end of file diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixMessageFilter.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixMessageFilter.kt index 4ce1995..4bd4746 100644 --- a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixMessageFilter.kt +++ b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixMessageFilter.kt @@ -4,17 +4,20 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.serialization.Serializable +/** + * A filter that allows receiving only messages with format, origin and target in given list. + */ @Serializable public data class MagixMessageFilter( - val format: Collection? = null, - val origin: Collection? = null, - val target: Collection? = null, + val format: Collection? = null, + val source: Collection? = null, + val target: Collection? = null, ) { public fun accepts(message: MagixMessage): Boolean = format?.contains(message.format) ?: true - && origin?.contains(message.origin) ?: true - && target?.contains(message.target) ?: true + && source?.contains(message.sourceEndpoint) ?: true + && target?.contains(message.targetEndpoint) ?: true public companion object { public val ALL: MagixMessageFilter = MagixMessageFilter() diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixRegistry.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixRegistry.kt deleted file mode 100644 index 7b3b111..0000000 --- a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/MagixRegistry.kt +++ /dev/null @@ -1,17 +0,0 @@ -package space.kscience.magix.api - -import kotlinx.serialization.json.JsonElement - -/** - * An interface to access distributed Magix property registry - */ -public interface MagixRegistry { - /** - * Request a property with name [propertyName] and user authentication data [user]. - * - * Return a property value in its generic form or null if it is not present. - * - * Throw an exception if property is present but access is denied. - */ - public suspend fun request(propertyName: String, user: JsonElement? = null): JsonElement? -} diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/converters.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/converters.kt deleted file mode 100644 index 441e141..0000000 --- a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/api/converters.kt +++ /dev/null @@ -1,30 +0,0 @@ -package space.kscience.magix.api - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.serialization.json.JsonElement - -/** - * Launch magix message converter service - */ -public fun CoroutineScope.launchMagixConverter( - endpoint: MagixEndpoint, - filter: MagixMessageFilter, - outputFormat: String, - newOrigin: String? = null, - transformer: suspend (JsonElement) -> JsonElement, -): Job = endpoint.subscribe(filter).onEach { message-> - val newPayload = transformer(message.payload) - val transformed: MagixMessage = MagixMessage( - outputFormat, - newPayload, - newOrigin ?: message.origin, - message.target, - message.id, - message.parentId, - message.user - ) - endpoint.broadcast(transformed) -}.launchIn(this) diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/services/MagixRegistry.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/services/MagixRegistry.kt new file mode 100644 index 0000000..3272131 --- /dev/null +++ b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/services/MagixRegistry.kt @@ -0,0 +1,153 @@ +package space.kscience.magix.services + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import space.kscience.magix.api.MagixEndpoint +import space.kscience.magix.api.MagixFormat +import space.kscience.magix.api.send +import space.kscience.magix.api.subscribe + +/** + * An interface to access distributed Magix property registry + */ +public interface MagixRegistry { + /** + * Request a property with name [propertyName]. + * + * Return a property value in its generic form or null if it is not present. + * + * Throw exception access is denied, or request failed. + */ + public suspend fun get(propertyName: String): JsonElement? +} + +public interface MutableMagixRegistry { + public suspend fun set(propertyName: String, value: JsonElement?, user: JsonElement?) +} + +@Serializable +public sealed class MagixRegistryMessage { + public abstract val propertyName: String + + public companion object { + public val format: MagixFormat = MagixFormat(serializer(), setOf("magix.registry")) + } +} + +@Serializable +@SerialName("registry.request") +public class MagixRegistryRequestMessage( + override val propertyName: String, +) : MagixRegistryMessage() + +@Serializable +@SerialName("registry.value") +public class MagixRegistryValueMessage( + override val propertyName: String, + public val value: JsonElement?, +) : MagixRegistryMessage() + +@Serializable +@SerialName("registry.error") +public class MagixRegistryErrorMessage( + override val propertyName: String, + public val errorType: String?, + public val errorMessage: String? = null, +) : MagixRegistryMessage() + +@Serializable +@SerialName("registry.modify") +public class MagixRegistryModifyMessage( + override val propertyName: String, + public val value: JsonElement, +) : MagixRegistryMessage() + +/** + * Launch a magix registry loop service based on local registry + */ +public fun CoroutineScope.launchMagixRegistry( + endpointName: String, + endpoint: MagixEndpoint, + registry: MagixRegistry, + originFilter: Collection? = null, + targetFilter: Collection? = null, +): Job = endpoint.subscribe(MagixRegistryMessage.format, originFilter, targetFilter) + .onEach { (magixMessage, payload) -> + try { + when { + payload is MagixRegistryRequestMessage -> { + endpoint.send( + MagixRegistryMessage.format, + MagixRegistryValueMessage(payload.propertyName, registry.get(payload.propertyName) ?: JsonNull), + source = endpointName, + target = magixMessage.sourceEndpoint, + parentId = magixMessage.id + ) + } + + payload is MagixRegistryModifyMessage && registry is MutableMagixRegistry -> { + registry.set(payload.propertyName, payload.value, magixMessage.user) + // Broadcast updates. Do not set target + endpoint.send( + MagixRegistryMessage.format, + MagixRegistryValueMessage( + payload.propertyName, + registry.get(payload.propertyName) + ), + source = endpointName, + parentId = magixMessage.id + ) + } + } + } catch (ex: Exception) { + endpoint.send( + MagixRegistryMessage.format, + MagixRegistryErrorMessage(payload.propertyName, ex::class.simpleName, ex.message), + source = endpointName, + target = magixMessage.sourceEndpoint, + parentId = magixMessage.id + ) + } + }.launchIn(this) + +/** + * Request a property with given name and return a [Flow] of pairs (sourceEndpoint, value). + * + * Flow is ordered by response receive time. + * The subscriber can terminate the flow at any moment to stop subscription, or use it indefinitely to continue observing changes. + * To request a single value, use [Flow.first] function. + * + * If [registryEndpoint] field is provided, send request only to given endpoint. + * + * @param endpointName the name of endpoint requesting a property + */ +public suspend fun MagixEndpoint.getProperty( + propertyName: String, + endpointName: String, + user: JsonElement? = null, + registryEndpoint: String? = null, +): Flow> = subscribe( + MagixRegistryMessage.format, + originFilter = registryEndpoint?.let { setOf(it) } +).mapNotNull { (message, response) -> + if (response is MagixRegistryValueMessage && response.propertyName == propertyName) { + message.sourceEndpoint to (response.value ?: return@mapNotNull null) + } else null +}.also { + //send the initial request after subscription + send( + MagixRegistryMessage.format, + MagixRegistryRequestMessage(propertyName), + source = endpointName, + target = registryEndpoint, + user = user + ) +} \ No newline at end of file diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/services/converters.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/services/converters.kt new file mode 100644 index 0000000..ac6e6e2 --- /dev/null +++ b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/services/converters.kt @@ -0,0 +1,41 @@ +package space.kscience.magix.services + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.serialization.json.JsonElement +import space.kscience.magix.api.MagixEndpoint +import space.kscience.magix.api.MagixMessage +import space.kscience.magix.api.MagixMessageFilter + +/** + * Launch magix message converter service. + * + * The service converts a payload from one format into another. + * + * @param endpoint The endpoint this converter is attached to + * @param filter a filter for messages to be converted. + * @param outputFormat a new value for [MagixMessage.format] field + * @param newSource a new value of [MagixMessage.sourceEndpoint]. By default uses the original message value + * @param transformer a function to transform the payload. + */ +public fun CoroutineScope.launchMagixConverter( + endpoint: MagixEndpoint, + filter: MagixMessageFilter, + outputFormat: String, + newSource: String? = null, + transformer: suspend (JsonElement) -> JsonElement, +): Job = endpoint.subscribe(filter).onEach { message-> + val newPayload = transformer(message.payload) + val transformed: MagixMessage = MagixMessage( + outputFormat, + newPayload, + newSource ?: message.sourceEndpoint, + message.targetEndpoint, + message.id, + message.parentId, + message.user + ) + endpoint.broadcast(transformed) +}.launchIn(this) diff --git a/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/services/magixPortal.kt b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/services/magixPortal.kt new file mode 100644 index 0000000..d9ed009 --- /dev/null +++ b/magix/magix-api/src/commonMain/kotlin/space/kscience/magix/services/magixPortal.kt @@ -0,0 +1,30 @@ +package space.kscience.magix.services + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import space.kscience.magix.api.MagixEndpoint +import space.kscience.magix.api.MagixMessageFilter + +/** + * Create a gateway between two magix endpoints using filters for forward and backward message passing. + * Portal is useful to create segmented magix loops: + * * limit the load on given loop segment by filtering some messages; + * * use different loop implementations. + */ +public fun CoroutineScope.launchMagixPortal( + firstEndpoint: MagixEndpoint, + secondEndpoint: MagixEndpoint, + forwardFilter: MagixMessageFilter = MagixMessageFilter.ALL, + backwardFilter: MagixMessageFilter = MagixMessageFilter.ALL, +): Job = launch { + firstEndpoint.subscribe(forwardFilter).onEach { + secondEndpoint.broadcast(it) + }.launchIn(this) + + secondEndpoint.subscribe(backwardFilter).onEach { + firstEndpoint.broadcast(it) + }.launchIn(this) +} diff --git a/magix/magix-java-client/README.md b/magix/magix-java-client/README.md deleted file mode 100644 index 375d6df..0000000 --- a/magix/magix-java-client/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Module magix-java-client - - - -## Usage - -## Artifact: - -The Maven coordinates of this project are `space.kscience:magix-java-client:0.1.1-SNAPSHOT`. - -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:magix-java-client:0.1.1-SNAPSHOT' -} -``` -**Gradle Kotlin DSL:** -```kotlin -repositories { - maven("https://repo.kotlin.link") - mavenCentral() -} - -dependencies { - implementation("space.kscience:magix-java-client:0.1.1-SNAPSHOT") -} -``` diff --git a/magix/magix-java-endpoint/README.md b/magix/magix-java-endpoint/README.md new file mode 100644 index 0000000..abcaa6f --- /dev/null +++ b/magix/magix-java-endpoint/README.md @@ -0,0 +1,23 @@ +# Module magix-java-endpoint + +Java API to work with magix endpoints without Kotlin + +## Usage + +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-java-endpoint:0.2.0`. + +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-java-endpoint:0.2.0") +} +``` diff --git a/magix/magix-java-endpoint/api/magix-java-client.api b/magix/magix-java-endpoint/api/magix-java-client.api new file mode 100644 index 0000000..5df03d9 --- /dev/null +++ b/magix/magix-java-endpoint/api/magix-java-client.api @@ -0,0 +1,7 @@ +public abstract interface class space/kscience/magix/client/MagixClient { + public abstract fun broadcast (Lspace/kscience/magix/api/MagixMessage;)V + public static fun rSocketTcp (Ljava/lang/String;I)Lspace/kscience/magix/client/MagixClient; + public static fun rSocketWs (Ljava/lang/String;ILjava/lang/String;)Lspace/kscience/magix/client/MagixClient; + public abstract fun subscribe ()Ljava/util/concurrent/Flow$Publisher; +} + diff --git a/magix/magix-java-client/build.gradle.kts b/magix/magix-java-endpoint/build.gradle.kts similarity index 79% rename from magix/magix-java-client/build.gradle.kts rename to magix/magix-java-endpoint/build.gradle.kts index 63041b3..ff51835 100644 --- a/magix/magix-java-client/build.gradle.kts +++ b/magix/magix-java-endpoint/build.gradle.kts @@ -1,5 +1,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import space.kscience.gradle.KScienceVersions +import space.kscience.gradle.Maturity plugins { java @@ -7,6 +8,10 @@ plugins { `maven-publish` } +description = """ + Java API to work with magix endpoints without Kotlin +""".trimIndent() + dependencies { implementation(project(":magix:magix-rsocket")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:${KScienceVersions.coroutinesVersion}") @@ -23,4 +28,8 @@ tasks.withType{ kotlinOptions { freeCompilerArgs -= "-Xjdk-release=11" } +} + +readme{ + maturity = Maturity.EXPERIMENTAL } \ No newline at end of file diff --git a/magix/magix-java-client/src/main/java/space/kscience/magix/client/MagixClient.java b/magix/magix-java-endpoint/src/main/java/space/kscience/magix/client/JMagixEndpoint.java similarity index 70% rename from magix/magix-java-client/src/main/java/space/kscience/magix/client/MagixClient.java rename to magix/magix-java-endpoint/src/main/java/space/kscience/magix/client/JMagixEndpoint.java index 3270544..08d0a26 100644 --- a/magix/magix-java-client/src/main/java/space/kscience/magix/client/MagixClient.java +++ b/magix/magix-java-endpoint/src/main/java/space/kscience/magix/client/JMagixEndpoint.java @@ -11,7 +11,7 @@ import java.util.concurrent.Flow; * * @param */ -public interface MagixClient { +public interface JMagixEndpoint { void broadcast(MagixMessage msg) throws IOException; Flow.Publisher subscribe(); @@ -22,8 +22,8 @@ public interface MagixClient { * @param port port of magix server event loop * @return the client */ - static MagixClient rSocketTcp(String host, int port) { - return ControlsMagixClient.Companion.rSocketTcp(host, port); + static JMagixEndpoint rSocketTcp(String host, int port) { + return KMagixEndpoint.Companion.rSocketTcp(host, port); } /** @@ -32,7 +32,7 @@ public interface MagixClient { * @param port port of magix server event loop * @param path context path for WS connection */ - static MagixClient rSocketWs(String host, int port, String path) { - return ControlsMagixClient.Companion.rSocketWs(host, port, path); + static JMagixEndpoint rSocketWs(String host, int port, String path) { + return KMagixEndpoint.Companion.rSocketWs(host, port, path); } } diff --git a/magix/magix-java-client/src/main/kotlin/space/kscience/magix/client/ControlsMagixClient.kt b/magix/magix-java-endpoint/src/main/kotlin/space/kscience/magix/client/KMagixEndpoint.kt similarity index 81% rename from magix/magix-java-client/src/main/kotlin/space/kscience/magix/client/ControlsMagixClient.kt rename to magix/magix-java-endpoint/src/main/kotlin/space/kscience/magix/client/KMagixEndpoint.kt index 1efa746..2d20d8f 100644 --- a/magix/magix-java-client/src/main/kotlin/space/kscience/magix/client/ControlsMagixClient.kt +++ b/magix/magix-java-endpoint/src/main/kotlin/space/kscience/magix/client/KMagixEndpoint.kt @@ -9,10 +9,10 @@ import space.kscience.magix.rsocket.rSocketWithTcp import space.kscience.magix.rsocket.rSocketWithWebSockets import java.util.concurrent.Flow -internal class ControlsMagixClient( +internal class KMagixEndpoint( private val endpoint: MagixEndpoint, private val filter: MagixMessageFilter, -) : MagixClient { +) : JMagixEndpoint { override fun broadcast(msg: MagixMessage): Unit = runBlocking { endpoint.broadcast(msg) @@ -25,22 +25,22 @@ internal class ControlsMagixClient( fun rSocketTcp( host: String, port: Int, - ): ControlsMagixClient { + ): KMagixEndpoint { val endpoint = runBlocking { MagixEndpoint.rSocketWithTcp(host, port) } - return ControlsMagixClient(endpoint, MagixMessageFilter()) + return KMagixEndpoint(endpoint, MagixMessageFilter()) } fun rSocketWs( host: String, port: Int, path: String = "/rsocket" - ): ControlsMagixClient { + ): KMagixEndpoint { val endpoint = runBlocking { MagixEndpoint.rSocketWithWebSockets(host, port, path) } - return ControlsMagixClient(endpoint, MagixMessageFilter()) + return KMagixEndpoint(endpoint, MagixMessageFilter()) } } } \ No newline at end of file diff --git a/magix/magix-mqtt/README.md b/magix/magix-mqtt/README.md index d8b9da6..6c34fdc 100644 --- a/magix/magix-mqtt/README.md +++ b/magix/magix-mqtt/README.md @@ -6,27 +6,18 @@ MQTT client magix endpoint ## Artifact: -The Maven coordinates of this project are `space.kscience:magix-mqtt:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:magix-mqtt:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:magix-mqtt:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:magix-mqtt:0.1.1-SNAPSHOT") + implementation("space.kscience:magix-mqtt:0.2.0") } ``` diff --git a/magix/magix-mqtt/src/main/kotlin/space/ksceince/magix/mqtt/MqttMagixEndpoint.kt b/magix/magix-mqtt/src/main/kotlin/space/ksceince/magix/mqtt/MqttMagixEndpoint.kt index c9ac7f7..ae41532 100644 --- a/magix/magix-mqtt/src/main/kotlin/space/ksceince/magix/mqtt/MqttMagixEndpoint.kt +++ b/magix/magix-mqtt/src/main/kotlin/space/ksceince/magix/mqtt/MqttMagixEndpoint.kt @@ -3,6 +3,7 @@ package space.ksceince.magix.mqtt import com.hivemq.client.mqtt.MqttClient import com.hivemq.client.mqtt.datatypes.MqttQos import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import com.hivemq.client.mqtt.mqtt5.Mqtt5ClientBuilder import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow @@ -13,12 +14,21 @@ import space.kscience.magix.api.MagixMessage import space.kscience.magix.api.MagixMessageFilter import java.util.* - +/** + * MQTT5 endpoint for magix. + * + * @param broadcastTopicBuilder defines how the topic is constructed from broadcast message structure. + * By default, use `magix/` topic if target is present and `magix` if it is not. + * @param subscribeTopicBuilder defines how the topic is constructed from the filter. + * By default, uses `magix/` if only a single target is presented and `magix/#` otherwise. + */ public class MqttMagixEndpoint( serverHost: String, clientId: String = UUID.randomUUID().toString(), - public val topic: String = DEFAULT_MAGIX_TOPIC_NAME, - public val qos: MqttQos = MqttQos.AT_LEAST_ONCE, + private val broadcastTopicBuilder: (MagixMessage) -> String = defaultBroadcastTopicBuilder, + private val subscribeTopicBuilder: (MagixMessageFilter) -> String = defaultSubscribeTopicBuilder, + private val qos: MqttQos = MqttQos.AT_LEAST_ONCE, + private val clientConfig: Mqtt5ClientBuilder.() -> Mqtt5ClientBuilder = { this }, ) : MagixEndpoint, AutoCloseable { private val client: Mqtt5AsyncClient by lazy { @@ -26,6 +36,7 @@ public class MqttMagixEndpoint( .identifier(clientId) .serverHost(serverHost) .useMqttVersion5() + .run(clientConfig) .buildAsync() } @@ -36,7 +47,7 @@ public class MqttMagixEndpoint( override fun subscribe(filter: MagixMessageFilter): Flow = callbackFlow { connection.await() client.subscribeWith() - .topicFilter(topic) + .topicFilter(subscribeTopicBuilder(filter)) .qos(qos) .callback { published -> val message = MagixEndpoint.magixJson.decodeFromString( @@ -54,7 +65,7 @@ public class MqttMagixEndpoint( override suspend fun broadcast(message: MagixMessage) { connection.await() - client.publishWith().topic(topic).qos(qos).payload( + client.publishWith().topic(broadcastTopicBuilder(message)).qos(qos).payload( MagixEndpoint.magixJson.encodeToString(MagixMessage.serializer(), message).encodeToByteArray() ).send() } @@ -65,5 +76,19 @@ public class MqttMagixEndpoint( public companion object { public const val DEFAULT_MAGIX_TOPIC_NAME: String = "magix" + + //TODO add target name escaping + + internal val defaultBroadcastTopicBuilder: (MagixMessage) -> String = { message -> + message.targetEndpoint?.let { "$DEFAULT_MAGIX_TOPIC_NAME/it" } ?: DEFAULT_MAGIX_TOPIC_NAME + } + + internal val defaultSubscribeTopicBuilder: (MagixMessageFilter) -> String = { filter -> + if (filter.target?.size == 1) { + "$DEFAULT_MAGIX_TOPIC_NAME/${filter.target!!.first()}" + } else { + "$DEFAULT_MAGIX_TOPIC_NAME/#" + } + } } } \ No newline at end of file diff --git a/magix/magix-rabbit/README.md b/magix/magix-rabbit/README.md index 7c60864..7fc42ad 100644 --- a/magix/magix-rabbit/README.md +++ b/magix/magix-rabbit/README.md @@ -6,27 +6,18 @@ RabbitMQ client magix endpoint ## Artifact: -The Maven coordinates of this project are `space.kscience:magix-rabbit:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:magix-rabbit:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:magix-rabbit:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:magix-rabbit:0.1.1-SNAPSHOT") + implementation("space.kscience:magix-rabbit:0.2.0") } ``` diff --git a/magix/magix-rabbit/src/main/kotlin/space/kscience/magix/rabbit/RabbitMQMagixEndpoint.kt b/magix/magix-rabbit/src/main/kotlin/space/kscience/magix/rabbit/RabbitMQMagixEndpoint.kt index 33af457..561acdd 100644 --- a/magix/magix-rabbit/src/main/kotlin/space/kscience/magix/rabbit/RabbitMQMagixEndpoint.kt +++ b/magix/magix-rabbit/src/main/kotlin/space/kscience/magix/rabbit/RabbitMQMagixEndpoint.kt @@ -27,7 +27,7 @@ public class RabbitMQMagixEndpoint( } override fun subscribe(filter: MagixMessageFilter): Flow = callbackFlow { - val deliverCallback: DeliverCallback = DeliverCallback { _: String, message: Delivery -> + val deliverCallback = DeliverCallback { _: String, message: Delivery -> val magixMessage = MagixEndpoint.magixJson.decodeFromString( MagixMessage.serializer(), message.body.decodeToString() ) diff --git a/magix/magix-rsocket/README.md b/magix/magix-rsocket/README.md index 0d5c9e6..799717d 100644 --- a/magix/magix-rsocket/README.md +++ b/magix/magix-rsocket/README.md @@ -6,27 +6,18 @@ Magix endpoint (client) based on RSocket ## Artifact: -The Maven coordinates of this project are `space.kscience:magix-rsocket:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:magix-rsocket:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:magix-rsocket:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:magix-rsocket:0.1.1-SNAPSHOT") + implementation("space.kscience:magix-rsocket:0.2.0") } ``` diff --git a/magix/magix-rsocket/api/magix-rsocket.api b/magix/magix-rsocket/api/magix-rsocket.api new file mode 100644 index 0000000..59dacca --- /dev/null +++ b/magix/magix-rsocket/api/magix-rsocket.api @@ -0,0 +1,37 @@ +public final class space/kscience/magix/rsocket/RSocketMagixEndpoint : java/io/Closeable, space/kscience/magix/api/MagixEndpoint { + public static final field Companion Lspace/kscience/magix/rsocket/RSocketMagixEndpoint$Companion; + public fun (Lio/rsocket/kotlin/RSocket;)V + public fun broadcast (Lspace/kscience/magix/api/MagixMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun close ()V + public fun subscribe (Lspace/kscience/magix/api/MagixMessageFilter;)Lkotlinx/coroutines/flow/Flow; +} + +public final class space/kscience/magix/rsocket/RSocketMagixEndpoint$Companion { +} + +public final class space/kscience/magix/rsocket/RSocketMagixEndpointKt { + public static final fun rSocketWithWebSockets (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;ILjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun rSocketWithWebSockets$default (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;ILjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class space/kscience/magix/rsocket/RSocketStreamMagixEndpoint : java/io/Closeable, space/kscience/magix/api/MagixEndpoint { + public fun (Lio/rsocket/kotlin/RSocket;Lspace/kscience/magix/api/MagixMessageFilter;)V + public synthetic fun (Lio/rsocket/kotlin/RSocket;Lspace/kscience/magix/api/MagixMessageFilter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun broadcast (Lspace/kscience/magix/api/MagixMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun close ()V + public final fun getStreamFilter ()Lspace/kscience/magix/api/MagixMessageFilter; + public fun subscribe (Lspace/kscience/magix/api/MagixMessageFilter;)Lkotlinx/coroutines/flow/Flow; +} + +public final class space/kscience/magix/rsocket/RSocketStreamMagixEndpointKt { + public static final fun rSocketStreamWithWebSockets (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;ILjava/lang/String;Lspace/kscience/magix/api/MagixMessageFilter;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun rSocketStreamWithWebSockets$default (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;ILjava/lang/String;Lspace/kscience/magix/api/MagixMessageFilter;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class space/kscience/magix/rsocket/WithTcpKt { + public static final fun rSocketStreamWithTcp (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;ILspace/kscience/magix/api/MagixMessageFilter;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun rSocketStreamWithTcp$default (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;ILspace/kscience/magix/api/MagixMessageFilter;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun rSocketWithTcp (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun rSocketWithTcp$default (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + diff --git a/magix/magix-rsocket/build.gradle.kts b/magix/magix-rsocket/build.gradle.kts index 39aa8aa..b2046bb 100644 --- a/magix/magix-rsocket/build.gradle.kts +++ b/magix/magix-rsocket/build.gradle.kts @@ -1,3 +1,5 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.mpp") `maven-publish` @@ -35,4 +37,8 @@ kotlin { } } } +} + +readme { + maturity = Maturity.EXPERIMENTAL } \ No newline at end of file diff --git a/magix/magix-rsocket/src/commonMain/kotlin/space/kscience/magix/rsocket/RSocketMagixEndpoint.kt b/magix/magix-rsocket/src/commonMain/kotlin/space/kscience/magix/rsocket/RSocketMagixEndpoint.kt index 19cc8f8..6c4e9dd 100644 --- a/magix/magix-rsocket/src/commonMain/kotlin/space/kscience/magix/rsocket/RSocketMagixEndpoint.kt +++ b/magix/magix-rsocket/src/commonMain/kotlin/space/kscience/magix/rsocket/RSocketMagixEndpoint.kt @@ -10,7 +10,10 @@ import io.rsocket.kotlin.ktor.client.RSocketSupport import io.rsocket.kotlin.ktor.client.rSocket import io.rsocket.kotlin.payload.buildPayload import io.rsocket.kotlin.payload.data -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -18,7 +21,6 @@ import space.kscience.magix.api.MagixEndpoint import space.kscience.magix.api.MagixMessage import space.kscience.magix.api.MagixMessageFilter import space.kscience.magix.api.filter -import kotlin.coroutines.coroutineContext public class RSocketMagixEndpoint(private val rSocket: RSocket) : MagixEndpoint, Closeable { @@ -34,7 +36,7 @@ public class RSocketMagixEndpoint(private val rSocket: RSocket) : MagixEndpoint, }.filter(filter).flowOn(rSocket.coroutineContext[CoroutineDispatcher] ?: Dispatchers.Unconfined) } - override suspend fun broadcast(message: MagixMessage): Unit = withContext(coroutineContext) { + override suspend fun broadcast(message: MagixMessage): Unit { val payload = buildPayload { data(MagixEndpoint.magixJson.encodeToString(MagixMessage.serializer(), message)) } @@ -49,11 +51,12 @@ public class RSocketMagixEndpoint(private val rSocket: RSocket) : MagixEndpoint, } -internal fun buildConnector(rSocketConfig: RSocketConnectorBuilder.ConnectionConfigBuilder.() -> Unit) = - RSocketConnector { - reconnectable(10) - connectionConfig(rSocketConfig) - } +internal fun buildConnector( + rSocketConfig: RSocketConnectorBuilder.ConnectionConfigBuilder.() -> Unit, +) = RSocketConnector { + reconnectable(5) + connectionConfig(rSocketConfig) +} /** * Build a websocket based endpoint connected to [host], [port] and given routing [path] diff --git a/magix/magix-rsocket/src/commonMain/kotlin/space/kscience/magix/rsocket/RSocketStreamMagixEndpoint.kt b/magix/magix-rsocket/src/commonMain/kotlin/space/kscience/magix/rsocket/RSocketStreamMagixEndpoint.kt index e4eae79..025036e 100644 --- a/magix/magix-rsocket/src/commonMain/kotlin/space/kscience/magix/rsocket/RSocketStreamMagixEndpoint.kt +++ b/magix/magix-rsocket/src/commonMain/kotlin/space/kscience/magix/rsocket/RSocketStreamMagixEndpoint.kt @@ -10,20 +10,16 @@ import io.rsocket.kotlin.ktor.client.rSocket import io.rsocket.kotlin.payload.Payload import io.rsocket.kotlin.payload.buildPayload import io.rsocket.kotlin.payload.data -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.map import space.kscience.magix.api.MagixEndpoint import space.kscience.magix.api.MagixMessage import space.kscience.magix.api.MagixMessageFilter import space.kscience.magix.api.filter -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.coroutineContext /** * RSocket endpoint based on an established channel. This way it works a lot faster than [RSocketMagixEndpoint] @@ -33,11 +29,10 @@ import kotlin.coroutines.coroutineContext */ public class RSocketStreamMagixEndpoint( private val rSocket: RSocket, - private val coroutineContext: CoroutineContext, public val streamFilter: MagixMessageFilter = MagixMessageFilter(), ) : MagixEndpoint, Closeable { - private val output: MutableSharedFlow = MutableSharedFlow() + private val output: Channel = Channel() private val input: Flow by lazy { rSocket.requestChannel( @@ -49,24 +44,22 @@ public class RSocketStreamMagixEndpoint( ) ) }, - output.map { message -> - buildPayload { - data(MagixEndpoint.magixJson.encodeToString(MagixMessage.serializer(), message)) - } - }.flowOn(coroutineContext[CoroutineDispatcher] ?: Dispatchers.Unconfined) + output.consumeAsFlow() ) } override fun subscribe( filter: MagixMessageFilter, - ): Flow { - return input.map { - MagixEndpoint.magixJson.decodeFromString(MagixMessage.serializer(), it.data.readText()) - }.filter(filter).flowOn(coroutineContext[CoroutineDispatcher] ?: Dispatchers.Unconfined) - } + ): Flow = input.map { + MagixEndpoint.magixJson.decodeFromString(MagixMessage.serializer(), it.data.readText()) + }.filter(filter) override suspend fun broadcast(message: MagixMessage): Unit { - output.emit(message) + output.send( + buildPayload { + data(MagixEndpoint.magixJson.encodeToString(MagixMessage.serializer(), message)) + } + ) } override fun close() { @@ -95,5 +88,5 @@ public suspend fun MagixEndpoint.Companion.rSocketStreamWithWebSockets( client.close() } - return RSocketStreamMagixEndpoint(rSocket, coroutineContext, filter) + return RSocketStreamMagixEndpoint(rSocket, filter) } \ No newline at end of file diff --git a/magix/magix-rsocket/src/jvmMain/kotlin/space/kscience/magix/rsocket/withTcp.kt b/magix/magix-rsocket/src/jvmMain/kotlin/space/kscience/magix/rsocket/withTcp.kt index 9dc0abd..02a6c9b 100644 --- a/magix/magix-rsocket/src/jvmMain/kotlin/space/kscience/magix/rsocket/withTcp.kt +++ b/magix/magix-rsocket/src/jvmMain/kotlin/space/kscience/magix/rsocket/withTcp.kt @@ -20,6 +20,7 @@ public suspend fun MagixEndpoint.Companion.rSocketWithTcp( val transport = TcpClientTransport( hostname = host, port = port, + context = coroutineContext, configure = tcpConfig ) val rSocket = buildConnector(rSocketConfig).connect(transport) @@ -38,9 +39,10 @@ public suspend fun MagixEndpoint.Companion.rSocketStreamWithTcp( val transport = TcpClientTransport( hostname = host, port = port, + context = coroutineContext, configure = tcpConfig ) val rSocket = buildConnector(rSocketConfig).connect(transport) - return RSocketStreamMagixEndpoint(rSocket, coroutineContext, filter) + return RSocketStreamMagixEndpoint(rSocket, filter) } \ No newline at end of file diff --git a/magix/magix-server/README.md b/magix/magix-server/README.md index f5a83e3..27d97e0 100644 --- a/magix/magix-server/README.md +++ b/magix/magix-server/README.md @@ -6,27 +6,18 @@ A magix event loop implementation in Kotlin. Includes HTTP/SSE and RSocket route ## Artifact: -The Maven coordinates of this project are `space.kscience:magix-server:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:magix-server:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:magix-server:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:magix-server:0.1.1-SNAPSHOT") + implementation("space.kscience:magix-server:0.2.0") } ``` diff --git a/magix/magix-server/api/magix-server.api b/magix/magix-server/api/magix-server.api new file mode 100644 index 0000000..6ed9184 --- /dev/null +++ b/magix/magix-server/api/magix-server.api @@ -0,0 +1,45 @@ +public final class space/kscience/magix/server/MagixModuleKt { + public static final fun magixModule (Lio/ktor/server/application/Application;Ljava/lang/String;I)V + public static final fun magixModule (Lio/ktor/server/application/Application;Lkotlinx/coroutines/flow/MutableSharedFlow;Ljava/lang/String;)V + public static synthetic fun magixModule$default (Lio/ktor/server/application/Application;Ljava/lang/String;IILjava/lang/Object;)V + public static synthetic fun magixModule$default (Lio/ktor/server/application/Application;Lkotlinx/coroutines/flow/MutableSharedFlow;Ljava/lang/String;ILjava/lang/Object;)V +} + +public final class space/kscience/magix/server/RSocketMagixFlowPlugin : space/kscience/magix/api/MagixFlowPlugin { + public static final field Companion Lspace/kscience/magix/server/RSocketMagixFlowPlugin$Companion; + public fun ()V + public fun (Ljava/lang/String;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun start (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; +} + +public final class space/kscience/magix/server/RSocketMagixFlowPlugin$Companion { + public final fun acceptor (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lio/rsocket/kotlin/ConnectionAcceptor; +} + +public final class space/kscience/magix/server/ServerKt { + public static final fun startMagixServer (Lkotlinx/coroutines/CoroutineScope;[Lspace/kscience/magix/api/MagixFlowPlugin;II)Lio/ktor/server/engine/ApplicationEngine; + public static synthetic fun startMagixServer$default (Lkotlinx/coroutines/CoroutineScope;[Lspace/kscience/magix/api/MagixFlowPlugin;IIILjava/lang/Object;)Lio/ktor/server/engine/ApplicationEngine; +} + +public final class space/kscience/magix/server/SseEvent { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lspace/kscience/magix/server/SseEvent; + public static synthetic fun copy$default (Lspace/kscience/magix/server/SseEvent;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lspace/kscience/magix/server/SseEvent; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Ljava/lang/String; + public final fun getEvent ()Ljava/lang/String; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class space/kscience/magix/server/SseKt { + public static final fun respondSse (Lio/ktor/server/application/ApplicationCall;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun writeSseFlow (Lio/ktor/utils/io/ByteWriteChannel;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + diff --git a/magix/magix-server/build.gradle.kts b/magix/magix-server/build.gradle.kts index 4a3b102..cb63049 100644 --- a/magix/magix-server/build.gradle.kts +++ b/magix/magix-server/build.gradle.kts @@ -1,3 +1,5 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.jvm") `maven-publish` @@ -28,4 +30,8 @@ dependencies{ api("io.rsocket.kotlin:rsocket-ktor-server:$rsocketVersion") api("io.rsocket.kotlin:rsocket-transport-ktor-tcp:$rsocketVersion") +} + +readme{ + maturity = Maturity.EXPERIMENTAL } \ No newline at end of file diff --git a/magix/magix-server/src/main/kotlin/space/kscience/magix/server/RSocketMagixFlowPlugin.kt b/magix/magix-server/src/main/kotlin/space/kscience/magix/server/RSocketMagixFlowPlugin.kt index 3116513..a00c33f 100644 --- a/magix/magix-server/src/main/kotlin/space/kscience/magix/server/RSocketMagixFlowPlugin.kt +++ b/magix/magix-server/src/main/kotlin/space/kscience/magix/server/RSocketMagixFlowPlugin.kt @@ -1,8 +1,10 @@ package space.kscience.magix.server +import io.ktor.network.sockets.SocketOptions import io.rsocket.kotlin.ConnectionAcceptor import io.rsocket.kotlin.RSocketRequestHandler import io.rsocket.kotlin.core.RSocketServer +import io.rsocket.kotlin.core.RSocketServerBuilder import io.rsocket.kotlin.payload.Payload import io.rsocket.kotlin.payload.buildPayload import io.rsocket.kotlin.payload.data @@ -10,18 +12,36 @@ import io.rsocket.kotlin.transport.ktor.tcp.TcpServer import io.rsocket.kotlin.transport.ktor.tcp.TcpServerTransport import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.serialization.encodeToString import space.kscience.magix.api.* import space.kscience.magix.api.MagixEndpoint.Companion.DEFAULT_MAGIX_RAW_PORT /** - * Raw TCP magix server + * Raw TCP magix server plugin */ -public class RSocketMagixFlowPlugin(public val port: Int = DEFAULT_MAGIX_RAW_PORT): MagixFlowPlugin { - override fun start(scope: CoroutineScope, magixFlow: MutableSharedFlow): Job { - val tcpTransport = TcpServerTransport(port = port) - val rSocketJob: TcpServer = RSocketServer().bindIn(scope, tcpTransport, acceptor(scope, magixFlow)) +public class RSocketMagixFlowPlugin( + private val serverHost: String = "0.0.0.0", + private val serverPort: Int = DEFAULT_MAGIX_RAW_PORT, + private val transportConfiguration: SocketOptions.AcceptorOptions.() -> Unit = {}, + private val rsocketConfiguration: RSocketServerBuilder.() -> Unit = {}, +) : MagixFlowPlugin { + + override fun start( + scope: CoroutineScope, + receive: Flow, + sendMessage: suspend (MagixMessage) -> Unit, + ): Job { + val tcpTransport = TcpServerTransport( + hostname = serverHost, + port = serverPort, + configure = transportConfiguration + ) + val rSocketJob: TcpServer = RSocketServer(rsocketConfiguration) + .bindIn(scope, tcpTransport, acceptor(scope, receive, sendMessage)) scope.coroutineContext[Job]?.invokeOnCompletion { rSocketJob.handlerJob.cancel() @@ -30,40 +50,54 @@ public class RSocketMagixFlowPlugin(public val port: Int = DEFAULT_MAGIX_RAW_POR return rSocketJob.handlerJob } - public companion object{ + public companion object { public fun acceptor( coroutineScope: CoroutineScope, - magixFlow: MutableSharedFlow, + receive: Flow, + sendMessage: suspend (MagixMessage) -> Unit, ): ConnectionAcceptor = ConnectionAcceptor { RSocketRequestHandler(coroutineScope.coroutineContext) { //handler for request/stream requestStream { request: Payload -> - val filter = MagixEndpoint.magixJson.decodeFromString(MagixMessageFilter.serializer(), request.data.readText()) - magixFlow.filter(filter).map { message -> + val filter = MagixEndpoint.magixJson.decodeFromString( + MagixMessageFilter.serializer(), + request.data.readText() + ) + + receive.filter(filter).map { message -> val string = MagixEndpoint.magixJson.encodeToString(MagixMessage.serializer(), message) buildPayload { data(string) } } } //single send fireAndForget { request: Payload -> - val message = MagixEndpoint.magixJson.decodeFromString(MagixMessage.serializer(), request.data.readText()) - magixFlow.emit(message) + val message = MagixEndpoint.magixJson.decodeFromString( + MagixMessage.serializer(), + request.data.readText() + ) + + sendMessage(message) } // bidirectional connection, used for streaming connection requestChannel { request: Payload, input: Flow -> - input.onEach { - magixFlow.emit(MagixEndpoint.magixJson.decodeFromString(MagixMessage.serializer(), it.data.readText())) + input.onEach { inputPayload -> + sendMessage( + MagixEndpoint.magixJson.decodeFromString( + MagixMessage.serializer(), + inputPayload.use { it.data.readText() } + ) + ) }.launchIn(this) - val filterText = request.data.readText() + val filterText = request.use { it.data.readText() } - val filter = if(filterText.isNotBlank()){ + val filter = if (filterText.isNotBlank()) { MagixEndpoint.magixJson.decodeFromString(MagixMessageFilter.serializer(), filterText) } else { MagixMessageFilter() } - magixFlow.filter(filter).map { message -> + receive.filter(filter).map { message -> val string = MagixEndpoint.magixJson.encodeToString(message) buildPayload { data(string) } } diff --git a/magix/magix-server/src/main/kotlin/space/kscience/magix/server/magixModule.kt b/magix/magix-server/src/main/kotlin/space/kscience/magix/server/magixModule.kt index e5cb6cb..dc197ad 100644 --- a/magix/magix-server/src/main/kotlin/space/kscience/magix/server/magixModule.kt +++ b/magix/magix-server/src/main/kotlin/space/kscience/magix/server/magixModule.kt @@ -47,24 +47,24 @@ public fun Application.magixModule(magixFlow: MutableSharedFlow, r install(WebSockets) } + if (pluginOrNull(RSocketSupport) == null) { + install(RSocketSupport) + } + + // if (pluginOrNull(CORS) == null) { // install(CORS) { // //TODO consider more safe policy // anyHost() // } // } - if (pluginOrNull(ContentNegotiation) == null) { - install(ContentNegotiation) { - json() - } - } - if (pluginOrNull(RSocketSupport) == null) { - install(RSocketSupport) - } routing { route(route) { + install(ContentNegotiation){ + json() + } get("state") { call.respondHtml { head { @@ -104,8 +104,11 @@ public fun Application.magixModule(magixFlow: MutableSharedFlow, r val message = call.receive() magixFlow.emit(message) } - //rSocket server. Filter from Payload - rSocket("rsocket", acceptor = RSocketMagixFlowPlugin.acceptor( application, magixFlow)) + //rSocket WS server. Filter from Payload + rSocket( + "rsocket", + acceptor = RSocketMagixFlowPlugin.acceptor(application, magixFlow) { magixFlow.emit(it) } + ) } } } diff --git a/magix/magix-storage/README.md b/magix/magix-storage/README.md index 7ebafae..fa367b4 100644 --- a/magix/magix-storage/README.md +++ b/magix/magix-storage/README.md @@ -1,4 +1,23 @@ # Module magix-storage +Magix history database API +## Usage +## Artifact: + +The Maven coordinates of this project are `space.kscience:magix-storage:0.2.0`. + +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") + mavenCentral() +} + +dependencies { + implementation("space.kscience:magix-storage:0.2.0") +} +``` diff --git a/magix/magix-storage/api/magix-storage.api b/magix/magix-storage/api/magix-storage.api new file mode 100644 index 0000000..4dfdc72 --- /dev/null +++ b/magix/magix-storage/api/magix-storage.api @@ -0,0 +1,183 @@ +public final class space/kscience/magix/storage/HistoryEndpointKt { + public static final fun launchHistory (Lspace/kscience/magix/api/MagixEndpoint;Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/magix/storage/MagixHistory;Ljava/lang/String;ILkotlinx/serialization/json/JsonElement;Ljava/lang/String;)Lkotlinx/coroutines/Job; + public static synthetic fun launchHistory$default (Lspace/kscience/magix/api/MagixEndpoint;Lkotlinx/coroutines/CoroutineScope;Lspace/kscience/magix/storage/MagixHistory;Ljava/lang/String;ILkotlinx/serialization/json/JsonElement;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/coroutines/Job; +} + +public final class space/kscience/magix/storage/HistoryRequestPayload : space/kscience/magix/storage/MagixHistoryPayload { + public static final field Companion Lspace/kscience/magix/storage/HistoryRequestPayload$Companion; + public synthetic fun (ILspace/kscience/magix/api/MagixMessageFilter;Lspace/kscience/magix/storage/MagixPayloadFilter;Ljava/lang/String;Ljava/lang/Integer;Lkotlinx/serialization/internal/SerializationConstructorMarker;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lspace/kscience/magix/api/MagixMessageFilter;Lspace/kscience/magix/storage/MagixPayloadFilter;Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lspace/kscience/magix/api/MagixMessageFilter;Lspace/kscience/magix/storage/MagixPayloadFilter;Ljava/lang/String;Ljava/lang/Integer;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lspace/kscience/magix/api/MagixMessageFilter; + public final fun component2 ()Lspace/kscience/magix/storage/MagixPayloadFilter; + public final fun component3-G2i_Sr0 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Integer; + public final fun copy-waRmP5I (Lspace/kscience/magix/api/MagixMessageFilter;Lspace/kscience/magix/storage/MagixPayloadFilter;Ljava/lang/String;Ljava/lang/Integer;)Lspace/kscience/magix/storage/HistoryRequestPayload; + public static synthetic fun copy-waRmP5I$default (Lspace/kscience/magix/storage/HistoryRequestPayload;Lspace/kscience/magix/api/MagixMessageFilter;Lspace/kscience/magix/storage/MagixPayloadFilter;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lspace/kscience/magix/storage/HistoryRequestPayload; + public fun equals (Ljava/lang/Object;)Z + public final fun getMagixFilter ()Lspace/kscience/magix/api/MagixMessageFilter; + public final fun getPageSize ()Ljava/lang/Integer; + public final fun getPayloadFilter ()Lspace/kscience/magix/storage/MagixPayloadFilter; + public final fun getUserFilter-G2i_Sr0 ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/magix/storage/HistoryRequestPayload;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/storage/HistoryRequestPayload$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/magix/storage/HistoryRequestPayload$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/magix/storage/HistoryRequestPayload; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/magix/storage/HistoryRequestPayload;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/storage/HistoryRequestPayload$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/storage/HistoryResponsePayload : space/kscience/magix/storage/MagixHistoryPayload { + public static final field Companion Lspace/kscience/magix/storage/HistoryResponsePayload$Companion; + public synthetic fun (ILjava/util/List;IZLkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ljava/util/List;IZ)V + public synthetic fun (Ljava/util/List;IZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()I + public final fun component3 ()Z + public final fun copy (Ljava/util/List;IZ)Lspace/kscience/magix/storage/HistoryResponsePayload; + public static synthetic fun copy$default (Lspace/kscience/magix/storage/HistoryResponsePayload;Ljava/util/List;IZILjava/lang/Object;)Lspace/kscience/magix/storage/HistoryResponsePayload; + public fun equals (Ljava/lang/Object;)Z + public final fun getLastPage ()Z + public final fun getMessages ()Ljava/util/List; + public final fun getPage ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Lspace/kscience/magix/storage/HistoryResponsePayload;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/storage/HistoryResponsePayload$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/magix/storage/HistoryResponsePayload$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lspace/kscience/magix/storage/HistoryResponsePayload; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lspace/kscience/magix/storage/HistoryResponsePayload;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/storage/HistoryResponsePayload$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class space/kscience/magix/storage/MagixHistory { + public static final field Companion Lspace/kscience/magix/storage/MagixHistory$Companion; + public static final field HISTORY_PAYLOAD_FORMAT Ljava/lang/String; + public abstract fun useMessages-2Yl6TEQ (Lspace/kscience/magix/api/MagixMessageFilter;Lspace/kscience/magix/storage/MagixPayloadFilter;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun useMessages-2Yl6TEQ$default (Lspace/kscience/magix/storage/MagixHistory;Lspace/kscience/magix/api/MagixMessageFilter;Lspace/kscience/magix/storage/MagixPayloadFilter;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class space/kscience/magix/storage/MagixHistory$Companion { + public static final field HISTORY_PAYLOAD_FORMAT Ljava/lang/String; + public final fun getMagixFormat ()Lspace/kscience/magix/api/MagixFormat; +} + +public final class space/kscience/magix/storage/MagixHistoryKt { + public static final fun filter (Lkotlin/sequences/Sequence;Lspace/kscience/magix/storage/MagixPayloadFilter;)Lkotlin/sequences/Sequence; + public static final fun test (Lspace/kscience/magix/storage/MagixPayloadFilter;Lkotlinx/serialization/json/JsonElement;)Z +} + +public abstract class space/kscience/magix/storage/MagixHistoryPayload { + public static final field Companion Lspace/kscience/magix/storage/MagixHistoryPayload$Companion; + public synthetic fun (ILkotlinx/serialization/internal/SerializationConstructorMarker;)V + public static final synthetic fun write$Self (Lspace/kscience/magix/storage/MagixHistoryPayload;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/storage/MagixHistoryPayload$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract class space/kscience/magix/storage/MagixPayloadFilter { + public static final field Companion Lspace/kscience/magix/storage/MagixPayloadFilter$Companion; + public synthetic fun (ILkotlinx/serialization/internal/SerializationConstructorMarker;)V + public static final synthetic fun write$Self (Lspace/kscience/magix/storage/MagixPayloadFilter;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class space/kscience/magix/storage/MagixPayloadFilter$And : space/kscience/magix/storage/MagixPayloadFilter { + public fun (Lspace/kscience/magix/storage/MagixPayloadFilter;Lspace/kscience/magix/storage/MagixPayloadFilter;)V + public final fun getLeft ()Lspace/kscience/magix/storage/MagixPayloadFilter; + public final fun getRight ()Lspace/kscience/magix/storage/MagixPayloadFilter; +} + +public final class space/kscience/magix/storage/MagixPayloadFilter$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/storage/MagixPayloadFilter$DateTimeInRange : space/kscience/magix/storage/MagixPayloadFilter { + public fun (Ljava/lang/String;Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/LocalDateTime;)V + public final fun getFrom ()Lkotlinx/datetime/LocalDateTime; + public final fun getPath ()Ljava/lang/String; + public final fun getTo ()Lkotlinx/datetime/LocalDateTime; +} + +public final class space/kscience/magix/storage/MagixPayloadFilter$Equals : space/kscience/magix/storage/MagixPayloadFilter { + public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public final fun getPath ()Ljava/lang/String; + public final fun getValue ()Lkotlinx/serialization/json/JsonElement; +} + +public final class space/kscience/magix/storage/MagixPayloadFilter$Not : space/kscience/magix/storage/MagixPayloadFilter { + public fun (Lspace/kscience/magix/storage/MagixPayloadFilter;)V + public final fun getArgument ()Lspace/kscience/magix/storage/MagixPayloadFilter; +} + +public final class space/kscience/magix/storage/MagixPayloadFilter$NumberInRange : space/kscience/magix/storage/MagixPayloadFilter { + public fun (Ljava/lang/String;Ljava/lang/Number;Ljava/lang/Number;)V + public final fun getFrom ()Ljava/lang/Number; + public final fun getPath ()Ljava/lang/String; + public final fun getTo ()Ljava/lang/Number; +} + +public final class space/kscience/magix/storage/MagixPayloadFilter$Or : space/kscience/magix/storage/MagixPayloadFilter { + public fun (Lspace/kscience/magix/storage/MagixPayloadFilter;Lspace/kscience/magix/storage/MagixPayloadFilter;)V + public final fun getLeft ()Lspace/kscience/magix/storage/MagixPayloadFilter; + public final fun getRight ()Lspace/kscience/magix/storage/MagixPayloadFilter; +} + +public final class space/kscience/magix/storage/MagixUsernameFilter { + public static final field Companion Lspace/kscience/magix/storage/MagixUsernameFilter$Companion; + public static final synthetic fun box-impl (Ljava/lang/String;)Lspace/kscience/magix/storage/MagixUsernameFilter; + public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public final fun getUserName ()Ljava/lang/String; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public final class space/kscience/magix/storage/MagixUsernameFilter$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lspace/kscience/magix/storage/MagixUsernameFilter$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize-OjVmumU (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/String; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize-dWVLUXE (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class space/kscience/magix/storage/MagixUsernameFilter$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class space/kscience/magix/storage/WriteableMagixHistory : space/kscience/magix/storage/MagixHistory { + public abstract fun send (Lspace/kscience/magix/api/MagixMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + diff --git a/controls-magix-client/build.gradle.kts b/magix/magix-storage/build.gradle.kts similarity index 56% rename from controls-magix-client/build.gradle.kts rename to magix/magix-storage/build.gradle.kts index cbdefcc..cb0bc24 100644 --- a/controls-magix-client/build.gradle.kts +++ b/magix/magix-storage/build.gradle.kts @@ -1,25 +1,29 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.mpp") `maven-publish` } description = """ - Magix service for binding controls devices (both as RPC client and server + Magix history database API """.trimIndent() + kscience { jvm() js() + native() useSerialization { json() } + useCoroutines() dependencies { api(projects.magix.magixApi) - api(projects.controlsCore) - api("com.benasher44:uuid:0.7.0") + api(spclibs.kotlinx.datetime) } } -readme { - -} \ No newline at end of file +readme{ + maturity = Maturity.PROTOTYPE +} diff --git a/magix/magix-storage/magix-storage-xodus/README.md b/magix/magix-storage/magix-storage-xodus/README.md index 57107c5..b3d871b 100644 --- a/magix/magix-storage/magix-storage-xodus/README.md +++ b/magix/magix-storage/magix-storage-xodus/README.md @@ -6,27 +6,18 @@ ## Artifact: -The Maven coordinates of this project are `space.kscience:magix-storage-xodus:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:magix-storage-xodus:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:magix-storage-xodus:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:magix-storage-xodus:0.1.1-SNAPSHOT") + implementation("space.kscience:magix-storage-xodus:0.2.0") } ``` diff --git a/magix/magix-storage/magix-storage-xodus/build.gradle.kts b/magix/magix-storage/magix-storage-xodus/build.gradle.kts index 8869867..ed1dc32 100644 --- a/magix/magix-storage/magix-storage-xodus/build.gradle.kts +++ b/magix/magix-storage/magix-storage-xodus/build.gradle.kts @@ -5,17 +5,18 @@ plugins { val xodusVersion: String by rootProject.extra -kscience{ +kscience { useCoroutines() } dependencies { - api(projects.magix.magixApi) + api(projects.magix.magixStorage) implementation("org.jetbrains.xodus:xodus-entity-store:$xodusVersion") +// implementation("org.jetbrains.xodus:dnq:2.0.0") testImplementation(spclibs.kotlinx.coroutines.test) } -readme{ +readme { maturity = space.kscience.gradle.Maturity.PROTOTYPE } diff --git a/magix/magix-storage/magix-storage-xodus/src/main/kotlin/space/kscience/magix/storage/xodus/XodusMagixStorage.kt b/magix/magix-storage/magix-storage-xodus/src/main/kotlin/space/kscience/magix/storage/xodus/XodusMagixStorage.kt index 39eb4ea..4a3efa3 100644 --- a/magix/magix-storage/magix-storage-xodus/src/main/kotlin/space/kscience/magix/storage/xodus/XodusMagixStorage.kt +++ b/magix/magix-storage/magix-storage-xodus/src/main/kotlin/space/kscience/magix/storage/xodus/XodusMagixStorage.kt @@ -1,8 +1,6 @@ package space.kscience.magix.storage.xodus -import jetbrains.exodus.entitystore.Entity -import jetbrains.exodus.entitystore.PersistentEntityStore -import jetbrains.exodus.entitystore.PersistentEntityStores +import jetbrains.exodus.entitystore.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -12,54 +10,152 @@ import space.kscience.magix.api.MagixEndpoint import space.kscience.magix.api.MagixEndpoint.Companion.magixJson import space.kscience.magix.api.MagixMessage import space.kscience.magix.api.MagixMessageFilter +import space.kscience.magix.api.userName +import space.kscience.magix.storage.* import java.nio.file.Path +import kotlin.sequences.Sequence + +private fun Entity.parseMagixMessage(): MagixMessage = MagixMessage( + format = getProperty(MagixMessage::format.name).toString(), + payload = getBlobString(MagixMessage::payload.name)?.let { + magixJson.parseToJsonElement(it) + } ?: JsonObject(emptyMap()), + sourceEndpoint = getProperty(MagixMessage::sourceEndpoint.name).toString(), + targetEndpoint = getProperty(MagixMessage::targetEndpoint.name)?.toString(), + id = getProperty(MagixMessage::id.name)?.toString(), + parentId = getProperty(MagixMessage::parentId.name)?.toString(), + user = getBlobString(MagixMessage::user.name)?.let { + magixJson.parseToJsonElement(it) + }, +) + +public class XodusMagixHistory(private val store: PersistentEntityStore) : WriteableMagixHistory { + + public fun writeMessage(storeTransaction: StoreTransaction, message: MagixMessage) { + storeTransaction.newEntity(XodusMagixStorage.MAGIC_MESSAGE_ENTITY_TYPE).apply { + setProperty(MagixMessage::sourceEndpoint.name, message.sourceEndpoint) + setProperty(MagixMessage::format.name, message.format) + + setBlobString(MagixMessage::payload.name, magixJson.encodeToString(message.payload)) + + message.targetEndpoint?.let { + setProperty(MagixMessage::targetEndpoint.name, it) + } + message.id?.let { + setProperty(MagixMessage::id.name, it) + } + message.parentId?.let { + setProperty(MagixMessage::parentId.name, it) + } + message.userName?.let { + setProperty(MagixMessage::user.name, it) + } + } + } + + override suspend fun send(message: MagixMessage) { + store.executeInTransaction { transaction -> + writeMessage(transaction, message) + } + } + + override suspend fun useMessages( + magixFilter: MagixMessageFilter?, + payloadFilter: MagixPayloadFilter?, + userFilter: MagixUsernameFilter?, + callback: (Sequence) -> Unit, + ): Unit = store.executeInReadonlyTransaction { transaction -> + val all = transaction.getAll(XodusMagixStorage.MAGIC_MESSAGE_ENTITY_TYPE) + + fun StoreTransaction.findAllIn( + entityType: String, + field: String, + values: Collection?, + ): EntityIterable? { + var union: EntityIterable? = null + values?.forEach { + val filter = transaction.find(entityType, field, it) + union = union?.union(filter) ?: filter + } + return union + } + + // filter by magix filter + val filteredByMagix: EntityIterable = magixFilter?.let { mf -> + var res = all + transaction.findAllIn(XodusMagixStorage.MAGIC_MESSAGE_ENTITY_TYPE, MagixMessage::format.name, mf.format) + ?.let { + res = res.intersect(it) + } + transaction.findAllIn( + XodusMagixStorage.MAGIC_MESSAGE_ENTITY_TYPE, + MagixMessage::sourceEndpoint.name, + mf.source + )?.let { + res = res.intersect(it) + } + transaction.findAllIn( + XodusMagixStorage.MAGIC_MESSAGE_ENTITY_TYPE, + MagixMessage::targetEndpoint.name, + mf.target + )?.let { + res = res.intersect(it) + } + + res + } ?: all + + val filteredByUser: EntityIterable = userFilter?.let { userFilter -> + filteredByMagix.intersect( + transaction.find( + XodusMagixStorage.MAGIC_MESSAGE_ENTITY_TYPE, + MagixMessage::user.name, + userFilter.userName + ) + ) + } ?: filteredByMagix + + + val sequence = filteredByUser.asSequence().map { it.parseMagixMessage() } + + val filteredSequence = if (payloadFilter == null) { + sequence + } else { + sequence.filter { + payloadFilter.test(it.payload) + } + } + + callback(filteredSequence) + } +} + + +/** + * Attach a Xodus storage process to the given endpoint. + */ public class XodusMagixStorage( scope: CoroutineScope, private val store: PersistentEntityStore, endpoint: MagixEndpoint, - filter: MagixMessageFilter = MagixMessageFilter(), + endpointName: String? = null, + subscriptionFilter: MagixMessageFilter = MagixMessageFilter.ALL, ) : AutoCloseable { + public val history: XodusMagixHistory = XodusMagixHistory(store) + //TODO consider message buffering - internal val subscriptionJob = endpoint.subscribe(filter).onEach { message -> - store.executeInTransaction { transaction -> - transaction.newEntity(MAGIC_MESSAGE_ENTITY_TYPE).apply { - setProperty(MagixMessage::origin.name, message.origin) - setProperty(MagixMessage::format.name, message.format) - - setBlobString(MagixMessage::payload.name, MagixEndpoint.magixJson.encodeToString(message.payload)) - - message.target?.let { - setProperty(MagixMessage::target.name, it) - } - message.id?.let { - setProperty(MagixMessage::id.name, it) - } - message.parentId?.let { - setProperty(MagixMessage::parentId.name, it) - } - message.user?.let { - setBlobString(MagixMessage::user.name, MagixEndpoint.magixJson.encodeToString(it)) - } - } - } + private val subscriptionJob = endpoint.subscribe(subscriptionFilter).onEach { message -> + history.send(message) }.launchIn(scope) - private fun Entity.parseMagixMessage(): MagixMessage = MagixMessage( - format = getProperty(MagixMessage::format.name).toString(), - payload = getBlobString(MagixMessage::payload.name)?.let { - magixJson.parseToJsonElement(it) - } ?: JsonObject(emptyMap()), - origin = getProperty(MagixMessage::origin.name).toString(), - target = getProperty(MagixMessage::target.name)?.toString(), - id = getProperty(MagixMessage::id.name)?.toString(), - parentId = getProperty(MagixMessage::parentId.name)?.toString(), - user = getBlobString(MagixMessage::user.name)?.let { - magixJson.parseToJsonElement(it) - }, - ) + private val broadcastJob = endpoint.launchHistory(scope, history, endpointName = endpointName) + + /** + * Access all messages in a given format + */ public fun readByFormat( format: String, block: (Sequence) -> Unit, @@ -74,6 +170,9 @@ public class XodusMagixStorage( block(sequence) } + /** + * Access all messages as + */ public fun readAll( block: (Sequence) -> Unit, ): Unit = store.executeInReadonlyTransaction { transaction -> @@ -98,14 +197,16 @@ public class XodusMagixStorage( public fun MagixEndpoint.storeInXodus( scope: CoroutineScope, xodusStore: PersistentEntityStore, + endpointName: String? = null, filter: MagixMessageFilter = MagixMessageFilter(), -): XodusMagixStorage = XodusMagixStorage(scope, xodusStore, this, filter) +): XodusMagixStorage = XodusMagixStorage(scope, xodusStore, this, endpointName, filter) public fun MagixEndpoint.storeInXodus( scope: CoroutineScope, path: Path, + endpointName: String? = null, filter: MagixMessageFilter = MagixMessageFilter(), ): XodusMagixStorage { val store = PersistentEntityStores.newInstance(path.toFile()) - return XodusMagixStorage(scope, store, this, filter) + return XodusMagixStorage(scope, store, this, endpointName, filter) } \ No newline at end of file diff --git a/magix/magix-storage/magix-storage-xodus/src/main/kotlin/space/kscience/magix/storage/xodus/storageAPITest.kt b/magix/magix-storage/magix-storage-xodus/src/main/kotlin/space/kscience/magix/storage/xodus/storageAPITest.kt new file mode 100644 index 0000000..dc1d42b --- /dev/null +++ b/magix/magix-storage/magix-storage-xodus/src/main/kotlin/space/kscience/magix/storage/xodus/storageAPITest.kt @@ -0,0 +1,50 @@ +package space.kscience.magix.storage.xodus + +import jetbrains.exodus.entitystore.PersistentEntityStores +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import space.kscience.magix.api.MagixMessage +import space.kscience.magix.api.MagixMessageFilter +import java.nio.file.Files +import kotlin.time.measureTime + +public suspend fun main() { + val storeDirectory = Files.createTempDirectory("controls-xodus").toFile() + println(storeDirectory) + val store = PersistentEntityStores.newInstance(storeDirectory) + val history = XodusMagixHistory(store) + + store.executeInTransaction { transaction -> + for (value in 1..100) { + for (source in 1..100) { + for (target in 1..100) { + history.writeMessage( + transaction, + MagixMessage( + "test", + sourceEndpoint = "source$source", + targetEndpoint = "target$target", + payload = buildJsonObject { + put("value", JsonPrimitive(value)) + } + ) + ) + } + } + } + } + + println("written million messages") + + + val time = measureTime { + history.useMessages( + MagixMessageFilter(source = listOf("source12"), target = listOf("target12")) + ) { sequence -> + println(sequence.count()) + } + } + println("Finished query in $time") + + store.close() +} \ No newline at end of file diff --git a/magix/magix-storage/src/commonMain/kotlin/space/kscience/magix/storage/MagixHistory.kt b/magix/magix-storage/src/commonMain/kotlin/space/kscience/magix/storage/MagixHistory.kt new file mode 100644 index 0000000..5501dc5 --- /dev/null +++ b/magix/magix-storage/src/commonMain/kotlin/space/kscience/magix/storage/MagixHistory.kt @@ -0,0 +1,120 @@ +package space.kscience.magix.storage + +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import space.kscience.magix.api.MagixFormat +import space.kscience.magix.api.MagixMessage +import space.kscience.magix.api.MagixMessageFilter +import kotlin.jvm.JvmInline + +@Serializable +public sealed class MagixPayloadFilter { + @SerialName("eq") + public class Equals(public val path: String, public val value: JsonElement) : MagixPayloadFilter() + +// @SerialName("like") +// public class Like(public val path: String, public val value: String) : MagixPayloadFilter() + + @SerialName("numberInRange") + public class NumberInRange(public val path: String, public val from: Number, public val to: Number) : + MagixPayloadFilter() + + @SerialName("dateTimeInRange") + public class DateTimeInRange( + public val path: String, + public val from: LocalDateTime, + public val to: LocalDateTime, + ) : MagixPayloadFilter() + + + @SerialName("not") + public class Not(public val argument: MagixPayloadFilter) : MagixPayloadFilter() + + @SerialName("and") + public class And(public val left: MagixPayloadFilter, public val right: MagixPayloadFilter) : MagixPayloadFilter() + + @SerialName("or") + public class Or(public val left: MagixPayloadFilter, public val right: MagixPayloadFilter) : MagixPayloadFilter() +} + +private fun JsonElement.takeElement(path: String): JsonElement? = if (path.isEmpty()) { + this +} else { + val separatorIndex = path.indexOf(".") + if (separatorIndex == -1) { + jsonObject[path] + } else { + val firstSegment = path.substring(0, separatorIndex) + val remaining = path.substring(separatorIndex + 1, path.length) + jsonObject[firstSegment]?.takeElement(remaining) + } + +} + +public fun MagixPayloadFilter.test(element: JsonElement): Boolean = when (this) { + is MagixPayloadFilter.Equals -> element.takeElement(path) == value + is MagixPayloadFilter.DateTimeInRange -> { + element.takeElement(path)?.jsonPrimitive?.content?.let { + LocalDateTime.parse(it) in from..to + } ?: false + } + is MagixPayloadFilter.NumberInRange -> { + element.takeElement(path)?.jsonPrimitive?.doubleOrNull?.let { + it in (from.toDouble()..to.toDouble()) + } ?: false + } + + is MagixPayloadFilter.Not -> !argument.test(element) + is MagixPayloadFilter.And -> left.test(element) && right.test(element) + is MagixPayloadFilter.Or -> left.test(element) || right.test(element) +} + +public fun Sequence.filter(magixPayloadFilter: MagixPayloadFilter): Sequence = filter { + magixPayloadFilter.test(it) +} + + +@Serializable +@JvmInline +public value class MagixUsernameFilter(public val userName: String) + +/** + * An interface for history access to magix messages + */ +public interface MagixHistory { + /** + * Find messages using intersection of given filters. If filters are not defined, get all messages. + * + * The result is supplied as a callback with [Sequence] of messages. If backing storage uses transactions, the function + * closes all transactions after use. + * + * @param magixFilter magix header filter. + * @param payloadFilter filter for payload fields. + * @param userFilter filters user names ("user.name"). + */ + public suspend fun useMessages( + magixFilter: MagixMessageFilter? = null, + payloadFilter: MagixPayloadFilter? = null, + userFilter: MagixUsernameFilter? = null, + callback: (Sequence) -> Unit, + ) + + public companion object { + public const val HISTORY_PAYLOAD_FORMAT: String = "magix.history" + + public val magixFormat: MagixFormat = MagixFormat( + MagixHistoryPayload.serializer(), + setOf(HISTORY_PAYLOAD_FORMAT) + ) + } +} + +public interface WriteableMagixHistory: MagixHistory{ + public suspend fun send(message: MagixMessage) +} + diff --git a/magix/magix-storage/src/commonMain/kotlin/space/kscience/magix/storage/MagixHistoryPayload.kt b/magix/magix-storage/src/commonMain/kotlin/space/kscience/magix/storage/MagixHistoryPayload.kt new file mode 100644 index 0000000..5438fa4 --- /dev/null +++ b/magix/magix-storage/src/commonMain/kotlin/space/kscience/magix/storage/MagixHistoryPayload.kt @@ -0,0 +1,44 @@ +package space.kscience.magix.storage + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import space.kscience.magix.api.MagixMessage +import space.kscience.magix.api.MagixMessageFilter + +/** + * Base class for history API request and response messages + */ +@Serializable +public sealed class MagixHistoryPayload + +/** + * Message to request history information from the storage + * + * @param magixFilter filter for magix headers + * @param payloadFilters filter for payload fields + * @param userFilter filter for user name + * @param pageSize if defined, defines the maximum number of messages per response message. If not defined, uses history provider default. + */ +@Serializable +@SerialName("history.request") +public data class HistoryRequestPayload( + val magixFilter: MagixMessageFilter? = null, + val payloadFilter: MagixPayloadFilter? = null, + val userFilter: MagixUsernameFilter? = null, + val pageSize: Int? = null +) : MagixHistoryPayload() + +/** + * A response to a [HistoryRequestPayload]. Contains a list of messages. + * + * @param messages the list of messages. + * @param page the index of current page for multiple page messages. Page indexing starts with 0. + * @param lastPage true if this page is the last. + */ +@Serializable +@SerialName("history.response") +public data class HistoryResponsePayload( + val messages: List, + val page: Int = 0, + val lastPage: Boolean = true +) : MagixHistoryPayload() \ No newline at end of file diff --git a/magix/magix-storage/src/commonMain/kotlin/space/kscience/magix/storage/historyEndpoint.kt b/magix/magix-storage/src/commonMain/kotlin/space/kscience/magix/storage/historyEndpoint.kt new file mode 100644 index 0000000..466fcb8 --- /dev/null +++ b/magix/magix-storage/src/commonMain/kotlin/space/kscience/magix/storage/historyEndpoint.kt @@ -0,0 +1,87 @@ +package space.kscience.magix.storage + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import space.kscience.magix.api.MagixEndpoint +import space.kscience.magix.api.MagixMessage +import space.kscience.magix.api.send +import space.kscience.magix.api.subscribe + +internal fun generateId(request: MagixMessage): String = if (request.id != null) { + "${request.id}.response" +} else { + "history[${request.payload.hashCode().toString(16)}" +} + +/** + * Launch responding history messages on this [MagixEndpoint]. The process does not store messages, only responds to history requests. + * + * @param scope the [CoroutineScope] in which the responding process runs. + * @param history the history database. + * @param endpointName the name of this endpoint that is used as a filter. + * @param pageSize maximum messages per page in the response. The default is 100. + * @param user user block for outgoing messages if defined. + * @param origin tag for outgoing messages if defined. + */ +public fun MagixEndpoint.launchHistory( + scope: CoroutineScope, + history: MagixHistory, + endpointName: String? = null, + pageSize: Int = 100, + user: JsonElement? = endpointName?.let { JsonPrimitive(endpointName) }, + origin: String = MagixHistory.HISTORY_PAYLOAD_FORMAT, +): Job = subscribe( + MagixHistory.magixFormat, + targetFilter = endpointName?.let { setOf(it) } +).onEach { (request, payload) -> + + fun send(chunk: List, pageNumber: Int, end: Boolean) { + scope.launch { + val sendPayload = HistoryResponsePayload( + chunk, + pageNumber, + lastPage = end + ) + send( + format = MagixHistory.magixFormat, + payload = sendPayload, + source = origin, + target = request.sourceEndpoint, + id = generateId(request), + parentId = request.id, + user = user, + ) + } + } + + + if (payload is HistoryRequestPayload) { + val realPageSize = payload.pageSize ?: pageSize + history.useMessages(payload.magixFilter, payload.payloadFilter, payload.userFilter) { sequence -> + // start from -1 because increment always happens first + var pageNumber = -1 + + //remember the last chunk to determine which is last + var chunk: List? = null + + sequence.chunked(realPageSize).forEach { + //If the last chunk was not final, send it + chunk?.let { chunk -> + send(chunk, pageNumber, false) + } + pageNumber++ + // update last chunk + chunk = it + } + // send the final chunk + chunk?.let { + send(it, pageNumber, true) + } + } + } +}.launchIn(scope) \ No newline at end of file diff --git a/magix/magix-storage/src/commonTest/kotlin/InMemoryMagixHistory.kt b/magix/magix-storage/src/commonTest/kotlin/InMemoryMagixHistory.kt new file mode 100644 index 0000000..432c6d8 --- /dev/null +++ b/magix/magix-storage/src/commonTest/kotlin/InMemoryMagixHistory.kt @@ -0,0 +1,32 @@ +package space.kscience.magix.storage + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import space.kscience.magix.api.MagixMessage +import space.kscience.magix.api.MagixMessageFilter + +class InMemoryMagixHistory() : WriteableMagixHistory { + + private val cache = mutableListOf() + private val mutex = Mutex() + + override suspend fun send(message: MagixMessage) { + mutex.withLock { + cache.add(message) + } + } + + override suspend fun useMessages( + magixFilter: MagixMessageFilter?, + payloadFilter: MagixPayloadFilter?, + userFilter: MagixUsernameFilter?, + callback: (Sequence) -> Unit, + ) = mutex.withLock { + val sequence = cache.asSequence().filter { message -> + (magixFilter?.accepts(message) ?: true) && + (userFilter?.userName?.equals(message.user) ?: true) && + payloadFilter?.test(message.payload) ?: true + } + callback(sequence) + } +} \ No newline at end of file diff --git a/magix/magix-zmq/README.md b/magix/magix-zmq/README.md index cf150fe..1338cce 100644 --- a/magix/magix-zmq/README.md +++ b/magix/magix-zmq/README.md @@ -6,27 +6,18 @@ ZMQ client endpoint for Magix ## Artifact: -The Maven coordinates of this project are `space.kscience:magix-zmq:0.1.1-SNAPSHOT`. +The Maven coordinates of this project are `space.kscience:magix-zmq:0.2.0`. -**Gradle Groovy:** -```groovy -repositories { - maven { url 'https://repo.kotlin.link' } - mavenCentral() -} - -dependencies { - implementation 'space.kscience:magix-zmq:0.1.1-SNAPSHOT' -} -``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") + //uncomment to access development builds + //maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") mavenCentral() } dependencies { - implementation("space.kscience:magix-zmq:0.1.1-SNAPSHOT") + implementation("space.kscience:magix-zmq:0.2.0") } ``` diff --git a/magix/magix-zmq/api/magix-zmq.api b/magix/magix-zmq/api/magix-zmq.api new file mode 100644 index 0000000..428d464 --- /dev/null +++ b/magix/magix-zmq/api/magix-zmq.api @@ -0,0 +1,23 @@ +public final class space/kscince/magix/zmq/ZmqMagixEndpoint : java/lang/AutoCloseable, space/kscience/magix/api/MagixEndpoint { + public fun (Ljava/lang/String;Ljava/lang/String;IILkotlin/coroutines/CoroutineContext;Lorg/zeromq/ZContext;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;IILkotlin/coroutines/CoroutineContext;Lorg/zeromq/ZContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun broadcast (Lspace/kscience/magix/api/MagixMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun close ()V + public fun subscribe (Lspace/kscience/magix/api/MagixMessageFilter;)Lkotlinx/coroutines/flow/Flow; +} + +public final class space/kscince/magix/zmq/ZmqMagixEndpointKt { + public static final fun zmq (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;Ljava/lang/String;IILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun zmq$default (Lspace/kscience/magix/api/MagixEndpoint$Companion;Ljava/lang/String;Ljava/lang/String;IILkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class space/kscince/magix/zmq/ZmqMagixFlowPlugin : space/kscience/magix/api/MagixFlowPlugin { + public fun ()V + public fun (Ljava/lang/String;IILorg/zeromq/ZContext;)V + public synthetic fun (Ljava/lang/String;IILorg/zeromq/ZContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getLocalHost ()Ljava/lang/String; + public final fun getZmqPubSocketPort ()I + public final fun getZmqPullSocketPort ()I + public fun start (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; +} + diff --git a/magix/magix-zmq/build.gradle.kts b/magix/magix-zmq/build.gradle.kts index e7c9f77..cf4ee9b 100644 --- a/magix/magix-zmq/build.gradle.kts +++ b/magix/magix-zmq/build.gradle.kts @@ -1,3 +1,5 @@ +import space.kscience.gradle.Maturity + plugins { id("space.kscience.gradle.jvm") `maven-publish` @@ -10,5 +12,9 @@ description = """ dependencies { api(projects.magix.magixApi) api("org.slf4j:slf4j-api:2.0.6") - implementation("org.zeromq:jeromq:0.5.2") + api("org.zeromq:jeromq:0.5.2") } + +readme { + maturity = Maturity.EXPERIMENTAL +} \ No newline at end of file diff --git a/magix/magix-zmq/src/main/kotlin/space/kscince/magix/zmq/ZmqMagixEndpoint.kt b/magix/magix-zmq/src/main/kotlin/space/kscince/magix/zmq/ZmqMagixEndpoint.kt index e878ce9..7a8aeb7 100644 --- a/magix/magix-zmq/src/main/kotlin/space/kscince/magix/zmq/ZmqMagixEndpoint.kt +++ b/magix/magix-zmq/src/main/kotlin/space/kscince/magix/zmq/ZmqMagixEndpoint.kt @@ -13,6 +13,7 @@ import space.kscience.magix.api.MagixMessage import space.kscience.magix.api.MagixMessageFilter import space.kscience.magix.api.filter import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.coroutineContext public class ZmqMagixEndpoint( private val host: String, @@ -20,10 +21,9 @@ public class ZmqMagixEndpoint( private val pubPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PUB_PORT, private val pullPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PULL_PORT, private val coroutineContext: CoroutineContext = Dispatchers.IO, + private val zmqContext: ZContext = ZContext() ) : MagixEndpoint, AutoCloseable { - private val zmqContext by lazy { ZContext() } - @OptIn(ExperimentalCoroutinesApi::class) override fun subscribe(filter: MagixMessageFilter): Flow { val socket = zmqContext.createSocket(SocketType.SUB) socket.connect("$protocol://$host:$pubPort") @@ -70,8 +70,7 @@ public class ZmqMagixEndpoint( } } -public fun MagixEndpoint.Companion.zmq( - scope: CoroutineScope, +public suspend fun MagixEndpoint.Companion.zmq( host: String, protocol: String = "tcp", pubPort: Int = DEFAULT_MAGIX_ZMQ_PUB_PORT, @@ -81,5 +80,5 @@ public fun MagixEndpoint.Companion.zmq( protocol, pubPort, pullPort, - coroutineContext = scope.coroutineContext + coroutineContext = coroutineContext ) \ No newline at end of file diff --git a/magix/magix-zmq/src/main/kotlin/space/kscince/magix/zmq/ZmqMagixFlowPlugin.kt b/magix/magix-zmq/src/main/kotlin/space/kscince/magix/zmq/ZmqMagixFlowPlugin.kt index e66d326..7813b8c 100644 --- a/magix/magix-zmq/src/main/kotlin/space/kscince/magix/zmq/ZmqMagixFlowPlugin.kt +++ b/magix/magix-zmq/src/main/kotlin/space/kscince/magix/zmq/ZmqMagixFlowPlugin.kt @@ -1,7 +1,7 @@ package space.kscince.magix.zmq import kotlinx.coroutines.* -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.serialization.decodeFromString @@ -18,36 +18,41 @@ public class ZmqMagixFlowPlugin( public val localHost: String = "tcp://*", public val zmqPubSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PUB_PORT, public val zmqPullSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PULL_PORT, + private val zContext: ZContext = ZContext() ) : MagixFlowPlugin { - override fun start(scope: CoroutineScope, magixFlow: MutableSharedFlow): Job = - scope.launch(Dispatchers.IO) { - val logger = LoggerFactory.getLogger("magix-server-zmq") - ZContext().use { context -> - //launch the publishing job - val pubSocket = context.createSocket(SocketType.PUB) - pubSocket.bind("$localHost:$zmqPubSocketPort") - magixFlow.onEach { message -> - val string = MagixEndpoint.magixJson.encodeToString(message) - pubSocket.send(string) - logger.trace("Published: $string") - }.launchIn(this) + override fun start( + scope: CoroutineScope, + receive: Flow, + sendMessage: suspend (MagixMessage) -> Unit, + ): Job = scope.launch(Dispatchers.IO) { + val logger = LoggerFactory.getLogger("magix-server-zmq") - //launch pulling job - val pullSocket = context.createSocket(SocketType.PULL) - pullSocket.bind("$localHost:$zmqPullSocketPort") - pullSocket.receiveTimeOut = 500 - //suspending loop while pulling is active - while (isActive) { - val string: String? = pullSocket.recvStr() - if (string != null) { - logger.trace("Received: $string") - val message = MagixEndpoint.magixJson.decodeFromString(string) - magixFlow.emit(message) - } + zContext.use { context -> + //launch the publishing job + val pubSocket = context.createSocket(SocketType.PUB) + pubSocket.bind("$localHost:$zmqPubSocketPort") + receive.onEach { message -> + val string = MagixEndpoint.magixJson.encodeToString(message) + pubSocket.send(string) + logger.trace("Published: $string") + }.launchIn(this) + + //launch pulling job + val pullSocket = context.createSocket(SocketType.PULL) + pullSocket.bind("$localHost:$zmqPullSocketPort") + pullSocket.receiveTimeOut = 500 + //suspending loop while pulling is active + while (isActive) { + val string: String? = pullSocket.recvStr() + if (string != null) { + logger.trace("Received: $string") + val message = MagixEndpoint.magixJson.decodeFromString(string) + sendMessage(message) } } } + } } \ No newline at end of file diff --git a/magix/rfc b/magix/rfc index 5ae42aa..7f50062 160000 --- a/magix/rfc +++ b/magix/rfc @@ -1 +1 @@ -Subproject commit 5ae42aa297fbd2ab2239601f064e1d1239487590 +Subproject commit 7f50062bd42dd5046110326cae2492ad1f7a195f diff --git a/settings.gradle.kts b/settings.gradle.kts index 21b2467..20ac44e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,8 +41,9 @@ dependencyResolutionManagement { include( ":controls-core", - ":controls-ktor-tcp", + ":controls-ports-ktor", ":controls-serial", + ":controls-pi", ":controls-server", ":controls-opcua", ":controls-modbus", @@ -53,14 +54,13 @@ include( ":magix:magix-api", ":magix:magix-server", ":magix:magix-rsocket", - ":magix:magix-java-client", + ":magix:magix-java-endpoint", ":magix:magix-zmq", ":magix:magix-rabbit", ":magix:magix-mqtt", - -// ":magix:magix-storage", + ":magix:magix-storage", ":magix:magix-storage:magix-storage-xodus", - ":controls-magix-client", + ":controls-magix", ":demo:all-things", ":demo:many-devices", ":demo:magix-demo",